diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a9b31aa --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,49 @@ +# License + +## calog + +MIT License + +Copyright (c) 2026 Scott Duensing + +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. + +--- + +## Third-party components + +calog vendors the following scripting engines under `vendor/`, built from +source. Each is distributed under its own license (all MIT), which applies to +its files; the full text ships alongside each engine's source. + +- **Lua 5.4** — Copyright (C) 1994-2023 Lua.org, PUC-Rio. MIT License. + `vendor/lua/` (see `vendor/lua/README` and the notice in `src/lua.h`). + +- **Duktape** (JavaScript) — Copyright (c) 2013-2019 by Duktape authors. MIT + License. `vendor/duktape/LICENSE.txt`. + +- **Squirrel 3.2** — Copyright (c) 2003-2022 Alberto Demichelis. MIT License. + `vendor/squirrel-src/COPYRIGHT`. + +- **MY-BASIC** — Copyright (C) 2011-2026 Tony Wang. MIT License. Notice in + `vendor/mybasic/myBasic.h`. + + > Note: `vendor/mybasic/myBasic.c` carries one small calog patch (making the + > global allocation counter `_Atomic` so interpreters are thread-safe); the + > unmodified original is preserved as `vendor/mybasic/myBasic.c.orig`. diff --git a/Makefile b/Makefile index bb96ac7..1bbe0da 100644 --- a/Makefile +++ b/Makefile @@ -89,8 +89,8 @@ DUKFLAGS = -std=c99 -w -g -O1 DUKOBJ = obj/duktape.o BINS = bin/testBroker bin/testLua bin/testMyBasic bin/testPolyglot bin/testActor \ - bin/testEngineLua bin/testSquirrel bin/testEngineSquirrel bin/testJs bin/testEngineJs \ - bin/embed + bin/testEngineLua bin/testEngineMyBasic bin/testSquirrel bin/testEngineSquirrel bin/testJs bin/testEngineJs \ + bin/testLoad bin/embed all: $(BINS) @@ -102,7 +102,7 @@ $(STRICTOBJ): obj/%.o: %.c | obj $(CC) $(COREFLAGS) $(INC) -c -o $@ $< # strict C, threaded -THREADOBJ = obj/context.o obj/testActor.o obj/testEngineLua.o obj/testEngineSquirrel.o obj/testEngineJs.o +THREADOBJ = obj/context.o obj/mybasicEngine.o obj/testActor.o obj/testEngineLua.o obj/testEngineSquirrel.o obj/testEngineJs.o obj/testEngineMyBasic.o $(THREADOBJ): obj/%.o: %.c | obj $(CC) $(COREFLAGS) $(INC) -pthread -c -o $@ $< @@ -150,7 +150,7 @@ obj/duktape.o: $(DUKDIR)/duktape.c | obj # (static members are pulled only when referenced). See examples/embed.c. CALOGLIB = obj/value.o obj/broker.o obj/context.o \ obj/luaAdapter.o obj/luaEngine.o obj/jsAdapter.o obj/jsEngine.o \ - obj/squirrelAdapter.o obj/squirrelEngine.o obj/mybasicAdapter.o + obj/squirrelAdapter.o obj/squirrelEngine.o obj/mybasicAdapter.o obj/mybasicEngine.o lib/libcalog.a: $(CALOGLIB) | lib ar rcs $@ $^ @@ -179,6 +179,9 @@ bin/testEngineLua: obj/testEngineLua.o lib/libcalog.a lib/liblua.a | bin bin/testMyBasic: obj/testMyBasic.o lib/libcalog.a lib/libmybasic.a | bin $(CC) $(LDFLAGS) -o $@ $^ -lm +bin/testEngineMyBasic: obj/testEngineMyBasic.o lib/libcalog.a lib/libmybasic.a | bin + $(CC) $(LDFLAGS) -pthread -o $@ $^ -lm + bin/testPolyglot: obj/testPolyglot.o lib/libcalog.a lib/liblua.a lib/libmybasic.a | bin $(CC) $(LDFLAGS) -o $@ $^ $(LUALIBS) @@ -201,13 +204,22 @@ obj/embed.o: examples/embed.c src/calog.h | obj bin/embed: obj/embed.o lib/libcalog.a lib/libduktape.a | bin $(CC) $(LDFLAGS) -pthread -o $@ $^ -lm +# load test: links every engine (compiled with all CALOG_WITH_* engine flags) so it +# exercises calogContextLoad + calogRegisterBuiltinEngines across all four languages -- +# including my-basic under the actor model. Uses only calog.h (the extern engine vtables). +obj/testLoad.o: tests/testLoad.c src/calog.h | obj + $(CC) $(COREFLAGS) -Isrc -pthread -DCALOG_WITH_LUA -DCALOG_WITH_JS -DCALOG_WITH_SQUIRREL -DCALOG_WITH_MYBASIC -c -o $@ $< + +bin/testLoad: obj/testLoad.o lib/libcalog.a lib/liblua.a lib/libduktape.a lib/libsquirrel.a lib/libmybasic.a | bin + $(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS) -lstdc++ -lm + obj bin lib: mkdir -p $@ test: all ./bin/testBroker && ./bin/testLua && ./bin/testMyBasic && ./bin/testPolyglot && \ - ./bin/testActor && ./bin/testEngineLua && ./bin/testSquirrel && ./bin/testEngineSquirrel && \ - ./bin/testJs && ./bin/testEngineJs + ./bin/testActor && ./bin/testEngineLua && ./bin/testEngineMyBasic && ./bin/testSquirrel && ./bin/testEngineSquirrel && \ + ./bin/testJs && ./bin/testEngineJs && ./bin/testLoad # ThreadSanitizer build of the actor core and the Lua engine path (cannot combine # with ASan). Recompiled from source under TSan; the vendored Lua objects are @@ -245,9 +257,21 @@ tsanjs: | bin obj setarch -R ./bin/testEngineJsTsan rm -f obj/duktape.tsan.o +# ThreadSanitizer build of the my-basic engine path: the vendored interpreter is +# recompiled under TSan (throwaway obj) so the engine's serialization of my-basic's +# process-global state (mb_init singletons, the _mb_allocated counter) is verified +# across the whole stack -- it races without the engine lock. +tsanmb: | bin obj + $(CC) $(MBFLAGS) -fsanitize=thread -c $(MBDIR)/myBasic.c -o obj/myBasic.tsan.o + $(CC) -std=c11 $(WARN) -g -O1 -fsanitize=thread -pthread $(INC) $(MBINC) -DMB_DOUBLE_FLOAT -o bin/testEngineMyBasicTsan \ + tests/testEngineMyBasic.c src/mybasic/mybasicEngine.c src/mybasic/mybasicAdapter.c src/context.c src/value.c src/broker.c \ + obj/myBasic.tsan.o -lm + setarch -R ./bin/testEngineMyBasicTsan + rm -f obj/myBasic.tsan.o + clean: rm -rf obj bin lib -include $(wildcard obj/*.d) -.PHONY: all test tsan tsansq tsanjs clean +.PHONY: all test tsan tsansq tsanjs tsanmb clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..212d7f2 --- /dev/null +++ b/README.md @@ -0,0 +1,374 @@ +# calog + +**C: Any Language, One Gateway.** + +calog embeds scripting into a C or C++ program. You register your native C functions +**once**, and they become callable from **any** embedded engine — Lua, JavaScript, +Squirrel, or MY-BASIC — with values passed through a single canonical type. Add a new +scripting language to your app by linking one more archive; your native functions don't +change. + +```c +#include "calog.h" + +CalogT *calog = calogCreate(); +calogRegister(calog, "hostLog", hostLog, NULL); // your C function, once + +CalogContextT *ctx = calogContextOpen(calog, &calogJsEngine); +calogContextEval(ctx, "hostLog('hello from JavaScript, ' + 6 * 7)"); +``` + +Swap `&calogJsEngine` for `&calogLuaEngine`, `&calogSquirrelEngine`, or +`&calogMyBasicEngine` — nothing else changes. + +--- + +## Why calog + +- **One API, many languages.** Register a native once; call it from every engine. + `calog.h` is the entire public surface. +- **One value type.** Everything crosses the boundary as a `CalogValueT` — nil, bool, + int, real, binary-safe string, a hybrid list/map aggregate, or a script function + value. No per-engine glue in your code. +- **Natives run on your thread, serialized.** A script calling a native is marshalled to + the host thread and run there during `calogPump()`, one at a time — so your C code is + never called concurrently and needs **no locking**. (An opt-in escape hatch runs a + native inline on the script's thread when you want it.) +- **Fire-and-forget scripts.** Scripts run on their own context threads; the host stays + responsive and drives everything from its own loop. +- **Callbacks both ways.** A script can hand you a function value; you keep it and call + it later, and calog routes the call back to the engine that owns it. +- **Many runtimes.** Independent `CalogT` runtimes coexist in one process; one host + thread can drive several. +- **Load by filename.** `calogContextLoad(calog, "config")` finds `config.lua` / + `config.js` / `config.nut` / `config.bas` and runs it on the matching engine. +- **Reproducible & rigorous.** Every engine is vendored and built from source (no system + packages). The core is warning-clean on gcc **and** clang (`-Wall -Wextra -Werror + -Wconversion -Wsign-conversion`) and runs clean under AddressSanitizer, UBSan, and + ThreadSanitizer. + +## Engines + +| Engine | Vtable | Extension | Notes | +|---------------|------------------------|-----------|--------------------------------| +| Lua 5.4 | `calogLuaEngine` | `.lua` | | +| JavaScript | `calogJsEngine` | `.js` | Duktape | +| Squirrel 3.2 | `calogSquirrelEngine` | `.nut` | C++ VM | +| MY-BASIC | `calogMyBasicEngine` | `.bas` | interpreters serialize at load | + +You can also bring your own: `CalogEngineT` is a public four-function vtable. + +--- + +## Build + +Requirements: a POSIX system (pthreads), `gcc` or `clang`, and GNU `make`. Developed on +Linux; macOS should work, Windows via WSL. + +```sh +make # builds lib/libcalog.a, the per-engine archives, tests, and the example +make test # runs the full suite under AddressSanitizer + UBSan +``` + +The engines are vendored under `vendor/` and compiled from source, so the build is +self-contained. + +### Linking your program + +Link against `lib/libcalog.a` plus the archive for **each engine you actually use** — +unused engines (and their vendored runtimes) stay out of your binary. + +```sh +# a JavaScript-only host: +cc -Isrc myhost.c lib/libcalog.a lib/libduktape.a -pthread -lm -o myhost +``` + +Per-engine link needs: + +| Engine | Archive | Extra libs (Linux) | +|----------|--------------------|----------------------| +| Lua | `lib/liblua.a` | `-ldl -lm` | +| JS | `lib/libduktape.a` | `-lm` | +| Squirrel | `lib/libsquirrel.a`| `-lstdc++ -lm` | +| MY-BASIC | `lib/libmybasic.a` | `-lm` | + +Everything links with `-pthread`. + +--- + +## A complete example + +```c +#include "calog.h" + +#include +#include + +// A native the host provides. It runs on the host thread during calogPump, serialized +// with every other native, so it can touch host state without locking. +static int32_t hostLog(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)userData; + calogValueNil(result); + if (argCount != 1 || args[0].type != calogStringE) { + return calogFail(result, calogErrArgE, "hostLog expects one string"); + } + printf("[host] %s\n", args[0].as.s.bytes); + return calogOkE; +} + +int main(void) { + CalogT *calog = calogCreate(); + calogRegister(calog, "hostLog", hostLog, NULL); + + CalogContextT *ctx = calogContextOpen(calog, &calogJsEngine); + calogContextEval(ctx, "hostLog('hello from JavaScript, ' + (6 * 7))"); + + // The script runs on its own thread and calls hostLog; the host services those + // calls by pumping in its own loop. (A real app pumps inside its main loop.) + struct timespec tick = { 0, 1000000 }; // 1 ms + for (int i = 0; i < 50; i++) { + calogPump(calog); + nanosleep(&tick, NULL); + } + + calogContextClose(ctx); + calogDestroy(calog); + return 0; +} +``` + +See [`examples/embed.c`](examples/embed.c) for this, ready to build (`make` produces +`bin/embed`). + +--- + +## How it works + +### The canonical value + +Every value crossing the C/script boundary is a `CalogValueT`: + +```c +typedef enum { calogNilE, calogBoolE, calogIntE, calogRealE, + calogStringE, calogAggE, calogFnE } CalogTypeE; + +struct CalogValueT { + CalogTypeE type; + union { bool b; int64_t i; double r; + CalogStringT s; CalogAggT *agg; CalogFnT *fn; } as; +}; +``` + +Build them with the constructors, and free owned values you're done with: + +```c +CalogValueT v; +calogValueInt(&v, 42); +calogValueString(&v, "binary\0safe", 11); // length-prefixed, NUL-safe +// ... calogValueBool / calogValueReal / calogValueAgg / calogValueFn ... +calogValueFree(&v); +``` + +`CalogAggT` is a hybrid **list + map** (`calogAggPush`, `calogAggSet`, `calogAggGet`), +so it round-trips onto engines that model sequences and dictionaries differently. +Strings are binary-safe (length-prefixed, always NUL-terminated for convenience). + +### Registering natives + +```c +// Runs on the HOST thread (serialized). This is what you want almost always. +calogRegister(calog, "add", add, NULL); + +// Escape hatch: runs INLINE on the calling script's thread. Faster (no host hop), +// but YOU guarantee it is thread-safe. +calogRegisterInline(calog, "clockNow", clockNow, NULL); +``` + +A native has one signature and reports failure through `result`: + +```c +static int32_t add(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)userData; + if (argCount != 2 || args[0].type != calogIntE || args[1].type != calogIntE) { + return calogFail(result, calogErrArgE, "add expects two integers"); + } + calogValueInt(result, args[0].as.i + args[1].as.i); + return calogOkE; +} +``` + +Every registered native is automatically visible in every context you open. + +### Contexts, scripts, and the pump + +A **context** is one engine instance on its own thread. `calogContextEval` is +**fire-and-forget**: it queues the script and returns immediately. + +```c +CalogContextT *ctx = calogContextOpen(calog, &calogLuaEngine); +calogContextEval(ctx, "hostLog('running')"); // returns now; script runs on ctx's thread +``` + +Because natives are serviced on the host thread, the host must **pump**: + +```c +for (;;) { + calogPump(calog); // runs any pending native calls here, then returns + do_other_host_work(); +} +``` + +Script results come back by **calling natives** — `calogContextEval` doesn't return a +value. Script errors are delivered to an optional handler on the host thread during +`calogPump`: + +```c +static void onError(uint64_t contextId, const char *message, void *userData) { + fprintf(stderr, "script %llu failed: %s\n", (unsigned long long)contextId, message); +} +calogSetErrorHandler(calog, onError, NULL); // default logs to stderr +``` + +### Callbacks: script functions as values + +A script can pass you a function. Keep it (retain), call it later, release it when done — +calog runs the call on the engine that owns it and returns the result to you: + +```c +static CalogFnT *savedCb = NULL; + +static int32_t onEvent(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)userData; + calogValueNil(result); + if (argCount != 1 || args[0].type != calogFnE) { + return calogFail(result, calogErrArgE, "onEvent expects a function"); + } + calogFnRetain(args[0].as.fn); // keep it beyond this call + savedCb = args[0].as.fn; + return calogOkE; +} + +// ...later, from the host: +CalogValueT arg, out; +calogValueInt(&arg, 42); +calogFnInvoke(savedCb, &arg, 1, &out); // routed to the script's thread, result returned here +calogValueFree(&out); +calogValueFree(&arg); +calogFnRelease(savedCb); +``` + +### Loading a script by filename + +Register the engines you want searchable (order is priority), then load by base name: + +```c +calogRegisterEngine(calog, &calogLuaEngine); +calogRegisterEngine(calog, &calogJsEngine); + +CalogContextT *ctx = calogContextLoad(calog, "config"); // tries config.lua, then config.js +``` + +Or let the build decide which engines exist. Compile the calling file with +`-DCALOG_WITH_LUA -DCALOG_WITH_JS …` and call the header-inline helper — it registers +exactly the engines you linked, and references no others: + +```c +calogRegisterBuiltinEngines(calog); +CalogContextT *ctx = calogContextLoad(calog, "config"); +``` + +### Multiple runtimes + +A `CalogT` is a self-contained runtime; you can create several in one process. Each is +driven by a host thread, and one thread may drive several by pumping each in turn. Keep +values and callbacks within a single runtime — they don't cross between runtimes. + +--- + +## API at a glance + +```c +// runtime +CalogT *calogCreate(void); +void calogDestroy(CalogT *); +void calogPump(CalogT *); // service native calls here +void calogSetErrorHandler(CalogT *, CalogErrorFnT, void *); + +// natives +int32_t calogRegister(CalogT *, const char *name, CalogNativeFnT, void *userData); // host thread +int32_t calogRegisterInline(CalogT *, const char *name, CalogNativeFnT, void *userData); // caller thread +int32_t calogCall(CalogT *, const char *name, CalogValueT *args, int32_t n, CalogValueT *result); +int32_t calogFail(CalogValueT *result, int32_t status, const char *message); +const char *calogTypeName(CalogTypeE); + +// values +void calogValueNil/Bool/Int/Real/Agg/Fn(...); +int32_t calogValueString(CalogValueT *, const char *bytes, int64_t length); +int32_t calogValueCopy(CalogValueT *dst, const CalogValueT *src); +void calogValueMove(CalogValueT *dst, CalogValueT *src); +void calogValueFree(CalogValueT *); +bool calogValueEquals(const CalogValueT *, const CalogValueT *); + +// aggregates (hybrid list + map) +int32_t calogAggCreate(CalogAggT **, CalogKindE); +void calogAggFree(CalogAggT *); +int32_t calogAggPush(CalogAggT *, CalogValueT *value); +int32_t calogAggSet(CalogAggT *, CalogValueT *key, CalogValueT *value); +CalogValueT *calogAggGet(CalogAggT *, const CalogValueT *key); + +// function values (script callbacks) +int32_t calogFnInvoke(CalogFnT *, CalogValueT *args, int32_t n, CalogValueT *result); +void calogFnRetain(CalogFnT *); +void calogFnRelease(CalogFnT *); + +// contexts + engines +CalogContextT *calogContextOpen(CalogT *, const CalogEngineT *); +void calogRegisterEngine(CalogT *, const CalogEngineT *); +CalogContextT *calogContextLoad(CalogT *, const char *baseFileName); +int32_t calogContextEval(CalogContextT *, const char *source); // fire-and-forget +void calogContextClose(CalogContextT *); +uint64_t calogContextId(const CalogContextT *); +uint64_t calogCurrentId(void); // 0 on the host thread +CalogT *calogCurrent(void); +``` + +Statuses are `CalogStatusE` (`calogOkE`, `calogErrArgE`, `calogErrOomE`, …); `calogOkE` +is 0. + +--- + +## Testing + +```sh +make test # full suite under AddressSanitizer + UBSan +make tsan # ThreadSanitizer: actor core + Lua +make tsansq # ThreadSanitizer: Squirrel +make tsanjs # ThreadSanitizer: JavaScript +make tsanmb # ThreadSanitizer: MY-BASIC +``` + +The ThreadSanitizer targets run under `setarch -R` (ASLR off) so TSan's shadow allocator +is happy on all kernels. + +## Project layout + +``` +src/ core (broker, values, actor) + one subdir per engine (lua, js, squirrel, mybasic) + calog.h the entire public API +tests/ the test programs +examples/ embed.c — a minimal host +vendor/ vendored engine sources, built from source +design.md the full design rationale and internals +``` + +## Design + +[`design.md`](design.md) is the long-form record: the canonical value model, the +actor/threading design, the function-value lifecycle, per-engine notes, and the +build system. + +## License + +calog is released under the [MIT License](LICENSE.md). The vendored engines (Lua, +Duktape, Squirrel, MY-BASIC) each retain their own MIT licenses — see +[`LICENSE.md`](LICENSE.md) for the third-party notices. diff --git a/design.md b/design.md index c4276c7..948fd97 100644 --- a/design.md +++ b/design.md @@ -627,7 +627,122 @@ dependencies automatically. `make clean` removes `obj/` and `bin/`. `make test` runs all ten binaries; `make tsan`/`make tsansq`/`make tsanjs` are the ThreadSanitizer variants. -## 15. Public embedding API (`calog.h`) -- as-built +## 16. Threading model rewrite -- host-thread natives, fire-and-forget scripts + +Supersedes the earlier "natives run inline on the calling context thread" model. The +host's own thread is now an implicit **host context (id 0)**: it has a queue but no OS +thread of its own, and the host drives it by calling **`calogPump`** in its loop. + +- **Scripts are fire-and-forget.** `calogContextEval(ctx, src)` enqueues the script + onto the context's thread and returns a status (not the result); the script runs + asynchronously, and results come back by calling natives. +- **A registered native runs on the host thread, serialized.** A script calling one + posts a CALL onto the host queue and parks; the host runs it during `calogPump`. So + host C code is never called concurrently and needs no locking. `actorRoute` inlines a + call already on the host thread; otherwise it marshals to the host context (id 0). +- **`calogRegisterInline`** is the escape hatch: the registry entry's `runInline` flag + (which replaced `ownerCtxId`) makes the native run on the calling script's thread. +- **Errors** from a fire-and-forget script are posted to the host queue and delivered + to the `CalogErrorFnT` handler (`calogSetErrorHandler`) during `calogPump` (default: + log to stderr). +- **Function values** (`CalogFnT`) still run on their owning engine's thread -- sec 10 + routing is unchanged, and `calogFnInvoke` from the host blocks-and-pumps the host + queue while it waits (the same nested pump, now applied to the host context). +- **Nested eval is allowed:** a new eval that arrives while a context is mid-script + (parked on a native call) runs nested via `pumpUntil` -- consistent with the sec-6.1 + re-entrancy contract (interpreters support nested `pcall`/`peval`). + +API shape: `calogRegister(c,name,fn,ud)` / `calogRegisterInline(...)`; +`calogContextOpen(c,engine) -> CalogContextT*` (create+start merged, since nothing is +registered between them anymore) and `calogContextClose`; `calogContextEval(ctx,src)` +fire-and-forget; `calogPump`; `calogSetErrorHandler`. `CalogConfigT` and the +`createInterpreter` config parameter are gone -- a context now exposes *every* +registered native (the engine binding walks the registry via the internal +`calogForEach`). Tests rewritten to drive calog the host way (register, open, eval, +pump-until-a-native-records-the-result); `testActor` is now engine-free, exercising the +dispatch machinery with C callables (`calogFnCreate`) on synthetic contexts. Verified: +`make test` 441 checks across 11 binaries (incl. `examples/embed.c`), gcc + clang +strict, ASan/UBSan + TSan clean (`make tsan`/`tsansq`/`tsanjs`). + +**`CalogT` owns its contexts; ids are unbounded.** The active-context registry moved +from `context.c` file-static globals *into* `struct CalogT` (now defined in +`calogInternal.h`): a runtime owns both its native-function registry and its +active-context registry (`ctxMutex`, `ctxSlots`, freelist). `context.c` reaches it via +one `runtime` pointer set in `calogActorInit` (which also refuses a second runtime). +So `calogDestroy` closes every still-open context automatically -- the host need not +track them (a test opens 32 and never closes them; ASan confirms no leak). Context ids +widened to **`uint64`** (32-bit slot index + 32-bit generation), so neither the live +count nor open/close churn hits a preset ceiling; `calogContextId`/`calogCurrentId` +return `uint64_t`. The now-dead `ownerGen` parameter was dropped from `calogFnCreate` +(generation lives in the packed id). Re-verified: `make test` 473 checks, ASan no +leaks, TSan clean, gcc + clang strict. + +**Independent runtimes in one process.** The one-runtime limit was not fundamental -- +just process-global state that hadn't moved into `CalogT`. All of it now has: the host +context, the routing hooks (`routeHook`/`invokeHook`/`releaseHook`), and the error sink +are `CalogT` fields; a `CalogFnT` carries a `runtime` pointer so `calogFnInvoke`/release +reach the right hooks (the callable path has no `CalogT` otherwise). The dispatch +reaches its runtime through the object it already holds -- the route hook is handed its +`calog`, the callable hooks read `calogFnRuntime`, context-thread code uses +`context->broker`, and the rest take an explicit `calog` argument. The **only** remaining +process global is `currentContext`, and it is thread-local (it names the calling +thread's context). So the setters (`calogSetRouteHook` etc.) are gone -- `calogActorInit` +assigns the fields directly -- and the `runtime` static that the earlier review flagged +is deleted. Runtimes are isolated: don't pass a value or callable between them (a +cross-runtime reply cannot route). A test spawns N threads, each creating, driving, and +destroying its own runtime concurrently. + +**One thread may host several runtimes.** `calogPump(calog)` sets `currentContext` to +`calog`'s host context for the drain and restores it after, so a single thread can drive +many runtimes by pumping each in turn -- a native serviced during `calogPump(A)` sees +`calogCurrent() == A` even if the thread also hosts B. Two consequences fall out and are +handled: context ids number from 1 in every runtime, so the "already on the owner's +thread" (inline) and "caller can take the token/pump path" (reply) decisions match the +*runtime* too, not the id alone -- a foreign or wrong-runtime caller takes a reply box, +which cannot misroute. A test creates two runtimes on one thread, runs a script in each, +and pumps both in a loop, asserting each runtime's native resolved `calogCurrent()` to +its own runtime (it fails if the pump doesn't rebind `currentContext`). Re-verified: +`make test` 480 checks, ASan no leaks, TSan clean (both concurrent runtimes and one +thread pumping two), gcc + clang strict. + +**Loading a script by filename.** Each engine carries a NULL-terminated `extensions` +list (`{"lua"}` / `{"js"}` / `{"nut"}` / `{"bas"}`), and a host makes engines available +for filename-based loading with `calogRegisterEngine(calog, &engine)`. +`calogContextLoad(calog, base)` then walks the registered engines in registration order +(each engine's extensions in order), forms `"."`, and the first one that +`fopen`s wins: it reads the file on the calling thread, opens a context on that engine, +and loads the contents fire-and-forget -- returning the context (NULL if nothing matched +or the load failed). Registration matters for more than search order: hardcoding the +built-in engine vtables in the core would force-link *all* of them (and their vendored +runtimes) into every binary, defeating the per-engine archives -- so the host opts in, +and a binary that never references an engine pulls in none (`testActor` stays +engine-free). Engine selection is fundamentally a build-time (link) choice, so +`calogRegisterBuiltinEngines` (a header-inline in `calog.h`) registers exactly the +engines whose `CALOG_WITH_` macro is set -- the host defines those alongside the +archives it links, and the inline emits nothing (references no engine) unless called, so +it never force-links. + +**my-basic as an actor engine.** Making my-basic loadable meant running it under the +actor model for the first time, which exposed two things. (1) Its native dispatch called +the C function *directly* instead of through `calogCall`, so natives ran on the my-basic +context thread rather than marshalling to the host -- fixed by routing `mbDispatch` +through `calogCall` (the binding now stores the registry name), matching the other +engines. (2) my-basic keeps process-global state -- lazy `mb_init` singletons and a +global `_mb_allocated` counter touched on every allocation (forced on in the vendored +header) -- so two my-basic contexts on different threads race (TSan-confirmed). The +singletons are built once by `mb_init` and read-only thereafter, so the only +execution-time shared write is that counter; a one-line vendored patch makes it +`_Atomic` (the original is preserved as `vendor/mybasic/myBasic.c.orig`). With the +counter safe, the my-basic *engine* (not the adapter, which stays usable single-threaded +and lock-free) needs a lock only across *lifecycle* -- `mb_init`'s first-context build, +`mb_dispose`'s last-context teardown, and the shared context refcount -- and NOT across +`runSource`, so several my-basic scripts execute concurrently. A `tsanmb` target proves +the parallel case is race-free (verified further by a 4-context stress running +arithmetic, strings, lists, and booleans). Verified: `make test` 494 checks (13 +binaries), ASan no leaks, TSan clean on all four engines +(`tsan`/`tsansq`/`tsanjs`/`tsanmb`), gcc + clang strict. + +## 15. Public embedding API (`calog.h`) -- as-built (superseded by sec 16 for threading/API) calog is packaged as an embedding library: a host links it, registers its own native C functions, creates script contexts on an engine, and runs scripts. Every public diff --git a/examples/embed.c b/examples/embed.c index 068521b..6e69718 100644 --- a/examples/embed.c +++ b/examples/embed.c @@ -1,15 +1,20 @@ -// examples/embed.c -- add scripting to a C program with calog, in ~30 lines. +// examples/embed.c -- add scripting to a C program with calog, in ~40 lines. // // The entire embedding surface is calog.h: create a runtime, register a native C -// function the host provides, create a script context on an engine that exposes it, -// and run a script that calls back into the host. Swap calogJsEngine for -// calogLuaEngine / calogSquirrelEngine to change languages -- nothing else changes. +// function the host provides, open a script context on an engine, fire-and-forget a +// script, and calogPump on the host thread to service the script's calls back into +// the host. Swap calogJsEngine for calogLuaEngine / calogSquirrelEngine to change +// languages -- nothing else changes. + +#define _POSIX_C_SOURCE 200809L #include "calog.h" #include +#include -// A host-provided native, callable from any embedded engine: hostLog(message). +// A host-provided native, callable from any embedded engine. Runs on the host thread +// (this one), during calogPump -- so it can touch host state without locking. static int32_t hostLog(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)userData; calogValueNil(result); @@ -22,21 +27,25 @@ static int32_t hostLog(CalogValueT *args, int32_t argCount, CalogValueT *result, int main(void) { - CalogT *calog; - CalogContextT *ctx; - CalogValueT result; - const char *expose[] = { "hostLog" }; - CalogConfigT config = { expose, 1 }; + CalogT *calog; + CalogContextT *ctx; + struct timespec tick = { 0, 1000000 }; // 1 ms + int i; calog = calogCreate(); - calogRegister(calog, "hostLog", hostLog, NULL, 0); + calogRegister(calog, "hostLog", hostLog, NULL); - calogContextCreate(calog, &calogJsEngine, &config, &ctx); - calogContextStart(ctx); - calogContextEval(ctx, "hostLog('hello from JavaScript, ' + (6 * 7))", &result); - calogValueFree(&result); + ctx = calogContextOpen(calog, &calogJsEngine); + calogContextEval(ctx, "hostLog('hello from JavaScript, ' + (6 * 7))"); - calogContextDestroy(ctx); + // A real host pumps calog inside its own main loop; here we tick briefly so the + // fire-and-forget script gets to run and call hostLog on this thread. + for (i = 0; i < 50; i++) { + calogPump(calog); + nanosleep(&tick, NULL); + } + + calogContextClose(ctx); calogDestroy(calog); return 0; } diff --git a/src/broker.c b/src/broker.c index b4db0bf..27977c9 100644 --- a/src/broker.c +++ b/src/broker.c @@ -25,17 +25,10 @@ _Static_assert((BROKER_INITIAL_SLOTS & (BROKER_INITIAL_SLOTS - 1)) == 0, "registry slot count must be a power of two"); _Static_assert((CALOG_GROWTH_FACTOR & (CALOG_GROWTH_FACTOR - 1)) == 0, "registry growth factor must be a power of two"); -struct CalogT { - CalogEntryT *slots; - int64_t slotCount; - int64_t entryCount; -}; - -static CalogRouteFnT routeHook = NULL; - static int32_t brokerGrow(CalogT *broker); static uint64_t hashName(const char *name); static CalogEntryT *probeSlot(CalogT *broker, const char *name); +static int32_t registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline); int32_t calogCall(CalogT *broker, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result) { @@ -46,8 +39,8 @@ int32_t calogCall(CalogT *broker, const char *name, CalogValueT *args, int32_t a if (entry == NULL) { return calogFail(result, calogErrNotFoundE, "no such function"); } - if (routeHook != NULL) { - return routeHook(entry, args, argCount, result); + if (broker->routeHook != NULL) { + return broker->routeHook(broker, entry, args, argCount, result); } return entry->fn(args, argCount, result, entry->userData); } @@ -81,6 +74,7 @@ void calogBrokerDestroy(CalogT *broker) { free(broker->slots[index].name); } free(broker->slots); + free(broker->engines); free(broker); } @@ -97,6 +91,17 @@ int32_t calogFail(CalogValueT *result, int32_t status, const char *message) { } +void calogForEach(CalogT *broker, void (*visit)(const CalogEntryT *entry, void *ud), void *ud) { + int64_t index; + + for (index = 0; index < broker->slotCount; index++) { + if (broker->slots[index].name != NULL) { + visit(&broker->slots[index], ud); + } + } +} + + static int32_t brokerGrow(CalogT *broker) { CalogEntryT *oldSlots; CalogEntryT *newSlots; @@ -137,18 +142,28 @@ CalogEntryT *calogLookup(CalogT *broker, const char *name) { } -int32_t calogRegister(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, uint32_t ownerCtxId) { +int32_t calogRegister(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) { + return registerEntry(broker, name, fn, userData, false); +} + + +int32_t calogRegisterInline(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) { + return registerEntry(broker, name, fn, userData, true); +} + + +static int32_t registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline) { CalogEntryT *slot; - char *nameCopy; - int32_t status; + char *nameCopy; + int32_t status; slot = probeSlot(broker, name); if (slot->name != NULL) { - // Re-registration replaces the existing binding in place: allocation - // free, so it never grows and never fails. - slot->fn = fn; - slot->userData = userData; - slot->ownerCtxId = ownerCtxId; + // Re-registration replaces the existing binding in place: allocation free, + // so it never grows and never fails. + slot->fn = fn; + slot->userData = userData; + slot->runInline = runInline; return calogOkE; } if ((broker->entryCount + 1) * BROKER_PERCENT_SCALE >= broker->slotCount * BROKER_LOAD_PERCENT) { @@ -162,20 +177,15 @@ int32_t calogRegister(CalogT *broker, const char *name, CalogNativeFnT fn, void if (nameCopy == NULL) { return calogErrOomE; } - slot->name = nameCopy; - slot->fn = fn; - slot->userData = userData; - slot->ownerCtxId = ownerCtxId; + slot->name = nameCopy; + slot->fn = fn; + slot->userData = userData; + slot->runInline = runInline; broker->entryCount++; return calogOkE; } -void calogSetRouteHook(CalogRouteFnT hook) { - routeHook = hook; -} - - static uint64_t hashName(const char *name) { uint64_t hash; size_t index; diff --git a/src/calog.h b/src/calog.h index d901391..a032557 100644 --- a/src/calog.h +++ b/src/calog.h @@ -1,13 +1,23 @@ // calog.h -- embed multi-language scripting into a C/C++ host. // -// calog is a script broker: register native C functions once and call them from -// any embedded engine (Lua, JavaScript/Duktape, Squirrel, my-basic), passing values -// by a single canonical type. This header is the entire public surface -- create a -// runtime, register natives, create script contexts on an engine, run scripts, and -// exchange values. Internal machinery lives in calogInternal.h and is not needed to -// embed calog. One runtime per process (the actor/registry state is process-global). +// calog is a script broker: register native C functions once and call them from any +// embedded engine (Lua, JavaScript/Duktape, Squirrel, my-basic), passing values by a +// single canonical type. This header is the entire public surface. A CalogT is a +// self-contained runtime -- independent runtimes may coexist in one process, and one +// host thread may drive several by pumping each in turn (don't share values across +// them). See design.md. // -// See design.md for the full architecture. +// Threading model (design.md sec 6): +// - Scripts run fire-and-forget on their own context threads; calogContextEval +// returns immediately. +// - A registered native runs on the HOST thread (the thread that calls +// calogCreate/calogPump), serialized, so host C code is never called +// concurrently and needs no locking. The host services those calls by calling +// calogPump() in its own loop. +// - calogRegisterInline is the escape hatch: an inline native runs directly on the +// calling script's thread (no host hop) -- YOU guarantee it is thread-safe. +// - A script error is delivered to the error handler (calogSetErrorHandler) on the +// host thread during calogPump; script results come back by calling natives. #ifndef CALOG_H #define CALOG_H @@ -17,13 +27,12 @@ #include // Maximum nesting depth honored by the recursive copy/marshal paths. A deeper -// structure, or a cycle, fails with calogErrDepthE instead of recursing without -// bound; the free path relies on the same invariant (aggregates must be acyclic). +// structure, or a cycle, fails with calogErrDepthE; the free path relies on the same +// invariant (aggregates must be acyclic). #define CALOG_MAX_DEPTH 64 -// Growth multiplier for growable buffers (aggregate storage, registry table). Must -// stay a power of two: the registry derives its probe mask from a power-of-two slot -// count. +// Growth multiplier for growable buffers. Must stay a power of two (the registry +// derives its probe mask from a power-of-two slot count). #define CALOG_GROWTH_FACTOR 2 typedef enum CalogStatusE { @@ -48,8 +57,8 @@ typedef enum CalogTypeE { calogFnE = 6 } CalogTypeE; -// Disambiguates an empty or mixed container so it round-trips deterministically -// onto engines (such as my-basic) that model lists and dicts as distinct values. +// Disambiguates an empty or mixed container so it round-trips deterministically onto +// engines (such as my-basic) that model lists and dicts as distinct values. typedef enum CalogKindE { calogListE = 0, calogMapE = 1, @@ -65,8 +74,7 @@ typedef struct CalogStringT CalogStringT; typedef struct CalogValueT CalogValueT; // Length-prefixed, binary-safe string. bytes is always NUL-terminated at -// bytes[length] so plain C consumers can read it, while length permits embedded -// NUL bytes. +// bytes[length] so plain C consumers can read it, while length permits embedded NULs. struct CalogStringT { char *bytes; int64_t length; @@ -90,8 +98,7 @@ struct CalogPairT { }; // Hybrid container: the sequence part lives in array[0, arrayCount), the keyed part -// in pairs[0, pairCount). A Lua/JS table fills both as needed; a my-basic LIST maps -// to array, a DICT to pairs. Only scalar and string keys are meaningful (see +// in pairs[0, pairCount). Only scalar and string keys are meaningful (see // calogValueEquals); aggregate/function keys match by pointer identity only. struct CalogAggT { CalogKindE kind; @@ -103,39 +110,40 @@ struct CalogAggT { int64_t pairCap; }; -// The uniform native-function signature. Write a function once against this and -// register it once; every engine can then call it. result is nil on entry; on -// failure write a message into result with calogFail and return a non-zero -// CalogStatusE. +// The uniform native-function signature. result is nil on entry; on failure write a +// message into result with calogFail and return a non-zero CalogStatusE. typedef int32_t (*CalogNativeFnT)(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +// Delivered on the host thread (during calogPump) when a fire-and-forget script +// fails: contextId is the failing context, message is the engine's error text. +typedef void (*CalogErrorFnT)(uint64_t contextId, const char *message, void *userData); + // Per-engine interpreter lifecycle + execution. Every hook runs on the context's OWN -// thread. createInterpreter receives the config passed to calogContextCreate; a NULL -// engine makes a synthetic context with no interpreter. Embedders may supply a custom -// engine, but normally pass one of the built-in vtables below. +// thread. createInterpreter exposes every registered native (calog does this via the +// adapter); a NULL engine makes a synthetic context with no interpreter. Embedders +// may supply a custom engine, but normally pass a built-in vtable below. typedef struct CalogEngineT { - const char *name; - int32_t (*createInterpreter)(CalogContextT *context, void *config, void **interpOut); + const char *name; + // NULL-terminated list of file extensions this engine handles, without the leading + // dot (e.g. {"lua", NULL}). calogContextLoad uses it to pick an engine by filename. + const char *const *extensions; + int32_t (*createInterpreter)(CalogContextT *context, void **interpOut); void (*destroyInterpreter)(void *interp); int32_t (*runSource)(void *interp, const char *source, CalogValueT *result); } CalogEngineT; -// Config for the built-in engine vtables: the broker natives to expose into the new -// interpreter (installed on the context's own thread). May be passed as NULL. -typedef struct CalogConfigT { - const char *const *exposeNames; - int32_t exposeCount; -} CalogConfigT; - // ---- runtime ---- -CalogT *calogCreate(void); -void calogDestroy(CalogT *calog); -int32_t calogRegister(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData, uint32_t ownerCtxId); -int32_t calogCall(CalogT *calog, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result); +CalogT *calogCreate(void); +void calogDestroy(CalogT *calog); +void calogPump(CalogT *calog); // run pending script->native calls on THIS thread +void calogSetErrorHandler(CalogT *calog, CalogErrorFnT fn, void *userData); -// ---- writing natives ---- -int32_t calogFail(CalogValueT *result, int32_t status, const char *message); -const char *calogTypeName(CalogTypeE type); +// ---- natives ---- +int32_t calogRegister(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData); // runs on the host thread +int32_t calogRegisterInline(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData); // runs on the calling script thread +int32_t calogCall(CalogT *calog, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result); +int32_t calogFail(CalogValueT *result, int32_t status, const char *message); +const char *calogTypeName(CalogTypeE type); // ---- values ---- void calogValueNil(CalogValueT *value); @@ -163,16 +171,46 @@ void calogFnRetain(CalogFnT *fn); void calogFnRelease(CalogFnT *fn); // ---- contexts + engines ---- -int32_t calogContextCreate(CalogT *calog, const CalogEngineT *engine, void *config, CalogContextT **out); -int32_t calogContextStart(CalogContextT *context); -int32_t calogContextEval(CalogContextT *context, const char *source, CalogValueT *result); -void calogContextDestroy(CalogContextT *context); -uint32_t calogContextId(const CalogContextT *context); -uint32_t calogCurrentId(void); // context id of the calling thread (0 if none) -CalogT *calogCurrent(void); // runtime of the calling context (NULL if none) +CalogContextT *calogContextOpen(CalogT *calog, const CalogEngineT *engine); // create + start; NULL on failure +// Make an engine available to calogContextLoad. Call at setup, before loading; +// registration order is the search priority. +void calogRegisterEngine(CalogT *calog, const CalogEngineT *engine); +// Search the registered engines for a readable file "." (first +// engine, then first extension, that names an existing file wins), open a context on +// that engine, load the file into it (fire-and-forget), and return the context. NULL if +// no file matched or the load failed. +CalogContextT *calogContextLoad(CalogT *calog, const char *baseFileName); +int32_t calogContextEval(CalogContextT *context, const char *source); // fire-and-forget +void calogContextClose(CalogContextT *context); +uint64_t calogContextId(const CalogContextT *context); +uint64_t calogCurrentId(void); // context id of the calling thread (0 = host thread) +CalogT *calogCurrent(void); // runtime of the calling context (NULL if none) extern const CalogEngineT calogLuaEngine; extern const CalogEngineT calogJsEngine; extern const CalogEngineT calogSquirrelEngine; +extern const CalogEngineT calogMyBasicEngine; + +// Register the built-in engines selected at build time, so calogContextLoad works +// without hand-registering each. Define CALOG_WITH_LUA / _JS / _SQUIRREL / _MYBASIC (in +// the TU that calls this -- normally via your build's engine flags) for each engine you +// link; only those are referenced here, so unselected engines and their archives stay +// unlinked. The listed order is the load-search priority. Being header-inline, this +// emits nothing unless actually called. +static inline void calogRegisterBuiltinEngines(CalogT *calog) { +#ifdef CALOG_WITH_LUA + calogRegisterEngine(calog, &calogLuaEngine); +#endif +#ifdef CALOG_WITH_JS + calogRegisterEngine(calog, &calogJsEngine); +#endif +#ifdef CALOG_WITH_SQUIRREL + calogRegisterEngine(calog, &calogSquirrelEngine); +#endif +#ifdef CALOG_WITH_MYBASIC + calogRegisterEngine(calog, &calogMyBasicEngine); +#endif + (void)calog; // no-op if the build selected no engines +} #endif diff --git a/src/calogInternal.h b/src/calogInternal.h index e91feae..cbf1dfe 100644 --- a/src/calogInternal.h +++ b/src/calogInternal.h @@ -1,58 +1,100 @@ // calogInternal.h -- internal API shared across calog's own translation units. // -// This is NOT part of the embedding surface (that is calog.h). It exposes the -// registry entry type, the routing/lifecycle hooks the actor layer installs, the -// low-level callable lifecycle the engine adapters drive, and the split -// registry/actor init used internally. Host programs include calog.h only. +// NOT part of the embedding surface (that is calog.h). Host programs include calog.h +// only. #ifndef CALOG_INTERNAL_H #define CALOG_INTERNAL_H #include "calog.h" -// A registry entry, resolved by name. ownerCtxId 0 means thread-agnostic. +#include + +// A registry entry, resolved by name. runInline true means the native runs on the +// calling thread (calogRegisterInline); false means it runs on the host thread. struct CalogEntryT { char *name; CalogNativeFnT fn; void *userData; - uint32_t ownerCtxId; + bool runInline; }; typedef struct CalogEntryT CalogEntryT; +// One active-context registry slot. context is NULL when free; generation is bumped +// on reuse so a stale id (of a closed + recycled context) resolves dead, never +// misrouting to the recycler. +typedef struct CalogRegistrySlotT { + CalogContextT *context; + uint32_t generation; +} CalogRegistrySlotT; + // Invoked when a CalogFnT's last reference drops, so the owning engine can release // its closure. The broker frees the CalogFnT shell after this returns; reach the // closure handle with calogFnUserData. typedef void (*CalogReleaseFnT)(CalogFnT *fn); -// Hooks installed by the actor layer (calogActorInit) so calogCall, calogFnInvoke, -// and the final calogFnRelease marshal to the owning context's thread instead of -// running inline. NULL by default (single-threaded use runs inline). See design.md -// sec 6/10. NB: the registry must be frozen before any context starts -- calogCall -// reads it locklessly from context threads, and calogRegister may grow (and free) -// the slot table. -typedef int32_t (*CalogRouteFnT)(CalogEntryT *entry, CalogValueT *args, int32_t argCount, CalogValueT *result); +// Hooks the actor layer installs on the runtime (calogActorInit stores them in CalogT) +// so calogCall, calogFnInvoke, and the final calogFnRelease marshal to the owning +// thread instead of running inline. NULL on a bare broker (single-threaded use runs +// inline). The route hook is handed its runtime; the callable hooks reach it through +// the CalogFnT (calogFnRuntime). See design.md sec 6/10. +typedef int32_t (*CalogRouteFnT)(CalogT *calog, CalogEntryT *entry, CalogValueT *args, int32_t argCount, CalogValueT *result); typedef int32_t (*CalogInvokeHookT)(CalogFnT *fn, CalogValueT *args, int32_t argCount, CalogValueT *result); typedef void (*CalogReleaseHookT)(CalogFnT *fn); +// The runtime. Owns the native-function registry AND the active-context registry, so +// a host's contexts are its property -- calogDestroy closes every open one. Both +// registries grow dynamically with no preset limit (context ids are 64-bit: a 32-bit +// slot index + 32-bit generation, so neither the live count nor open/close churn is +// capped in practice). All per-runtime actor state (host context, routing hooks, error +// sink) lives here too, so independent CalogT runtimes coexist in one process; each is +// created and pumped by its own host thread. +struct CalogT { + // native-function registry (broker.c) + CalogEntryT *slots; + int64_t slotCount; + int64_t entryCount; + // active-context registry (context.c), guarded by ctxMutex + pthread_mutex_t ctxMutex; + CalogRegistrySlotT *ctxSlots; + int64_t ctxCount; // high-water slot count + int64_t ctxCap; + int64_t *ctxFree; // recycled slot indices + int64_t ctxFreeCount; + int64_t ctxFreeCap; + // actor layer (context.c): installed by calogActorInit, all NULL on a bare broker + CalogContextT *hostContext; // id 0, no thread; driven by calogPump + CalogRouteFnT routeHook; + CalogInvokeHookT invokeHook; + CalogReleaseHookT releaseHook; + CalogErrorFnT errorHandler; + void *errorUserData; + // engines available to calogContextLoad (calogRegisterEngine), search-priority order + const CalogEngineT **engines; + int64_t engineCount; + int64_t engineCap; +}; + // ---- registry (calogCreate/calogDestroy compose these with the actor layer) ---- CalogT *calogBrokerCreate(void); void calogBrokerDestroy(CalogT *calog); CalogEntryT *calogLookup(CalogT *calog, const char *name); -void calogSetRouteHook(CalogRouteFnT hook); +// Visit every registered entry (used by createInterpreter to expose all natives). +// Safe only while the registry is frozen -- i.e. before any context starts. +void calogForEach(CalogT *calog, void (*visit)(const CalogEntryT *entry, void *ud), void *ud); // ---- callable lifecycle (driven by the engine adapters) ---- -int32_t calogFnCreate(CalogFnT **out, CalogNativeFnT fn, void *userData, CalogReleaseFnT release, uint32_t ownerCtxId, uint32_t ownerGen); +int32_t calogFnCreate(CalogFnT **out, CalogT *runtime, CalogNativeFnT fn, void *userData, CalogReleaseFnT release, uint64_t ownerCtxId); void calogFnFinalize(CalogFnT *fn); CalogNativeFnT calogFnNative(const CalogFnT *fn); void calogFnMarkDead(CalogFnT *fn); -uint32_t calogFnOwner(const CalogFnT *fn); +uint64_t calogFnOwner(const CalogFnT *fn); +CalogT *calogFnRuntime(const CalogFnT *fn); void *calogFnUserData(const CalogFnT *fn); -void calogSetInvokeHook(CalogInvokeHookT hook); -void calogSetReleaseHook(CalogReleaseHookT hook); // ---- actor layer (composed into calogCreate/calogDestroy) ---- int32_t calogActorInit(CalogT *calog); -void calogActorShutdown(void); +void calogActorShutdown(CalogT *calog); CalogT *calogContextBroker(const CalogContextT *context); void *calogContextInterp(CalogContextT *context); diff --git a/src/context.c b/src/context.c index 9cd8849..30606ef 100644 --- a/src/context.c +++ b/src/context.c @@ -1,49 +1,63 @@ -// context.c -- actor-model threading: one thread + one MPSC queue per context. +// context.c -- actor threading: one thread + one MPSC queue per context, plus an +// implicit "host context" (id 0) driven by the host thread via calogPump. // -// Everything a context waits on flows through its own queue. A cross-context -// call enqueues a CALL onto the target; the target runs it on its thread and -// sends a REPLY back. A context caller does not sleep while waiting -- it pumps -// its own queue (servicing re-entrant CALLs) until its REPLY arrives, matched by -// a per-call token. Replies that belong to an outer pump are stashed. An -// external (non-context) caller has no queue, so it blocks on a private reply -// box that the target signals. +// Model (design.md sec 6): +// - Scripts run fire-and-forget on their own context threads (calogContextEval +// enqueues and returns). +// - A registered native runs on the HOST thread: a script calling one parks and +// posts a CALL onto the host queue, which calogPump services on the host thread. +// So native C code is serialized on one thread and needs no locking. An inline +// native (calogRegisterInline) instead runs on the calling thread. +// - A script function value (CalogFnT) runs on its owning engine's thread (sec 10). +// - A script error is posted to the host queue and delivered to the error handler +// during calogPump. // -// Ownership across the queue: a CALL deep-copies its args (the caller keeps its -// originals); the target frees those args after the call and MOVES the result -// into the REPLY/box; the caller MOVES the result out. No heap pointer is shared -// between threads at rest. +// All per-runtime actor state (the context registry, the host context, the routing +// hooks, the error sink) lives in CalogT, so independent runtimes coexist in one +// process. The dispatch reaches its runtime through the object it already holds -- the +// serving context's ->broker, a callable's ->runtime, or an explicit calog argument -- +// never a process global. currentContext (a thread-local) names the calling thread's +// context; calogPump sets it to the pumped runtime's host for the drain, so ONE thread +// can host several runtimes by pumping each in turn. Because context ids number from 1 +// per runtime, inline/reply decisions match the runtime too, not the id alone. A value +// or callable is still not shared across runtimes (a cross-runtime reply cannot route). // -// Locking: registryMutex guards the context table and is always taken before any -// per-context queueMutex (the only nested order), so there is no lock cycle. -// Shutdown assumes quiescence (no call in flight) -- see context.h. +// Everything a thread waits on flows through its own queue; a caller that is itself +// a context pumps its own queue (servicing re-entrant calls) rather than sleeping. +// Ownership across the queue: a CALL deep-copies its args; the target frees them and +// MOVES the result back. No heap pointer is shared between threads at rest. #define _POSIX_C_SOURCE 200809L #include "calogInternal.h" #include +#include #include #include #define CONTEXT_INITIAL_CONTEXTS 8 +#define CONTEXT_INITIAL_ENGINES 4 -// A context id packs a 1-based slot index in the low CONTEXT_INDEX_BITS and a -// generation counter above it. id 0 is reserved (a thread-agnostic native / "no -// context"), which is why the index is stored 1-based. When a slot is recycled -// its generation is bumped, so a handle minted against the old occupant no longer -// resolves -- it is rejected as a dead context instead of misrouting to the new -// one. Both fields are 16 bits: up to 65535 live contexts, 65536 reuses per slot -// before a generation wraps (acceptable for v1; noted in design.md sec 9). -#define CONTEXT_INDEX_BITS 16 -#define CONTEXT_INDEX_MASK 0xFFFFu -#define CONTEXT_GEN_MASK 0xFFFFu +// The host context id (script contexts get 1..N). currentContext == calog->hostContext +// means "on this runtime's host thread." +#define CALOG_HOST_ID 0 + +// A script context id packs a 1-based slot index in the low CONTEXT_INDEX_BITS and a +// generation counter above it. When a slot is recycled its generation is bumped, so a +// handle minted against the old occupant no longer resolves (design.md sec 9). The id +// is 64-bit -- a 32-bit slot index + 32-bit generation -- so neither the live-context +// count nor open/close churn hits a preset ceiling in practice. +#define CONTEXT_INDEX_BITS 32 +#define CONTEXT_INDEX_MASK 0xFFFFFFFFu typedef enum MessageKindE { messageCallE = 0, messageReplyE = 1, messageShutdownE = 2, messageEvalE = 3, - messageReleaseE = 4 + messageReleaseE = 4, + messageErrorE = 5 } MessageKindE; typedef struct ReplyBoxT { @@ -51,144 +65,160 @@ typedef struct ReplyBoxT { pthread_cond_t cond; bool done; int32_t status; - CalogValueT result; + CalogValueT result; } ReplyBoxT; typedef struct MessageT { MessageKindE kind; - CalogNativeFnT fn; // messageCallE: the native to run + CalogNativeFnT fn; // messageCallE: the native to run void *userData; // messageCallE: its userData - CalogValueT *args; // messageCallE: deep-copied arguments + CalogValueT *args; // messageCallE: deep-copied arguments int32_t argCount; - char *source; // messageEvalE: deep-copied script source - CalogFnT *callable; // messageReleaseE: the callable to finalize - uint32_t replyToId; + char *source; // messageEvalE: script source; messageErrorE: error text + CalogFnT *callable; // messageReleaseE: the callable to finalize + uint64_t replyToId; // messageErrorE: the failing context id ReplyBoxT *replyBox; uint64_t token; int32_t status; - CalogValueT result; + CalogValueT result; struct MessageT *next; } MessageT; struct CalogContextT { - uint32_t id; - CalogT *broker; - const CalogEngineT *engine; - void *config; - void *interp; - pthread_t thread; - pthread_mutex_t queueMutex; - pthread_cond_t queueCond; - MessageT *head; - MessageT *tail; - MessageT *stash; - uint64_t nextToken; - bool shuttingDown; - bool started; + uint64_t id; + CalogT *broker; + const CalogEngineT *engine; + void *interp; + pthread_t thread; + pthread_mutex_t queueMutex; + pthread_cond_t queueCond; + MessageT *head; + MessageT *tail; + MessageT *stash; + uint64_t nextToken; + bool shuttingDown; + bool started; }; -// One registry slot. context is NULL when the slot is free; generation is the -// generation of the slot's current (or most recent) occupant, bumped on reuse. -typedef struct RegistrySlotT { - CalogContextT *context; - uint32_t generation; -} RegistrySlotT; - -static pthread_mutex_t registryMutex = PTHREAD_MUTEX_INITIALIZER; -static RegistrySlotT *contexts = NULL; // slots [0, contextCount), high-water -static int64_t contextCount = 0; -static int64_t contextCap = 0; -static int64_t *freeList = NULL; // recycled slot indices -static int64_t freeCount = 0; -static int64_t freeCap = 0; +// currentContext is the ONLY process-global actor state -- and it is thread-local: it +// names the context (host or script) the calling thread belongs to. A foreign thread +// (never a calog thread) leaves it NULL and reaches its runtime through a passed +// handle instead. static _Thread_local CalogContextT *currentContext = NULL; static int32_t actorInvokeCallable(CalogFnT *callable, CalogValueT *args, int32_t argCount, CalogValueT *result); static void actorReleaseCallable(CalogFnT *callable); -static int32_t actorRoute(CalogEntryT *entry, CalogValueT *args, int32_t argCount, CalogValueT *result); -static int32_t contextDispatch(uint32_t targetId, CalogNativeFnT fn, void *userData, CalogValueT *args, int32_t argCount, CalogValueT *result); +static int32_t actorRoute(CalogT *calog, CalogEntryT *entry, CalogValueT *args, int32_t argCount, CalogValueT *result); +static int32_t contextDispatch(CalogT *calog, uint64_t targetId, CalogNativeFnT fn, void *userData, CalogValueT *args, int32_t argCount, CalogValueT *result); static void contextDispatchCall(CalogContextT *context, MessageT *message); +static void contextDispatchError(CalogT *calog, MessageT *message); static void contextDispatchEval(CalogContextT *context, MessageT *message); static void contextDispatchRelease(MessageT *message); -static int32_t contextEnqueue(uint32_t targetId, MessageT *message); -static int32_t contextPostRelease(uint32_t targetId, CalogFnT *callable); -static void contextReply(MessageT *message, int32_t status, CalogValueT *result); -static int32_t contextSendBlocking(uint32_t targetId, MessageT *message, CalogValueT *result); +static int32_t contextEnqueue(CalogT *calog, uint64_t targetId, MessageT *message); +static int32_t contextPostRelease(CalogT *calog, uint64_t targetId, CalogFnT *callable); +static void contextReply(CalogT *calog, MessageT *message, int32_t status, CalogValueT *result); +static int32_t contextSendBlocking(CalogT *calog, uint64_t targetId, MessageT *message, CalogValueT *result); static void enqueueRaw(CalogContextT *context, MessageT *message); -static uint32_t idCompose(int64_t index, uint32_t generation); -static uint32_t idGeneration(uint32_t id); -static int64_t idIndex(uint32_t id); +static void hostDispatch(CalogT *calog, MessageT *message); +static uint64_t idCompose(int64_t index, uint32_t generation); +static uint32_t idGeneration(uint64_t id); +static int64_t idIndex(uint64_t id); static MessageT *messageDequeue(CalogContextT *context); static void messageFree(MessageT *message); +static bool onOwnerThread(CalogT *runtime, uint64_t owner); +static void postError(CalogT *calog, uint64_t contextId, const CalogValueT *result); static int32_t pumpUntil(CalogContextT *context, uint64_t token, int32_t *outStatus, CalogValueT *result); -static int32_t registryFreePush(int64_t index); -static CalogContextT *registryResolveLocked(uint32_t id); +static int32_t registryFreePush(CalogT *calog, int64_t index); +static CalogContextT *registryResolveLocked(CalogT *calog, uint64_t id); static void serveLoop(CalogContextT *context); +static MessageT *tryDequeue(CalogContextT *context); static void *threadMain(void *arg); -int32_t calogActorInit(CalogT *broker) { - (void)broker; - calogSetRouteHook(actorRoute); - calogSetInvokeHook(actorInvokeCallable); - calogSetReleaseHook(actorReleaseCallable); +int32_t calogActorInit(CalogT *calog) { + // Build this runtime's host context (id 0, no thread) and claim the calling thread + // as its host. All actor state hangs off CalogT, so multiple runtimes coexist. + calog->hostContext = (CalogContextT *)calloc(1, sizeof(*calog->hostContext)); + if (calog->hostContext == NULL) { + return calogErrOomE; + } + calog->hostContext->id = CALOG_HOST_ID; + calog->hostContext->broker = calog; + pthread_mutex_init(&calog->hostContext->queueMutex, NULL); + pthread_cond_init(&calog->hostContext->queueCond, NULL); + currentContext = calog->hostContext; + + pthread_mutex_init(&calog->ctxMutex, NULL); + calog->routeHook = actorRoute; + calog->invokeHook = actorInvokeCallable; + calog->releaseHook = actorReleaseCallable; return calogOkE; } -static int32_t actorInvokeCallable(CalogFnT *callable, CalogValueT *args, int32_t argCount, CalogValueT *result) { - uint32_t owner; +// True when the calling thread is the owner context's own thread. Ids alone are +// ambiguous across runtimes (each numbers contexts from 1), so the runtime must match +// too; a foreign thread (currentContext NULL) is never the owner. +static bool onOwnerThread(CalogT *runtime, uint64_t owner) { + return currentContext != NULL && currentContext->broker == runtime && currentContext->id == owner; +} - // Inline on the owner's own thread (or a thread-agnostic callable); otherwise - // marshal the invoke to the owner's thread. A callable's fn + userData are - // exactly a native call, so the CALL machinery carries it unchanged. - owner = calogFnOwner(callable); - if (owner == 0 || owner == calogCurrentId()) { + +static int32_t actorInvokeCallable(CalogFnT *callable, CalogValueT *args, int32_t argCount, CalogValueT *result) { + CalogT *runtime; + uint64_t owner; + + // Inline when already on the owner's thread; otherwise marshal to the owner (in the + // callable's own runtime). A callable's fn + userData are exactly a native call, so + // the CALL machinery carries it unchanged. + runtime = calogFnRuntime(callable); + owner = calogFnOwner(callable); + if (onOwnerThread(runtime, owner)) { CalogNativeFnT fn; fn = calogFnNative(callable); return fn(args, argCount, result, calogFnUserData(callable)); } - return contextDispatch(owner, calogFnNative(callable), calogFnUserData(callable), args, argCount, result); + return contextDispatch(runtime, owner, calogFnNative(callable), calogFnUserData(callable), args, argCount, result); } static void actorReleaseCallable(CalogFnT *callable) { - uint32_t owner; + CalogT *runtime; + uint64_t owner; - // Finalize inline when already on the owner's thread or for a thread-agnostic - // callable; otherwise marshal the finalize to the owner's thread so the engine - // release (luaL_unref / sq_release) runs there. - owner = calogFnOwner(callable); - if (owner == 0 || owner == calogCurrentId()) { + // Finalize inline when already on the owner's thread; otherwise marshal the + // finalize to the owner so the engine release (luaL_unref / sq_release) runs there. + runtime = calogFnRuntime(callable); + owner = calogFnOwner(callable); + if (onOwnerThread(runtime, owner)) { calogFnFinalize(callable); return; } - if (contextPostRelease(owner, callable) != calogOkE) { - // Owner unreachable -- only possible if its context was torn down while this - // callable still held a reference, i.e. a quiescence violation (design.md - // sec 9, deferred). Best-effort finalize inline. + if (contextPostRelease(runtime, owner, callable) != calogOkE) { + // Owner unreachable -- a quiescence violation (design.md sec 9, deferred). + // Best-effort finalize inline. calogFnFinalize(callable); } } -static int32_t actorRoute(CalogEntryT *entry, CalogValueT *args, int32_t argCount, CalogValueT *result) { - // Inline for a thread-agnostic native (owner 0) or a same-context call; - // otherwise marshal the call to the owning context's thread. - if (entry->ownerCtxId == 0 || entry->ownerCtxId == calogCurrentId()) { +static int32_t actorRoute(CalogT *calog, CalogEntryT *entry, CalogValueT *args, int32_t argCount, CalogValueT *result) { + // An inline native, or any call already on this runtime's host thread, runs inline; + // otherwise the native is marshalled to the host thread (serviced by calogPump). + if (entry->runInline || currentContext == calog->hostContext) { return entry->fn(args, argCount, result, entry->userData); } - return contextDispatch(entry->ownerCtxId, entry->fn, entry->userData, args, argCount, result); + return contextDispatch(calog, CALOG_HOST_ID, entry->fn, entry->userData, args, argCount, result); } -void calogActorShutdown(void) { - int64_t index; +void calogActorShutdown(CalogT *calog) { + int64_t index; + MessageT *message; - for (index = 0; index < contextCount; index++) { + for (index = 0; index < calog->ctxCount; index++) { CalogContextT *context; - MessageT *message; - context = contexts[index].context; + context = calog->ctxSlots[index].context; if (context == NULL || !context->started) { continue; } @@ -198,9 +228,9 @@ void calogActorShutdown(void) { enqueueRaw(context, message); } } - for (index = 0; index < contextCount; index++) { + for (index = 0; index < calog->ctxCount; index++) { CalogContextT *context; - context = contexts[index].context; + context = calog->ctxSlots[index].context; if (context == NULL) { continue; } @@ -210,43 +240,39 @@ void calogActorShutdown(void) { pthread_mutex_destroy(&context->queueMutex); pthread_cond_destroy(&context->queueCond); free(context); - contexts[index].context = NULL; + calog->ctxSlots[index].context = NULL; } - free(contexts); - free(freeList); - contexts = NULL; - freeList = NULL; - contextCount = 0; - contextCap = 0; - freeCount = 0; - freeCap = 0; - calogSetRouteHook(NULL); - calogSetInvokeHook(NULL); - calogSetReleaseHook(NULL); -} + free(calog->ctxSlots); + free(calog->ctxFree); + calog->ctxSlots = NULL; + calog->ctxFree = NULL; + calog->ctxCount = 0; + calog->ctxCap = 0; + calog->ctxFreeCount = 0; + calog->ctxFreeCap = 0; + pthread_mutex_destroy(&calog->ctxMutex); - -// The public runtime lifecycle: calogCreate composes the registry (calogBrokerCreate) -// with the actor layer (calogActorInit, which installs the routing hooks); calogDestroy -// tears the actor layer down (joining context threads) before freeing the registry. -CalogT *calogCreate(void) { - CalogT *calog; - - calog = calogBrokerCreate(); - if (calog == NULL) { - return NULL; + // Drain and free the host context. Only clear the calling thread's currentContext + // if it named THIS runtime's host -- the thread may still host other runtimes. + if (calog->hostContext != NULL) { + bool wasHost; + wasHost = (currentContext == calog->hostContext); + while ((message = tryDequeue(calog->hostContext)) != NULL) { + messageFree(message); + } + pthread_mutex_destroy(&calog->hostContext->queueMutex); + pthread_cond_destroy(&calog->hostContext->queueCond); + free(calog->hostContext); + calog->hostContext = NULL; + if (wasHost) { + currentContext = NULL; + } } - calogActorInit(calog); - return calog; -} - - -void calogDestroy(CalogT *calog) { - if (calog == NULL) { - return; - } - calogActorShutdown(); - calogBrokerDestroy(calog); + calog->routeHook = NULL; + calog->invokeHook = NULL; + calog->releaseHook = NULL; + calog->errorHandler = NULL; + calog->errorUserData = NULL; } @@ -255,65 +281,167 @@ CalogT *calogContextBroker(const CalogContextT *context) { } -int32_t calogContextCreate(CalogT *broker, const CalogEngineT *engine, void *config, CalogContextT **out) { - CalogContextT *context; - int64_t index; - uint32_t generation; +// The public runtime lifecycle: calogCreate composes the registry with the actor +// layer (which builds the host context and installs the routing hooks); calogDestroy +// tears the actor layer down (joining context threads) before freeing the registry. +CalogT *calogCreate(void) { + CalogT *calog; + + calog = calogBrokerCreate(); + if (calog == NULL) { + return NULL; + } + if (calogActorInit(calog) != calogOkE) { + calogBrokerDestroy(calog); + return NULL; + } + return calog; +} + + +void calogDestroy(CalogT *calog) { + if (calog == NULL) { + return; + } + calogActorShutdown(calog); + calogBrokerDestroy(calog); +} + + +// Create + start a context in one step (design: no owned-native registration happens +// between the two, so there is nothing to do in between). Returns NULL on failure. +CalogContextT *calogContextOpen(CalogT *broker, const CalogEngineT *engine) { + CalogContextT *context; + int64_t index; + uint32_t generation; - *out = NULL; context = (CalogContextT *)calloc(1, sizeof(*context)); if (context == NULL) { - return calogErrOomE; + return NULL; } context->broker = broker; context->engine = engine; - context->config = config; pthread_mutex_init(&context->queueMutex, NULL); pthread_cond_init(&context->queueCond, NULL); - pthread_mutex_lock(®istryMutex); - if (freeCount > 0) { - // Recycle a freed slot, bumping its generation so any handle that still - // names the previous occupant resolves dead. - freeCount--; - index = freeList[freeCount]; - generation = (contexts[index].generation + 1) & CONTEXT_GEN_MASK; + pthread_mutex_lock(&broker->ctxMutex); + if (broker->ctxFreeCount > 0) { + broker->ctxFreeCount--; + index = broker->ctxFree[broker->ctxFreeCount]; + generation = broker->ctxSlots[index].generation + 1u; } else { - if (contextCount + 1 > (int64_t)CONTEXT_INDEX_MASK) { - // A new index would not fit the id's index field; refuse rather than - // mint a colliding id. Only reachable past 65534 live contexts. - pthread_mutex_unlock(®istryMutex); + if (broker->ctxCount + 1 > (int64_t)CONTEXT_INDEX_MASK) { + pthread_mutex_unlock(&broker->ctxMutex); pthread_mutex_destroy(&context->queueMutex); pthread_cond_destroy(&context->queueCond); free(context); - return calogErrRangeE; + return NULL; } - if (contextCount == contextCap) { - int64_t newCap; - RegistrySlotT *grown; - newCap = (contextCap == 0) ? CONTEXT_INITIAL_CONTEXTS : contextCap * CALOG_GROWTH_FACTOR; - grown = (RegistrySlotT *)realloc(contexts, (size_t)newCap * sizeof(RegistrySlotT)); + if (broker->ctxCount == broker->ctxCap) { + int64_t newCap; + CalogRegistrySlotT *grown; + newCap = (broker->ctxCap == 0) ? CONTEXT_INITIAL_CONTEXTS : broker->ctxCap * CALOG_GROWTH_FACTOR; + grown = (CalogRegistrySlotT *)realloc(broker->ctxSlots, (size_t)newCap * sizeof(CalogRegistrySlotT)); if (grown == NULL) { - pthread_mutex_unlock(®istryMutex); + pthread_mutex_unlock(&broker->ctxMutex); pthread_mutex_destroy(&context->queueMutex); pthread_cond_destroy(&context->queueCond); free(context); - return calogErrOomE; + return NULL; } - contexts = grown; - contextCap = newCap; + broker->ctxSlots = grown; + broker->ctxCap = newCap; } - index = contextCount; + index = broker->ctxCount; generation = 0; - contextCount++; + broker->ctxCount++; } - context->id = idCompose(index, generation); - contexts[index].context = context; - contexts[index].generation = generation; - pthread_mutex_unlock(®istryMutex); + context->id = idCompose(index, generation); + broker->ctxSlots[index].context = context; + broker->ctxSlots[index].generation = generation; + pthread_mutex_unlock(&broker->ctxMutex); - *out = context; - return calogOkE; + if (pthread_create(&context->thread, NULL, threadMain, context) != 0) { + pthread_mutex_lock(&broker->ctxMutex); + broker->ctxSlots[index].context = NULL; + registryFreePush(broker, index); + pthread_mutex_unlock(&broker->ctxMutex); + pthread_mutex_destroy(&context->queueMutex); + pthread_cond_destroy(&context->queueCond); + free(context); + return NULL; + } + context->started = true; + return context; +} + + +// Search the registered engines for a file ".". The first engine +// (in registration order), then its first extension, that names a readable file wins: +// its contents are loaded fire-and-forget into a fresh context on that engine. Reads +// the file on the calling thread. Returns NULL if nothing matched or the load failed. +CalogContextT *calogContextLoad(CalogT *calog, const char *baseFileName) { + int64_t engineIndex; + + for (engineIndex = 0; engineIndex < calog->engineCount; engineIndex++) { + const CalogEngineT *engine; + int32_t extIndex; + + engine = calog->engines[engineIndex]; + if (engine->extensions == NULL) { + continue; + } + for (extIndex = 0; engine->extensions[extIndex] != NULL; extIndex++) { + CalogContextT *context; + const char *ext; + char *path; + char *source; + FILE *file; + size_t pathSize; + long fileSize; + size_t readCount; + + ext = engine->extensions[extIndex]; + pathSize = strlen(baseFileName) + strlen(ext) + 2; // '.' + '\0' + path = (char *)malloc(pathSize); + if (path == NULL) { + return NULL; + } + snprintf(path, pathSize, "%s.%s", baseFileName, ext); + file = fopen(path, "rb"); + free(path); + if (file == NULL) { + continue; // not this extension -- try the next + } + // First existing file wins. Read it whole, open a context, load it. + if (fseek(file, 0, SEEK_END) != 0 || (fileSize = ftell(file)) < 0) { + fclose(file); + return NULL; + } + rewind(file); + source = (char *)malloc((size_t)fileSize + 1); + if (source == NULL) { + fclose(file); + return NULL; + } + readCount = fread(source, 1, (size_t)fileSize, file); + fclose(file); + source[readCount] = '\0'; + context = calogContextOpen(calog, engine); + if (context == NULL) { + free(source); + return NULL; + } + if (calogContextEval(context, source) != calogOkE) { + free(source); + calogContextClose(context); + return NULL; + } + free(source); + return context; + } + } + return NULL; } @@ -322,22 +450,22 @@ CalogT *calogCurrent(void) { } -uint32_t calogCurrentId(void) { - return currentContext != NULL ? currentContext->id : 0; +uint64_t calogCurrentId(void) { + return currentContext != NULL ? currentContext->id : CALOG_HOST_ID; } -// Tear down a single context (quiescence assumed -- no call targeting it is in -// flight). The thread is stopped and joined first, then the slot is unlinked -// under the registry lock so no foreign enqueue can reach the queue mutex after -// it is destroyed. The freed index returns to the freelist with its generation -// preserved; the next reuse bumps it. -void calogContextDestroy(CalogContextT *context) { +// Tear down a single context (quiescence assumed). The thread is stopped and joined, +// then the slot is unlinked under the registry lock so no foreign enqueue can reach +// the freed queue mutex; the freed index returns to the freelist. +void calogContextClose(CalogContextT *context) { + CalogT *broker; int64_t index; if (context == NULL) { return; } + broker = context->broker; if (context->started) { MessageT *message; message = (MessageT *)calloc(1, sizeof(*message)); @@ -347,20 +475,20 @@ void calogContextDestroy(CalogContextT *context) { } pthread_join(context->thread, NULL); } - pthread_mutex_lock(®istryMutex); + pthread_mutex_lock(&broker->ctxMutex); index = idIndex(context->id); - if (index >= 0 && index < contextCount && contexts[index].context == context) { - contexts[index].context = NULL; - registryFreePush(index); + if (index >= 0 && index < broker->ctxCount && broker->ctxSlots[index].context == context) { + broker->ctxSlots[index].context = NULL; + registryFreePush(broker, index); } - pthread_mutex_unlock(®istryMutex); + pthread_mutex_unlock(&broker->ctxMutex); pthread_mutex_destroy(&context->queueMutex); pthread_cond_destroy(&context->queueCond); free(context); } -static int32_t contextDispatch(uint32_t targetId, CalogNativeFnT fn, void *userData, CalogValueT *args, int32_t argCount, CalogValueT *result) { +static int32_t contextDispatch(CalogT *calog, uint64_t targetId, CalogNativeFnT fn, void *userData, CalogValueT *args, int32_t argCount, CalogValueT *result) { MessageT *call; int32_t status; int32_t index; @@ -393,16 +521,15 @@ static int32_t contextDispatch(uint32_t targetId, CalogNativeFnT fn, void *userD } } } - return contextSendBlocking(targetId, call, result); + return contextSendBlocking(calog, targetId, call, result); } static void contextDispatchCall(CalogContextT *context, MessageT *message) { - CalogValueT result; - int32_t status; - int32_t index; + CalogValueT result; + int32_t status; + int32_t index; - (void)context; calogValueNil(&result); status = message->fn(message->args, message->argCount, &result, message->userData); @@ -413,56 +540,72 @@ static void contextDispatchCall(CalogContextT *context, MessageT *message) { free(message->args); message->args = NULL; } - contextReply(message, status, &result); + contextReply(context->broker, message, status, &result); +} + + +static void contextDispatchError(CalogT *calog, MessageT *message) { + // Runs on the host thread (calogPump / a nested host pump): deliver a fire-and- + // forget script error to the handler, or log it if none is set. + if (calog->errorHandler != NULL) { + calog->errorHandler(message->replyToId, message->source != NULL ? message->source : "", calog->errorUserData); + } else { + fprintf(stderr, "calog: context %llu script error: %s\n", (unsigned long long)message->replyToId, message->source != NULL ? message->source : ""); + } + free(message->source); + free(message); } static void contextDispatchEval(CalogContextT *context, MessageT *message) { - CalogValueT result; - int32_t status; + CalogValueT result; + int32_t status; + // Fire-and-forget: run the script, free the message, and on failure post the + // error to the host thread. No reply. calogValueNil(&result); if (context->engine == NULL || context->engine->runSource == NULL) { status = calogFail(&result, calogErrUnsupportedE, "context has no runnable engine"); } else if (context->interp == NULL) { - // createInterpreter failed on this context's thread (threadMain ignores its - // status by design): the context still serves native calls, but cannot run - // scripts. Reject the eval cleanly instead of dereferencing a NULL interp. + // createInterpreter failed (threadMain ignores its status); reject cleanly. status = calogFail(&result, calogErrUnsupportedE, "engine interpreter unavailable"); } else { status = context->engine->runSource(context->interp, message->source, &result); } free(message->source); - message->source = NULL; - contextReply(message, status, &result); + free(message); + if (status != calogOkE) { + postError(context->broker, context->id, &result); + } + calogValueFree(&result); } static void contextDispatchRelease(MessageT *message) { - // Runs on the callable's owner thread: perform the engine's closure release - // (luaL_unref / sq_release via calogFnFinalize), then free the message shell. + // Runs on the callable's owner thread: perform the engine's closure release via + // calogFnFinalize, then free the message shell. calogFnFinalize(message->callable); free(message); } -static int32_t contextEnqueue(uint32_t targetId, MessageT *message) { +static int32_t contextEnqueue(CalogT *calog, uint64_t targetId, MessageT *message) { CalogContextT *target; int64_t index; bool inRange; - pthread_mutex_lock(®istryMutex); - target = registryResolveLocked(targetId); + pthread_mutex_lock(&calog->ctxMutex); + target = registryResolveLocked(calog, targetId); if (target != NULL) { enqueueRaw(target, message); - pthread_mutex_unlock(®istryMutex); + pthread_mutex_unlock(&calog->ctxMutex); return calogOkE; } // Resolve failed: an in-range index means the slot existed and has since been // freed or recycled (a dead context); out of range means the id named nothing. index = idIndex(targetId); - inRange = index >= 0 && index < contextCount; - pthread_mutex_unlock(®istryMutex); + inRange = index >= 0 && index < calog->ctxCount; + pthread_mutex_unlock(&calog->ctxMutex); if (inRange) { return calogErrDeadE; } @@ -470,10 +613,9 @@ static int32_t contextEnqueue(uint32_t targetId, MessageT *message) { } -// Fire-and-forget: post a callable finalize to its owner thread (no reply). On -// enqueue failure the message is freed and the caller (actorReleaseCallable) falls -// back to finalizing the dead callable itself. -static int32_t contextPostRelease(uint32_t targetId, CalogFnT *callable) { +// Fire-and-forget: post a callable finalize to its owner thread. On enqueue failure +// the message is freed and actorReleaseCallable finalizes the dead callable itself. +static int32_t contextPostRelease(CalogT *calog, uint64_t targetId, CalogFnT *callable) { MessageT *message; int32_t status; @@ -483,7 +625,7 @@ static int32_t contextPostRelease(uint32_t targetId, CalogFnT *callable) { } message->kind = messageReleaseE; message->callable = callable; - status = contextEnqueue(targetId, message); + status = contextEnqueue(calog, targetId, message); if (status != calogOkE) { free(message); } @@ -491,27 +633,33 @@ static int32_t contextPostRelease(uint32_t targetId, CalogFnT *callable) { } -int32_t calogContextEval(CalogContextT *context, const char *source, CalogValueT *result) { +int32_t calogContextEval(CalogContextT *context, const char *source) { MessageT *eval; char *sourceCopy; + int32_t status; - calogValueNil(result); sourceCopy = strdup(source); if (sourceCopy == NULL) { - return calogFail(result, calogErrOomE, "out of memory copying source"); + return calogErrOomE; } eval = (MessageT *)calloc(1, sizeof(*eval)); if (eval == NULL) { free(sourceCopy); - return calogFail(result, calogErrOomE, "out of memory creating eval message"); + return calogErrOomE; } eval->kind = messageEvalE; eval->source = sourceCopy; - return contextSendBlocking(context->id, eval, result); + // Fire-and-forget: enqueue onto the context's thread and return. The script runs + // asynchronously; errors surface via the error handler. + status = contextEnqueue(context->broker, context->id, eval); + if (status != calogOkE) { + messageFree(eval); + } + return status; } -uint32_t calogContextId(const CalogContextT *context) { +uint64_t calogContextId(const CalogContextT *context) { return context->id; } @@ -521,10 +669,55 @@ void *calogContextInterp(CalogContextT *context) { } -// The reply tail shared by every on-thread dispatch (CALL, EVAL): hand (status, -// result) back to whoever is blocked on this message -- an external caller's reply -// box, or a context caller via a REPLY enqueued onto its queue. Consumes message. -static void contextReply(MessageT *message, int32_t status, CalogValueT *result) { +void calogPump(CalogT *calog) { + CalogContextT *previous; + MessageT *message; + + if (calog->hostContext == NULL) { + return; + } + // Present the calling thread as THIS runtime's host for the drain, so a native -- + // and anything it calls -- resolves to this runtime. Restoring afterward lets one + // thread pump several runtimes in turn (each drain acts as the right host). + previous = currentContext; + currentContext = calog->hostContext; + // Non-blocking: run every pending host-thread message and return. + while ((message = tryDequeue(calog->hostContext)) != NULL) { + hostDispatch(calog, message); + } + currentContext = previous; +} + + +// Append an engine to the runtime's calogContextLoad search list (setup-time, host +// thread). On OOM the engine is simply not registered -- load just won't find it. +void calogRegisterEngine(CalogT *calog, const CalogEngineT *engine) { + if (calog->engineCount == calog->engineCap) { + int64_t newCap; + const CalogEngineT **grown; + newCap = (calog->engineCap == 0) ? CONTEXT_INITIAL_ENGINES : calog->engineCap * CALOG_GROWTH_FACTOR; + grown = (const CalogEngineT **)realloc(calog->engines, (size_t)newCap * sizeof(*grown)); + if (grown == NULL) { + return; + } + calog->engines = grown; + calog->engineCap = newCap; + } + calog->engines[calog->engineCount] = engine; + calog->engineCount++; +} + + +void calogSetErrorHandler(CalogT *calog, CalogErrorFnT fn, void *userData) { + calog->errorHandler = fn; + calog->errorUserData = userData; +} + + +// The reply tail shared by CALL dispatch: hand (status, result) back to whoever is +// blocked on this message -- an external caller's reply box, or a context caller via +// a REPLY enqueued onto its queue (in the serving context's runtime). Consumes message. +static void contextReply(CalogT *calog, MessageT *message, int32_t status, CalogValueT *result) { if (message->replyBox != NULL) { ReplyBoxT *box; box = message->replyBox; @@ -536,39 +729,39 @@ static void contextReply(MessageT *message, int32_t status, CalogValueT *result) pthread_mutex_unlock(&box->mutex); free(message); } else { - // Reuse the request message as the REPLY: it is already heap-allocated and - // still carries the caller's token and id, so the wakeup cannot be lost to - // an allocation failure here -- and it saves an allocation per cross-context - // call. The dispatcher already released the request's args/source. - uint32_t replyToId; + // Reuse the request message as the REPLY: it already carries the caller's + // token and id, so the wakeup cannot be lost to an allocation failure here. + uint64_t replyToId; replyToId = message->replyToId; message->kind = messageReplyE; message->status = status; calogValueMove(&message->result, result); - if (contextEnqueue(replyToId, message) != calogOkE) { + if (contextEnqueue(calog, replyToId, message) != calogOkE) { messageFree(message); } } } -// Enqueue an already-built blocking message (CALL or EVAL) to targetId and wait -// for its reply. A context caller pumps its own queue (servicing re-entrant work) -// instead of sleeping; an external caller blocks on a private reply box. The -// message is consumed either way (freed on the reply path, or by messageFree if -// the enqueue fails). -static int32_t contextSendBlocking(uint32_t targetId, MessageT *message, CalogValueT *result) { +// Enqueue an already-built CALL to targetId and wait for its reply. A context caller +// pumps its own queue (servicing re-entrant work) instead of sleeping; a foreign +// caller blocks on a private reply box. The message is consumed either way. +static int32_t contextSendBlocking(CalogT *calog, uint64_t targetId, MessageT *message, CalogValueT *result) { CalogContextT *caller; int32_t status; - caller = currentContext; + // The token/pump path needs the caller's queue in THIS runtime (its reply routes + // by the caller's id, resolved in calog). A thread that is foreign to calog -- a + // non-calog thread, or one currently hosting a different runtime -- takes the reply + // box instead, so a cross-runtime call cannot misroute its reply. + caller = (currentContext != NULL && currentContext->broker == calog) ? currentContext : NULL; if (caller != NULL) { uint64_t token; int32_t replyStatus; token = ++caller->nextToken; message->replyToId = caller->id; message->token = token; - status = contextEnqueue(targetId, message); + status = contextEnqueue(calog, targetId, message); if (status != calogOkE) { messageFree(message); return calogFail(result, status, "target context unavailable"); @@ -586,7 +779,7 @@ static int32_t contextSendBlocking(uint32_t targetId, MessageT *message, CalogVa box.status = calogOkE; calogValueNil(&box.result); message->replyBox = &box; - status = contextEnqueue(targetId, message); + status = contextEnqueue(calog, targetId, message); if (status != calogOkE) { messageFree(message); pthread_mutex_destroy(&box.mutex); @@ -606,18 +799,6 @@ static int32_t contextSendBlocking(uint32_t targetId, MessageT *message, CalogVa } -int32_t calogContextStart(CalogContextT *context) { - if (context->started) { - return calogErrArgE; - } - if (pthread_create(&context->thread, NULL, threadMain, context) != 0) { - return calogErrOomE; - } - context->started = true; - return calogOkE; -} - - static void enqueueRaw(CalogContextT *context, MessageT *message) { pthread_mutex_lock(&context->queueMutex); message->next = NULL; @@ -632,18 +813,36 @@ static void enqueueRaw(CalogContextT *context, MessageT *message) { } -static uint32_t idCompose(int64_t index, uint32_t generation) { - return ((generation & CONTEXT_GEN_MASK) << CONTEXT_INDEX_BITS) | (uint32_t)(index + 1); +static void hostDispatch(CalogT *calog, MessageT *message) { + switch (message->kind) { + case messageCallE: + contextDispatchCall(calog->hostContext, message); + break; + case messageReleaseE: + contextDispatchRelease(message); + break; + case messageErrorE: + contextDispatchError(calog, message); + break; + default: + messageFree(message); + break; + } } -static uint32_t idGeneration(uint32_t id) { - return (id >> CONTEXT_INDEX_BITS) & CONTEXT_GEN_MASK; +static uint64_t idCompose(int64_t index, uint32_t generation) { + return ((uint64_t)generation << CONTEXT_INDEX_BITS) | (uint64_t)(index + 1); } -static int64_t idIndex(uint32_t id) { - uint32_t low; +static uint32_t idGeneration(uint64_t id) { + return (uint32_t)(id >> CONTEXT_INDEX_BITS); +} + + +static int64_t idIndex(uint64_t id) { + uint64_t low; low = id & CONTEXT_INDEX_MASK; if (low == 0) { @@ -687,7 +886,7 @@ static void messageFree(MessageT *message) { } free(message->args); } - if (message->kind == messageEvalE) { + if (message->kind == messageEvalE || message->kind == messageErrorE) { free(message->source); } if (message->kind == messageReplyE) { @@ -697,15 +896,37 @@ static void messageFree(MessageT *message) { } +// Post a fire-and-forget script error to the host thread's error handler. +static void postError(CalogT *calog, uint64_t contextId, const CalogValueT *result) { + MessageT *message; + const char *text; + + text = (result->type == calogStringE) ? result->as.s.bytes : "script error"; + message = (MessageT *)calloc(1, sizeof(*message)); + if (message == NULL) { + return; + } + message->kind = messageErrorE; + message->replyToId = contextId; + message->source = strdup(text); + if (message->source == NULL) { + free(message); + return; + } + if (contextEnqueue(calog, CALOG_HOST_ID, message) != calogOkE) { + free(message->source); + free(message); + } +} + + static int32_t pumpUntil(CalogContextT *context, uint64_t token, int32_t *outStatus, CalogValueT *result) { for (;;) { MessageT *message; MessageT *prev; MessageT *node; - // Re-check the stash every iteration: a nested pump may have stashed this - // token's reply while servicing a re-entrant call, so it can appear here - // without ever passing through messageDequeue again. + // Re-check the stash: a nested pump may have stashed this token's reply. prev = NULL; node = context->stash; while (node != NULL) { @@ -752,47 +973,53 @@ static int32_t pumpUntil(CalogContextT *context, uint64_t token, int32_t *outSta contextDispatchRelease(message); continue; } + if (message->kind == messageErrorE) { + contextDispatchError(context->broker, message); + continue; + } context->shuttingDown = true; free(message); } } -// Push a freed slot index onto the recycle freelist. Called under registryMutex. -// On OOM the index is simply not recycled (the slot stays unused) -- a leaked -// slot, never a correctness fault -- so the caller need not unwind. -static int32_t registryFreePush(int64_t index) { - if (freeCount == freeCap) { +// Push a freed slot index onto the recycle freelist. Called under calog->ctxMutex. On +// OOM the index is simply not recycled (a leaked slot, never a correctness fault). +static int32_t registryFreePush(CalogT *calog, int64_t index) { + if (calog->ctxFreeCount == calog->ctxFreeCap) { int64_t newCap; int64_t *grown; - newCap = (freeCap == 0) ? CONTEXT_INITIAL_CONTEXTS : freeCap * CALOG_GROWTH_FACTOR; - grown = (int64_t *)realloc(freeList, (size_t)newCap * sizeof(int64_t)); + newCap = (calog->ctxFreeCap == 0) ? CONTEXT_INITIAL_CONTEXTS : calog->ctxFreeCap * CALOG_GROWTH_FACTOR; + grown = (int64_t *)realloc(calog->ctxFree, (size_t)newCap * sizeof(int64_t)); if (grown == NULL) { return calogErrOomE; } - freeList = grown; - freeCap = newCap; + calog->ctxFree = grown; + calog->ctxFreeCap = newCap; } - freeList[freeCount] = index; - freeCount++; + calog->ctxFree[calog->ctxFreeCount] = index; + calog->ctxFreeCount++; return calogOkE; } -static CalogContextT *registryResolveLocked(uint32_t id) { +static CalogContextT *registryResolveLocked(CalogT *calog, uint64_t id) { int64_t index; + if (id == CALOG_HOST_ID) { + return calog->hostContext; + } index = idIndex(id); - if (index < 0 || index >= contextCount) { + if (index < 0 || index >= calog->ctxCount) { return NULL; } - if (contexts[index].context == NULL) { + if (calog->ctxSlots[index].context == NULL) { return NULL; } - if (contexts[index].generation != idGeneration(id)) { + if (calog->ctxSlots[index].generation != idGeneration(id)) { return NULL; } - return contexts[index].context; + return calog->ctxSlots[index].context; } @@ -825,13 +1052,32 @@ static void serveLoop(CalogContextT *context) { } +static MessageT *tryDequeue(CalogContextT *context) { + MessageT *message; + + pthread_mutex_lock(&context->queueMutex); + message = context->head; + if (message != NULL) { + context->head = message->next; + if (context->head == NULL) { + context->tail = NULL; + } + } + pthread_mutex_unlock(&context->queueMutex); + if (message != NULL) { + message->next = NULL; + } + return message; +} + + static void *threadMain(void *arg) { CalogContextT *context; context = (CalogContextT *)arg; currentContext = context; if (context->engine != NULL && context->engine->createInterpreter != NULL) { - context->engine->createInterpreter(context, context->config, &context->interp); + context->engine->createInterpreter(context, &context->interp); } serveLoop(context); if (context->engine != NULL && context->engine->destroyInterpreter != NULL) { diff --git a/src/js/jsAdapter.c b/src/js/jsAdapter.c index 29d6d83..9811bc2 100644 --- a/src/js/jsAdapter.c +++ b/src/js/jsAdapter.c @@ -54,7 +54,7 @@ typedef struct JsExportT { struct CalogJsT { duk_context *ctx; CalogT *broker; - uint32_t ctxId; + uint64_t ctxId; BindingT **bindings; int32_t bindingCount; int32_t bindingCap; @@ -118,7 +118,7 @@ static void jsCallableRelease(CalogFnT *callable) { } -int32_t calogJsCreate(CalogJsT **out, CalogT *broker, uint32_t ctxId) { +int32_t calogJsCreate(CalogJsT **out, CalogT *broker, uint64_t ctxId) { CalogJsT *context; duk_context *ctx; @@ -231,7 +231,7 @@ static int32_t jsExportAt(CalogJsT *context, duk_idx_t idx, CalogFnT **out) { export->context = context; export->heapPtr = heapPtr; export->ref = ref; - status = calogFnCreate(out, jsCallableInvoke, export, jsCallableRelease, context->ctxId, 0); + status = calogFnCreate(out, context->broker, jsCallableInvoke, export, jsCallableRelease, context->ctxId); if (status != calogOkE) { duk_push_global_stash(ctx); duk_get_prop_string(ctx, -1, JS_EXPORTS_KEY); diff --git a/src/js/jsAdapter.h b/src/js/jsAdapter.h index 52af055..8f7a7c9 100644 --- a/src/js/jsAdapter.h +++ b/src/js/jsAdapter.h @@ -17,7 +17,7 @@ typedef struct CalogJsT CalogJsT; -int32_t calogJsCreate(CalogJsT **out, CalogT *broker, uint32_t ctxId); +int32_t calogJsCreate(CalogJsT **out, CalogT *broker, uint64_t ctxId); void calogJsDestroy(CalogJsT *context); int32_t calogJsExport(CalogJsT *context, const char *globalName, CalogFnT **out); int32_t calogJsExpose(CalogJsT *context, const char *name); diff --git a/src/js/jsEngine.c b/src/js/jsEngine.c index 4ee2c72..ecb7599 100644 --- a/src/js/jsEngine.c +++ b/src/js/jsEngine.c @@ -8,44 +8,42 @@ #include "calogInternal.h" #include "jsAdapter.h" -static int32_t jsEngineCreate(CalogContextT *context, void *config, void **interpOut); +static int32_t jsEngineCreate(CalogContextT *context, void **interpOut); static void jsEngineDestroy(void *interp); static int32_t jsEngineRun(void *interp, const char *source, CalogValueT *result); +static void jsExposeVisitor(const CalogEntryT *entry, void *ud); + +static const char *const jsExtensions[] = { "js", NULL }; const CalogEngineT calogJsEngine = { "javascript", + jsExtensions, jsEngineCreate, jsEngineDestroy, jsEngineRun }; -static int32_t jsEngineCreate(CalogContextT *context, void *config, void **interpOut) { +static int32_t jsEngineCreate(CalogContextT *context, void **interpOut) { CalogJsT *jc; - int32_t status; + int32_t status; *interpOut = NULL; status = calogJsCreate(&jc, calogContextBroker(context), calogContextId(context)); if (status != calogOkE) { return status; } - if (config != NULL) { - CalogConfigT *cfg; - int32_t index; - cfg = (CalogConfigT *)config; - for (index = 0; index < cfg->exposeCount; index++) { - status = calogJsExpose(jc, cfg->exposeNames[index]); - if (status != calogOkE) { - calogJsDestroy(jc); - return status; - } - } - } + calogForEach(calogContextBroker(context), jsExposeVisitor, jc); *interpOut = jc; return calogOkE; } +static void jsExposeVisitor(const CalogEntryT *entry, void *ud) { + calogJsExpose((CalogJsT *)ud, entry->name); +} + + static void jsEngineDestroy(void *interp) { calogJsDestroy((CalogJsT *)interp); } diff --git a/src/lua/luaAdapter.c b/src/lua/luaAdapter.c index cf3d2c3..6c214d9 100644 --- a/src/lua/luaAdapter.c +++ b/src/lua/luaAdapter.c @@ -40,7 +40,7 @@ typedef struct LuaExportT { struct CalogLuaT { lua_State *L; CalogT *broker; - uint32_t ctxId; + uint64_t ctxId; BindingT **bindings; int32_t bindingCount; int32_t bindingCap; @@ -103,7 +103,7 @@ static void luaCallableRelease(CalogFnT *callable) { } -int32_t calogLuaCreate(CalogLuaT **out, CalogT *broker, uint32_t ctxId) { +int32_t calogLuaCreate(CalogLuaT **out, CalogT *broker, uint64_t ctxId) { CalogLuaT *context; lua_State *L; @@ -191,7 +191,7 @@ static int32_t luaExportAt(CalogLuaT *context, int idx, CalogFnT **out) { ref = luaL_ref(L, LUA_REGISTRYINDEX); export->context = context; export->ref = ref; - status = calogFnCreate(out, luaCallableInvoke, export, luaCallableRelease, context->ctxId, 0); + status = calogFnCreate(out, context->broker, luaCallableInvoke, export, luaCallableRelease, context->ctxId); if (status != calogOkE) { luaL_unref(L, LUA_REGISTRYINDEX, ref); free(export); diff --git a/src/lua/luaAdapter.h b/src/lua/luaAdapter.h index ca1feaf..3885986 100644 --- a/src/lua/luaAdapter.h +++ b/src/lua/luaAdapter.h @@ -16,7 +16,7 @@ typedef struct CalogLuaT CalogLuaT; -int32_t calogLuaCreate(CalogLuaT **out, CalogT *broker, uint32_t ctxId); +int32_t calogLuaCreate(CalogLuaT **out, CalogT *broker, uint64_t ctxId); void calogLuaDestroy(CalogLuaT *context); int32_t calogLuaExport(CalogLuaT *context, const char *globalName, CalogFnT **out); int32_t calogLuaExpose(CalogLuaT *context, const char *name); diff --git a/src/lua/luaEngine.c b/src/lua/luaEngine.c index 5b78c2b..dd3dd48 100644 --- a/src/lua/luaEngine.c +++ b/src/lua/luaEngine.c @@ -9,44 +9,42 @@ #include "calogInternal.h" #include "luaAdapter.h" -static int32_t luaEngineCreate(CalogContextT *context, void *config, void **interpOut); +static int32_t luaEngineCreate(CalogContextT *context, void **interpOut); static void luaEngineDestroy(void *interp); static int32_t luaEngineRun(void *interp, const char *source, CalogValueT *result); +static void luaExposeVisitor(const CalogEntryT *entry, void *ud); + +static const char *const luaExtensions[] = { "lua", NULL }; const CalogEngineT calogLuaEngine = { "lua", + luaExtensions, luaEngineCreate, luaEngineDestroy, luaEngineRun }; -static int32_t luaEngineCreate(CalogContextT *context, void *config, void **interpOut) { +static int32_t luaEngineCreate(CalogContextT *context, void **interpOut) { CalogLuaT *lc; - int32_t status; + int32_t status; *interpOut = NULL; status = calogLuaCreate(&lc, calogContextBroker(context), calogContextId(context)); if (status != calogOkE) { return status; } - if (config != NULL) { - CalogConfigT *cfg; - int32_t index; - cfg = (CalogConfigT *)config; - for (index = 0; index < cfg->exposeCount; index++) { - status = calogLuaExpose(lc, cfg->exposeNames[index]); - if (status != calogOkE) { - calogLuaDestroy(lc); - return status; - } - } - } + calogForEach(calogContextBroker(context), luaExposeVisitor, lc); *interpOut = lc; return calogOkE; } +static void luaExposeVisitor(const CalogEntryT *entry, void *ud) { + calogLuaExpose((CalogLuaT *)ud, entry->name); +} + + static void luaEngineDestroy(void *interp) { calogLuaDestroy((CalogLuaT *)interp); } diff --git a/src/mybasic/mybasicAdapter.c b/src/mybasic/mybasicAdapter.c index c317752..6a65606 100644 --- a/src/mybasic/mybasicAdapter.c +++ b/src/mybasic/mybasicAdapter.c @@ -21,8 +21,8 @@ #define MB_NATIVE_ERROR SE_RN_FAILED_TO_OPERATE typedef struct BindingT { - CalogNativeFnT fn; - void *userData; + const char *name; // registry name; dispatched through calogCall so the actor + // layer marshals the native to the host thread } BindingT; typedef struct MyBasicRoutineT { @@ -33,7 +33,7 @@ typedef struct MyBasicRoutineT { struct CalogMyBasicT { struct mb_interpreter_t *bas; CalogT *broker; - uint32_t ctxId; + uint64_t ctxId; void **currentL; BindingT bank[MB_BANK_SIZE]; int32_t bankCount; @@ -344,9 +344,12 @@ static int mbDispatch(int32_t slot, struct mb_interpreter_t *s, void **l) { goto cleanupArgs; } + // Route through calogCall so the actor layer marshals the native to the host thread + // (like the other engines); currentL is published first so a host native that calls + // back into a BASIC routine finds this context's serving frame. savedL = context->currentL; context->currentL = l; - status = binding->fn(args, argCount, &result, binding->userData); + status = calogCall(context->broker, binding->name, args, argCount, &result); context->currentL = savedL; for (index = 0; index < argCount; index++) { @@ -540,7 +543,7 @@ static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFn // corrupt the scope. The handle is therefore valid only while that scope is // alive -- i.e. while the context is serving (a native call is on the stack, // which the actor layer's parked serve frame guarantees). - status = calogFnCreate(out, mbCallableInvoke, holder, mbCallableRelease, context->ctxId, 0); + status = calogFnCreate(out, context->broker, mbCallableInvoke, holder, mbCallableRelease, context->ctxId); if (status != calogOkE) { free(holder); return status; @@ -549,7 +552,7 @@ static int32_t mbWrapRoutine(CalogMyBasicT *context, mb_value_t routine, CalogFn } -int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint32_t ctxId) { +int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId) { CalogMyBasicT *context; struct mb_interpreter_t *bas; @@ -639,8 +642,7 @@ int32_t calogMyBasicExpose(CalogMyBasicT *context, const char *name) { return calogErrRangeE; } slot = context->bankCount; - context->bank[slot].fn = entry->fn; - context->bank[slot].userData = entry->userData; + context->bank[slot].name = entry->name; if (mb_register_func(context->bas, name, mbTrampTable[slot]) == 0) { return calogErrArgE; } diff --git a/src/mybasic/mybasicAdapter.h b/src/mybasic/mybasicAdapter.h index 390e748..b80db4d 100644 --- a/src/mybasic/mybasicAdapter.h +++ b/src/mybasic/mybasicAdapter.h @@ -17,7 +17,7 @@ typedef struct CalogMyBasicT CalogMyBasicT; -int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint32_t ctxId); +int32_t calogMyBasicCreate(CalogMyBasicT **out, CalogT *broker, uint64_t ctxId); void calogMyBasicDestroy(CalogMyBasicT *context); int32_t calogMyBasicExportRoutine(CalogMyBasicT *context, const char *routineName, CalogFnT **out); int32_t calogMyBasicExpose(CalogMyBasicT *context, const char *name); diff --git a/src/mybasic/mybasicEngine.c b/src/mybasic/mybasicEngine.c new file mode 100644 index 0000000..2f669b7 --- /dev/null +++ b/src/mybasic/mybasicEngine.c @@ -0,0 +1,77 @@ +// mybasicEngine.c -- bridges the my-basic adapter to the actor layer's CalogEngineT +// vtable, so a my-basic interpreter runs on its own context thread and is loadable by +// file extension (.bas) via calogContextLoad. +// +// my-basic keeps a little process-global state, but only two things are shared across +// interpreters: an allocation counter (patched to _Atomic in vendor/mybasic/myBasic.c, +// preserved as myBasic.c.orig) and the mb_init singletons, which are built once and are +// read-only thereafter. So interpreters EXECUTE in parallel; only lifecycle needs +// serializing -- mb_init's lazy build on the first context, mb_dispose on the last, and +// the adapter's shared context refcount. This lock covers just create and destroy; +// runSource runs unlocked, so several my-basic scripts run at once. + +#define _POSIX_C_SOURCE 200809L + +#include "calogInternal.h" +#include "mybasicAdapter.h" + +#include + +static int32_t mybasicEngineCreate(CalogContextT *context, void **interpOut); +static void mybasicEngineDestroy(void *interp); +static int32_t mybasicEngineRun(void *interp, const char *source, CalogValueT *result); +static void mybasicExposeVisitor(const CalogEntryT *entry, void *ud); + +// Serializes my-basic context lifecycle (mb_init/mb_dispose + the shared refcount); it +// is NOT held during execution, so scripts run concurrently. +static pthread_mutex_t mbLifecycleLock = PTHREAD_MUTEX_INITIALIZER; + +static const char *const mybasicExtensions[] = { "bas", NULL }; + +const CalogEngineT calogMyBasicEngine = { + "mybasic", + mybasicExtensions, + mybasicEngineCreate, + mybasicEngineDestroy, + mybasicEngineRun +}; + + +static int32_t mybasicEngineCreate(CalogContextT *context, void **interpOut) { + CalogMyBasicT *mb; + int32_t status; + + *interpOut = NULL; + pthread_mutex_lock(&mbLifecycleLock); + status = calogMyBasicCreate(&mb, calogContextBroker(context), calogContextId(context)); + if (status == calogOkE) { + calogForEach(calogContextBroker(context), mybasicExposeVisitor, mb); + *interpOut = mb; + } + pthread_mutex_unlock(&mbLifecycleLock); + return status; +} + + +static void mybasicEngineDestroy(void *interp) { + pthread_mutex_lock(&mbLifecycleLock); + calogMyBasicDestroy((CalogMyBasicT *)interp); + pthread_mutex_unlock(&mbLifecycleLock); +} + + +static int32_t mybasicEngineRun(void *interp, const char *source, CalogValueT *result) { + int32_t status; + + calogValueNil(result); + status = calogMyBasicRun((CalogMyBasicT *)interp, source); + if (status != calogOkE) { + return calogFail(result, status, "my-basic script failed"); + } + return calogOkE; +} + + +static void mybasicExposeVisitor(const CalogEntryT *entry, void *ud) { + calogMyBasicExpose((CalogMyBasicT *)ud, entry->name); +} diff --git a/src/squirrel/squirrelAdapter.c b/src/squirrel/squirrelAdapter.c index 2e03e7c..71d72ee 100644 --- a/src/squirrel/squirrelAdapter.c +++ b/src/squirrel/squirrelAdapter.c @@ -42,7 +42,7 @@ typedef struct SquirrelExportT { struct CalogSquirrelT { HSQUIRRELVM v; CalogT *broker; - uint32_t ctxId; + uint64_t ctxId; BindingT **bindings; int32_t bindingCount; int32_t bindingCap; @@ -100,7 +100,7 @@ static void squirrelCallableRelease(CalogFnT *callable) { } -int32_t calogSquirrelCreate(CalogSquirrelT **out, CalogT *broker, uint32_t ctxId) { +int32_t calogSquirrelCreate(CalogSquirrelT **out, CalogT *broker, uint64_t ctxId) { CalogSquirrelT *context; HSQUIRRELVM v; @@ -193,7 +193,7 @@ static int32_t squirrelExportAt(CalogSquirrelT *context, SQInteger idx, CalogFnT sq_addref(v, &obj); export->context = context; export->obj = obj; - status = calogFnCreate(out, squirrelCallableInvoke, export, squirrelCallableRelease, context->ctxId, 0); + status = calogFnCreate(out, context->broker, squirrelCallableInvoke, export, squirrelCallableRelease, context->ctxId); if (status != calogOkE) { sq_release(v, &obj); free(export); diff --git a/src/squirrel/squirrelAdapter.h b/src/squirrel/squirrelAdapter.h index 04e5f8e..e1117db 100644 --- a/src/squirrel/squirrelAdapter.h +++ b/src/squirrel/squirrelAdapter.h @@ -23,7 +23,7 @@ typedef struct CalogSquirrelT CalogSquirrelT; -int32_t calogSquirrelCreate(CalogSquirrelT **out, CalogT *broker, uint32_t ctxId); +int32_t calogSquirrelCreate(CalogSquirrelT **out, CalogT *broker, uint64_t ctxId); void calogSquirrelDestroy(CalogSquirrelT *context); int32_t calogSquirrelExport(CalogSquirrelT *context, const char *globalName, CalogFnT **out); int32_t calogSquirrelExpose(CalogSquirrelT *context, const char *name); diff --git a/src/squirrel/squirrelEngine.c b/src/squirrel/squirrelEngine.c index 33f4522..f19775e 100644 --- a/src/squirrel/squirrelEngine.c +++ b/src/squirrel/squirrelEngine.c @@ -8,44 +8,42 @@ #include "calogInternal.h" #include "squirrelAdapter.h" -static int32_t squirrelEngineCreate(CalogContextT *context, void *config, void **interpOut); +static int32_t squirrelEngineCreate(CalogContextT *context, void **interpOut); static void squirrelEngineDestroy(void *interp); static int32_t squirrelEngineRun(void *interp, const char *source, CalogValueT *result); +static void squirrelExposeVisitor(const CalogEntryT *entry, void *ud); + +static const char *const squirrelExtensions[] = { "nut", NULL }; const CalogEngineT calogSquirrelEngine = { "squirrel", + squirrelExtensions, squirrelEngineCreate, squirrelEngineDestroy, squirrelEngineRun }; -static int32_t squirrelEngineCreate(CalogContextT *context, void *config, void **interpOut) { +static int32_t squirrelEngineCreate(CalogContextT *context, void **interpOut) { CalogSquirrelT *sc; - int32_t status; + int32_t status; *interpOut = NULL; status = calogSquirrelCreate(&sc, calogContextBroker(context), calogContextId(context)); if (status != calogOkE) { return status; } - if (config != NULL) { - CalogConfigT *cfg; - int32_t index; - cfg = (CalogConfigT *)config; - for (index = 0; index < cfg->exposeCount; index++) { - status = calogSquirrelExpose(sc, cfg->exposeNames[index]); - if (status != calogOkE) { - calogSquirrelDestroy(sc); - return status; - } - } - } + calogForEach(calogContextBroker(context), squirrelExposeVisitor, sc); *interpOut = sc; return calogOkE; } +static void squirrelExposeVisitor(const CalogEntryT *entry, void *ud) { + calogSquirrelExpose((CalogSquirrelT *)ud, entry->name); +} + + static void squirrelEngineDestroy(void *interp) { calogSquirrelDestroy((CalogSquirrelT *)interp); } diff --git a/src/value.c b/src/value.c index 8edd991..2506ffc 100644 --- a/src/value.c +++ b/src/value.c @@ -19,13 +19,13 @@ #define CALLABLE_INITIAL_REFCOUNT 1 struct CalogFnT { - CalogNativeFnT fn; - void *userData; - CalogReleaseFnT release; - uint32_t ownerCtxId; - uint32_t ownerGen; - _Atomic int32_t refCount; - _Atomic bool alive; + CalogNativeFnT fn; + void *userData; + CalogReleaseFnT release; + CalogT *runtime; // owning runtime; its hooks route invoke/release + uint64_t ownerCtxId; // the owning context's 64-bit id (0 = host) + _Atomic int32_t refCount; + _Atomic bool alive; }; static int32_t aggregateCopyDepth(CalogAggT **out, const CalogAggT *src, int32_t depth); @@ -176,15 +176,7 @@ int32_t calogAggSet(CalogAggT *aggregate, CalogValueT *key, CalogValueT *value) } -// Optional hooks installed by the actor layer to route function-value invocation -// and the final release to the owner context's thread (design.md sec 10). NULL by -// default, leaving calogFnInvoke/calogFnRelease as plain inline operations for -// single-threaded use. -static CalogInvokeHookT invokeHook = NULL; -static CalogReleaseHookT releaseHook = NULL; - - -int32_t calogFnCreate(CalogFnT **out, CalogNativeFnT fn, void *userData, CalogReleaseFnT release, uint32_t ownerCtxId, uint32_t ownerGen) { +int32_t calogFnCreate(CalogFnT **out, CalogT *runtime, CalogNativeFnT fn, void *userData, CalogReleaseFnT release, uint64_t ownerCtxId) { CalogFnT *callable; *out = NULL; @@ -195,8 +187,8 @@ int32_t calogFnCreate(CalogFnT **out, CalogNativeFnT fn, void *userData, CalogRe callable->fn = fn; callable->userData = userData; callable->release = release; + callable->runtime = runtime; callable->ownerCtxId = ownerCtxId; - callable->ownerGen = ownerGen; atomic_init(&callable->refCount, CALLABLE_INITIAL_REFCOUNT); atomic_init(&callable->alive, true); *out = callable; @@ -229,10 +221,10 @@ int32_t calogFnInvoke(CalogFnT *callable, CalogValueT *args, int32_t argCount, C if (!atomic_load_explicit(&callable->alive, memory_order_acquire)) { return calogFail(result, calogErrNotFoundE, "callable owner no longer exists"); } - // The actor layer, if present, marshals a foreign-thread invoke to the owner's - // thread; inline otherwise. - if (invokeHook != NULL) { - return invokeHook(callable, args, argCount, result); + // The owning runtime's actor layer, if present, marshals a foreign-thread invoke + // to the owner's thread; inline otherwise (a bare broker has no hook). + if (callable->runtime != NULL && callable->runtime->invokeHook != NULL) { + return callable->runtime->invokeHook(callable, args, argCount, result); } return callable->fn(args, argCount, result, callable->userData); } @@ -246,7 +238,7 @@ void calogFnMarkDead(CalogFnT *callable) { } -uint32_t calogFnOwner(const CalogFnT *callable) { +uint64_t calogFnOwner(const CalogFnT *callable) { return callable->ownerCtxId; } @@ -259,10 +251,10 @@ void calogFnRelease(CalogFnT *callable) { } previous = atomic_fetch_sub_explicit(&callable->refCount, 1, memory_order_acq_rel); if (previous == 1) { - // This drop took the count to zero. The actor layer (if installed) routes - // the finalize to the owner's thread; otherwise finalize inline. - if (releaseHook != NULL) { - releaseHook(callable); + // This drop took the count to zero. The owning runtime's actor layer (if + // installed) routes the finalize to the owner's thread; otherwise inline. + if (callable->runtime != NULL && callable->runtime->releaseHook != NULL) { + callable->runtime->releaseHook(callable); } else { calogFnFinalize(callable); } @@ -278,13 +270,8 @@ void calogFnRetain(CalogFnT *callable) { } -void calogSetInvokeHook(CalogInvokeHookT hook) { - invokeHook = hook; -} - - -void calogSetReleaseHook(CalogReleaseHookT hook) { - releaseHook = hook; +CalogT *calogFnRuntime(const CalogFnT *callable) { + return callable->runtime; } diff --git a/tests/testActor.c b/tests/testActor.c index 1dc99fd..6a95663 100644 --- a/tests/testActor.c +++ b/tests/testActor.c @@ -1,12 +1,16 @@ -// testActor.c -- tests for the actor threading core using synthetic contexts. +// testActor.c -- the actor dispatch machinery, engine-free. // -// Synthetic contexts have no interpreter (engine == NULL); their "natives" are -// plain C functions routed to the owning context's thread. This isolates the -// threading machinery -- registry, queue, cross-context calls, the nested pump, -// the reply token+stash, and shutdown -- from any engine. Built under ASan+UBSan -// here and under ThreadSanitizer via `make tsan`. +// Uses synthetic contexts (engine == NULL) and plain C callables (calogFnCreate) to +// exercise the cross-thread invoke/reply path, the always-live nested pump (the +// re-entrant A->B->A deadlock test), the generationed registry (a callable owned by +// a closed+recycled context is rejected as dead, not misrouted), and concurrent +// drivers. calogFnInvoke on the host thread blocks-and-pumps the host queue while it +// waits, so no explicit calogPump is needed here. Built under ASan+UBSan and, via +// `make tsan`, ThreadSanitizer. -#include "calog.h" +#define _POSIX_C_SOURCE 200809L + +#include "calogInternal.h" #include #include @@ -17,34 +21,30 @@ #define DRIVER_COUNT 4 #define STRESS_ITERATIONS 100 -#define RELAY_EXPECTED 6 // ctx ids 1 + 2 + 3 +#define INNER_VALUE 100 #define REENTRANT_EXPECTED 101 -#define BOOM_MESSAGE "boom went off" -typedef struct DriverArgT { - CalogT *broker; - int32_t iterations; - int32_t failures; -} DriverArgT; - -static CalogT *broker = NULL; -static CalogContextT *ctx1 = NULL; -static CalogContextT *ctx2 = NULL; -static CalogContextT *ctx3 = NULL; -static int32_t testsRun = 0; -static int32_t testsFailed = 0; +static CalogT *calog = NULL; +static CalogContextT *ctx1 = NULL; +static CalogContextT *ctx2 = NULL; +static CalogFnT *innerA = NULL; // owned by ctx1: returns INNER_VALUE +static CalogFnT *relayB = NULL; // owned by ctx2: innerA() + 1 +static CalogFnT *entryA = NULL; // owned by ctx1: relayB() +static CalogFnT *whoFn = NULL; // owned by ctx1: returns its running context id +static int32_t testsRun = 0; +static int32_t testsFailed = 0; static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static void *driver(void *arg); -static int32_t nativeAEntry(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static int32_t nativeAInner(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static int32_t nativeBoom(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static int32_t nativeBRelay(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static int32_t nativeRelay(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static int32_t nativeWhoAmI(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t fnEntryA(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t fnInnerA(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t fnRelayB(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t fnWho(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static void *runtimeThread(void *arg); +static void testConcurrentRuntimes(void); static void testConcurrentStress(void); -static void testCrossContextCall(void); -static void testErrorChannel(void); +static void testCrossThreadInvoke(void); +static void testDestroyClosesOpenContexts(void); static void testGeneration(void); static void testReentrant(void); @@ -59,16 +59,16 @@ static void checkImpl(bool condition, const char *message, const char *file, int static void *driver(void *arg) { - DriverArgT *driverArg; - int32_t index; + int32_t *failures; + int32_t index; - driverArg = (DriverArgT *)arg; - for (index = 0; index < driverArg->iterations; index++) { - CalogValueT result; - int32_t status; - status = calogCall(driverArg->broker, "relay", NULL, 0, &result); - if (status != calogOkE || result.type != calogIntE || result.as.i != RELAY_EXPECTED) { - driverArg->failures++; + failures = (int32_t *)arg; + for (index = 0; index < STRESS_ITERATIONS; index++) { + CalogValueT result; + int32_t status; + status = calogFnInvoke(whoFn, NULL, 0, &result); + if (status != calogOkE || result.type != calogIntE || result.as.i != (int64_t)calogContextId(ctx1)) { + (*failures)++; } calogValueFree(&result); } @@ -76,59 +76,45 @@ static void *driver(void *arg) { } -static int32_t nativeAEntry(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { - CalogT *callBroker; - CalogValueT inner; - int32_t status; +static int32_t fnEntryA(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + CalogValueT inner; + int32_t status; (void)args; (void)argCount; (void)userData; - callBroker = calogCurrent(); - status = calogCall(callBroker, "bRelay", NULL, 0, &inner); + // Runs on ctx1; calls relayB (ctx2), which calls back into ctx1 -- the deadlock + // test, resolved by ctx1's nested pump. + status = calogFnInvoke(relayB, NULL, 0, &inner); if (status != calogOkE) { calogValueFree(&inner); - return calogFail(result, status, "aEntry: bRelay failed"); + return calogFail(result, status, "entryA: relayB failed"); } calogValueMove(result, &inner); return calogOkE; } -static int32_t nativeAInner(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { +static int32_t fnInnerA(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)args; (void)argCount; (void)userData; - calogValueInt(result, 100); + calogValueInt(result, INNER_VALUE); return calogOkE; } -static int32_t nativeBoom(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { - (void)args; - (void)argCount; - (void)userData; - // Fails on a context other than the caller's: exercises the single error - // channel -- the message rides back in result, status carries the code. - return calogFail(result, calogErrArgE, BOOM_MESSAGE); -} - - -static int32_t nativeBRelay(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { - CalogT *callBroker; - CalogValueT inner; - int32_t status; +static int32_t fnRelayB(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + CalogValueT inner; + int32_t status; (void)args; (void)argCount; (void)userData; - // Calls back into context 1 while context 1 is blocked waiting on this call: - // the deadlock test. Context 1 services it via the nested pump. - callBroker = calogCurrent(); - status = calogCall(callBroker, "aInner", NULL, 0, &inner); + status = calogFnInvoke(innerA, NULL, 0, &inner); if (status != calogOkE) { calogValueFree(&inner); - return calogFail(result, status, "bRelay: aInner failed"); + return calogFail(result, status, "relayB: innerA failed"); } calogValueInt(result, inner.as.i + 1); calogValueFree(&inner); @@ -136,35 +122,7 @@ static int32_t nativeBRelay(CalogValueT *args, int32_t argCount, CalogValueT *re } -static int32_t nativeRelay(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { - CalogT *callBroker; - CalogValueT v2; - CalogValueT v3; - int32_t status; - - (void)args; - (void)argCount; - (void)userData; - callBroker = calogCurrent(); - status = calogCall(callBroker, "who2", NULL, 0, &v2); - if (status != calogOkE) { - calogValueFree(&v2); - return calogFail(result, status, "relay: who2 failed"); - } - status = calogCall(callBroker, "who3", NULL, 0, &v3); - if (status != calogOkE) { - calogValueFree(&v2); - calogValueFree(&v3); - return calogFail(result, status, "relay: who3 failed"); - } - calogValueInt(result, (int64_t)calogCurrentId() + v2.as.i + v3.as.i); - calogValueFree(&v2); - calogValueFree(&v3); - return calogOkE; -} - - -static int32_t nativeWhoAmI(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { +static int32_t fnWho(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)args; (void)argCount; (void)userData; @@ -173,129 +131,168 @@ static int32_t nativeWhoAmI(CalogValueT *args, int32_t argCount, CalogValueT *re } -static void testConcurrentStress(void) { - pthread_t threads[DRIVER_COUNT]; - DriverArgT driverArgs[DRIVER_COUNT]; - int32_t index; - int32_t totalFailures; +static void *runtimeThread(void *arg) { + int32_t *ok; + CalogT *rt; + CalogContextT *ctx; + CalogFnT *fn; + CalogValueT result; + int32_t status; + + // Each thread creates, drives, and destroys its OWN independent runtime -- no + // process-global state is shared. A callable owned by this runtime's context must + // route to that context's thread and back, entirely within this runtime. + ok = (int32_t *)arg; + rt = calogCreate(); + if (rt == NULL) { + return NULL; + } + ctx = calogContextOpen(rt, NULL); + if (ctx == NULL) { + calogDestroy(rt); + return NULL; + } + calogFnCreate(&fn, rt, fnWho, NULL, NULL, calogContextId(ctx)); + status = calogFnInvoke(fn, NULL, 0, &result); + if (status == calogOkE && result.type == calogIntE && result.as.i == (int64_t)calogContextId(ctx)) { + *ok = 1; + } + calogValueFree(&result); + calogFnRelease(fn); + calogContextClose(ctx); + calogDestroy(rt); + return NULL; +} + + +static void testConcurrentRuntimes(void) { + pthread_t threads[DRIVER_COUNT]; + int32_t oks[DRIVER_COUNT]; + int32_t index; for (index = 0; index < DRIVER_COUNT; index++) { - driverArgs[index].broker = broker; - driverArgs[index].iterations = STRESS_ITERATIONS; - driverArgs[index].failures = 0; - pthread_create(&threads[index], NULL, driver, &driverArgs[index]); + oks[index] = 0; + pthread_create(&threads[index], NULL, runtimeThread, &oks[index]); } - totalFailures = 0; for (index = 0; index < DRIVER_COUNT; index++) { pthread_join(threads[index], NULL); - totalFailures += driverArgs[index].failures; + CHECK(oks[index] == 1, "an independent runtime on its own host thread dispatched correctly"); } - CHECK(totalFailures == 0, "concurrent drivers: every fan-out call returned the expected sum"); } -static void testCrossContextCall(void) { - CalogValueT result; - int32_t status; +static void testConcurrentStress(void) { + pthread_t threads[DRIVER_COUNT]; + int32_t failures[DRIVER_COUNT]; + int32_t index; + int32_t total; - status = calogCall(broker, "who1", NULL, 0, &result); - CHECK(status == calogOkE && result.type == calogIntE && result.as.i == 1, "call ran on context 1's thread"); - calogValueFree(&result); - - status = calogCall(broker, "who2", NULL, 0, &result); - CHECK(status == calogOkE && result.type == calogIntE && result.as.i == 2, "call ran on context 2's thread"); - calogValueFree(&result); - - status = calogCall(broker, "who3", NULL, 0, &result); - CHECK(status == calogOkE && result.type == calogIntE && result.as.i == 3, "call ran on context 3's thread"); - calogValueFree(&result); + for (index = 0; index < DRIVER_COUNT; index++) { + failures[index] = 0; + pthread_create(&threads[index], NULL, driver, &failures[index]); + } + total = 0; + for (index = 0; index < DRIVER_COUNT; index++) { + pthread_join(threads[index], NULL); + total += failures[index]; + } + CHECK(total == 0, "concurrent drivers: every callable invoke ran on ctx1 and returned its id"); } -static void testErrorChannel(void) { - CalogValueT result; - int32_t status; +static void testCrossThreadInvoke(void) { + CalogValueT result; + int32_t status; - // A cross-context call that fails must surface its message through result - // (the single error channel) and the failure status -- freed once, no leak. - status = calogCall(broker, "boom", NULL, 0, &result); - CHECK(status == calogErrArgE && result.type == calogStringE && strcmp(result.as.s.bytes, BOOM_MESSAGE) == 0, "cross-context error rides back in result, freed once"); + // whoFn is owned by ctx1; invoking it from the host thread must run it on ctx1. + status = calogFnInvoke(whoFn, NULL, 0, &result); + CHECK(status == calogOkE && result.type == calogIntE && result.as.i == (int64_t)calogContextId(ctx1), "callable invoke routed to its owner context's thread"); calogValueFree(&result); } static void testGeneration(void) { - CalogContextT *tmpA; - CalogContextT *tmpB; - uint32_t staleId; - CalogValueT result; - int32_t status; + CalogContextT *tmp; + CalogFnT *orphan; + CalogValueT result; + uint64_t staleId; + int32_t status; - calogContextCreate(broker, NULL, NULL, &tmpA); - calogContextStart(tmpA); - staleId = calogContextId(tmpA); - calogContextDestroy(tmpA); + tmp = calogContextOpen(calog, NULL); + staleId = calogContextId(tmp); + status = calogFnCreate(&orphan, calog, fnWho, NULL, NULL, staleId); + CHECK(status == calogOkE, "created a callable owned by a soon-to-close context"); - // The next create recycles tmpA's slot but with a bumped generation, so its - // id differs from the destroyed one. - calogContextCreate(broker, NULL, NULL, &tmpB); - CHECK(calogContextId(tmpB) != staleId, "recycled slot yields a new generationed id"); - calogContextStart(tmpB); + calogContextClose(tmp); - // A binding still naming the destroyed context's id must fail dead, never - // misroute to the context that recycled the slot. - calogRegister(broker, "ghost", nativeWhoAmI, NULL, staleId); - status = calogCall(broker, "ghost", NULL, 0, &result); - CHECK(status == calogErrDeadE, "call to a recycled id is rejected as dead, not misrouted"); + // A new context recycles tmp's slot with a bumped generation. + tmp = calogContextOpen(calog, NULL); + CHECK(calogContextId(tmp) != staleId, "recycled slot yields a new generationed id"); + + // Invoking the orphan (old id) must fail dead, not misroute to the recycler. + status = calogFnInvoke(orphan, NULL, 0, &result); + CHECK(status == calogErrDeadE, "callable of a recycled context is rejected as dead, not misrouted"); calogValueFree(&result); - calogContextDestroy(tmpB); + calogFnRelease(orphan); + calogContextClose(tmp); } static void testReentrant(void) { - CalogValueT result; - int32_t status; + CalogValueT result; + int32_t status; - // 1 -> 2 -> 1: context 1 calls context 2, which calls back into context 1 - // while context 1 is blocked. The nested pump must service it (no deadlock). - status = calogCall(broker, "aEntry", NULL, 0, &result); + // host -> entryA(ctx1) -> relayB(ctx2) -> innerA(ctx1): the re-entrant call back + // into ctx1 must be serviced by its nested pump (no deadlock). + status = calogFnInvoke(entryA, NULL, 0, &result); CHECK(status == calogOkE && result.type == calogIntE && result.as.i == REENTRANT_EXPECTED, "re-entrant A->B->A resolved without deadlock"); calogValueFree(&result); } +static void testDestroyClosesOpenContexts(void) { + int32_t i; + + // Open several contexts and deliberately DO NOT close them: because CalogT owns + // its active-context list, calogDestroy (in main) must close them all. ASan + // verifies there is no leak at exit -- this is the cleanup guarantee. + for (i = 0; i < 32; i++) { + CalogContextT *leaked; + leaked = calogContextOpen(calog, NULL); + CHECK(leaked != NULL, "opened a context left for calogDestroy to close"); + } +} + + int main(void) { - broker = calogCreate(); - if (broker == NULL) { - printf("broker create failed\n"); + calog = calogCreate(); + if (calog == NULL) { + printf("calog create failed\n"); return 1; } + ctx1 = calogContextOpen(calog, NULL); + ctx2 = calogContextOpen(calog, NULL); - calogContextCreate(broker, NULL, NULL, &ctx1); - calogContextCreate(broker, NULL, NULL, &ctx2); - calogContextCreate(broker, NULL, NULL, &ctx3); + calogFnCreate(&whoFn, calog, fnWho, NULL, NULL, calogContextId(ctx1)); + calogFnCreate(&innerA, calog, fnInnerA, NULL, NULL, calogContextId(ctx1)); + calogFnCreate(&relayB, calog, fnRelayB, NULL, NULL, calogContextId(ctx2)); + calogFnCreate(&entryA, calog, fnEntryA, NULL, NULL, calogContextId(ctx1)); - calogRegister(broker, "who1", nativeWhoAmI, NULL, calogContextId(ctx1)); - calogRegister(broker, "who2", nativeWhoAmI, NULL, calogContextId(ctx2)); - calogRegister(broker, "who3", nativeWhoAmI, NULL, calogContextId(ctx3)); - calogRegister(broker, "aEntry", nativeAEntry, NULL, calogContextId(ctx1)); - calogRegister(broker, "bRelay", nativeBRelay, NULL, calogContextId(ctx2)); - calogRegister(broker, "aInner", nativeAInner, NULL, calogContextId(ctx1)); - calogRegister(broker, "relay", nativeRelay, NULL, calogContextId(ctx1)); - calogRegister(broker, "boom", nativeBoom, NULL, calogContextId(ctx2)); - - calogContextStart(ctx1); - calogContextStart(ctx2); - calogContextStart(ctx3); - - testCrossContextCall(); + testCrossThreadInvoke(); testReentrant(); testConcurrentStress(); - testErrorChannel(); testGeneration(); + testDestroyClosesOpenContexts(); + testConcurrentRuntimes(); - calogDestroy(broker); + calogFnRelease(whoFn); + calogFnRelease(innerA); + calogFnRelease(relayB); + calogFnRelease(entryA); + calogContextClose(ctx1); + calogContextClose(ctx2); + calogDestroy(calog); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); diff --git a/tests/testBroker.c b/tests/testBroker.c index 0a93c98..ece884e 100644 --- a/tests/testBroker.c +++ b/tests/testBroker.c @@ -140,7 +140,7 @@ static void testAggregateCopyCleanup(void) { int32_t level; releaseHookCalls = 0; - status = calogFnCreate(&callable, nativeAdd, NULL, countingRelease, 0, 0); + status = calogFnCreate(&callable, NULL, nativeAdd, NULL, countingRelease, 0); CHECK(status == calogOkE, "cleanup callable create"); status = calogAggCreate(&list, calogListE); @@ -234,7 +234,7 @@ static void testCallable(void) { releaseHookCalls = 0; - status = calogFnCreate(&callable, nativeAdd, NULL, countingRelease, 0, 0); + status = calogFnCreate(&callable, NULL, nativeAdd, NULL, countingRelease, 0); CHECK(status == calogOkE, "callable create"); calogValueFn(&handle, callable); @@ -267,7 +267,7 @@ static void testCallableDead(void) { int32_t status; releaseHookCalls = 0; - status = calogFnCreate(&callable, nativeAdd, NULL, countingRelease, 0, 0); + status = calogFnCreate(&callable, NULL, nativeAdd, NULL, countingRelease, 0); CHECK(status == calogOkE, "dead callable create"); calogValueFn(&handle, callable); @@ -354,21 +354,21 @@ static void testRegistry(void) { broker = calogBrokerCreate(); CHECK(broker != NULL, "broker create"); - status = calogRegister(broker, "add", nativeAdd, NULL, 0); + status = calogRegister(broker, "add", nativeAdd, NULL); CHECK(status == calogOkE, "register add"); - status = calogRegister(broker, "boom", nativeBoom, NULL, 0); + status = calogRegister(broker, "boom", nativeBoom, NULL); CHECK(status == calogOkE, "register boom"); for (index = 0; index < BULK_REGISTER; index++) { snprintf(name, sizeof(name), "fn%d", index); - status = calogRegister(broker, name, nativeAdd, NULL, 0); + status = calogRegister(broker, name, nativeAdd, NULL); CHECK(status == calogOkE, "register bulk"); } CHECK(calogLookup(broker, "fn50") != NULL, "bulk lookup hit"); CHECK(calogLookup(broker, "nope") == NULL, "lookup miss"); // Re-registration must replace in place without adding a slot. - status = calogRegister(broker, "add", nativeBoom, NULL, 0); + status = calogRegister(broker, "add", nativeBoom, NULL); CHECK(status == calogOkE, "re-register add"); calogValueInt(&args[0], 3); @@ -376,7 +376,7 @@ static void testRegistry(void) { status = calogCall(broker, "boom", args, ADD_ARITY, &result); CHECK(status == calogErrArgE, "re-registered add now boom"); calogValueFree(&result); - status = calogRegister(broker, "add", nativeAdd, NULL, 0); + status = calogRegister(broker, "add", nativeAdd, NULL); CHECK(status == calogOkE, "restore add"); status = calogCall(broker, "add", args, ADD_ARITY, &result); diff --git a/tests/testEngineJs.c b/tests/testEngineJs.c index bc85271..851b0c8 100644 --- a/tests/testEngineJs.c +++ b/tests/testEngineJs.c @@ -1,12 +1,9 @@ -// testEngineJs.c -- a real Duktape (JavaScript) heap running on a CalogContextT -// thread, the fourth engine. -// -// Proves the CalogEngineT wiring and that the actor core drives JS exactly as it drives -// Lua/Squirrel: a JS script on jsCtx's thread calls a thread-agnostic native -// (report), a native owned by a different context (doubleIt, routed cross-thread), -// and -- exercising design.md sec 10 -- a JS closure captured on jsCtx's thread is -// invoked and released from the main thread, both marshalled back to the owner. -// Built under ASan+UBSan. +// testEngineJs.c -- Duktape (JavaScript) on a context thread, driven the host way: +// register natives, open a context, fire-and-forget a script, and calogPump on the +// host thread. Verifies host-thread dispatch, the inline escape hatch, the sec-10 +// cross-thread callback, the error handler, and concurrent contexts. + +#define _POSIX_C_SOURCE 200809L #include "calog.h" @@ -14,29 +11,35 @@ #include #include #include +#include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) -#define DOUBLE_EXPECTED 42 -#define CALLBACK_INPUT 7 -#define CALLBACK_EXPECTED 107 +#define PUMP_LIMIT 4000 -static CalogT *broker = NULL; -static CalogContextT *jsCtx = NULL; -static CalogContextT *workerCtx = NULL; +static CalogT *calog = NULL; static _Atomic int64_t reportedValue = 0; -static _Atomic uint32_t reportedCtxId = 0; -static _Atomic uint32_t doubleItCtxId = 0; -static CalogFnT *_Atomic storedCb = NULL; +static _Atomic uint64_t reportedCtxId = 0xFFFFu; +static _Atomic uint64_t inlineCtxId = 0xFFFFu; +static _Atomic int32_t bumpCount = 0; +static _Atomic bool scriptDone = false; +static _Atomic int32_t errorCount = 0; +static _Atomic uint64_t errorCtxId = 0; +static CalogFnT *_Atomic storedCb = NULL; static int32_t testsRun = 0; static int32_t testsFailed = 0; static void checkImpl(bool condition, const char *message, const char *file, int32_t line); -static int32_t nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static void testCallbackAcrossContexts(void); -static void testCrossContextFromScript(void); +static void onError(uint64_t contextId, const char *message, void *userData); +static void pumpUntilDone(void); +static void testConcurrentContexts(void); +static void testCrossThreadCallback(void); +static void testHostAndInlineNatives(void); static void testScriptError(void); @@ -49,24 +52,43 @@ static void checkImpl(bool condition, const char *message, const char *file, int } -static int32_t nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { +static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; (void)userData; - atomic_store(&doubleItCtxId, calogCurrentId()); - if (argCount != 1 || args[0].type != calogIntE) { - return calogFail(result, calogErrArgE, "doubleIt expects one integer"); - } - calogValueInt(result, args[0].as.i * 2); + atomic_fetch_add(&bumpCount, 1); + calogValueNil(result); + return calogOkE; +} + + +static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + (void)userData; + atomic_store(&scriptDone, true); + calogValueNil(result); return calogOkE; } static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)userData; + calogValueNil(result); if (argCount != 1 || args[0].type != calogIntE) { return calogFail(result, calogErrArgE, "report expects one integer"); } atomic_store(&reportedCtxId, calogCurrentId()); atomic_store(&reportedValue, args[0].as.i); + return calogOkE; +} + + +static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + (void)userData; + atomic_store(&inlineCtxId, calogCurrentId()); calogValueNil(result); return calogOkE; } @@ -78,96 +100,139 @@ static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *res if (argCount != 1 || args[0].type != calogFnE) { return calogFail(result, calogErrArgE, "setCb expects one function"); } - // Runs on jsCtx's thread; the closure is owned by jsCtx. Retain it past this call. calogFnRetain(args[0].as.fn); atomic_store(&storedCb, args[0].as.fn); return calogOkE; } -static void testCallbackAcrossContexts(void) { - CalogFnT *callback; - CalogValueT arg; - CalogValueT result; - int32_t status; - - // Capture a JS closure on jsCtx's thread via setCb (owned by jsCtx). - status = calogContextEval(jsCtx, "setCb(function(x) { return x + 100; })", &result); - CHECK(status == calogOkE, "captured a JS closure as a CalogFnT on its owner thread"); - calogValueFree(&result); - - callback = atomic_load(&storedCb); - CHECK(callback != NULL, "callback was stored"); - - // Invoke from the MAIN thread: calogFnInvoke must marshal to jsCtx's thread - // (design.md sec 10), so the closure runs on its owner heap, never here. - calogValueInt(&arg, CALLBACK_INPUT); - status = calogFnInvoke(callback, &arg, 1, &result); - CHECK(status == calogOkE && result.type == calogIntE && result.as.i == CALLBACK_EXPECTED, "cross-thread callable invoke routed to the owner context"); - calogValueFree(&result); - calogValueFree(&arg); - - // Release from the main thread: the registry-slot drop must run on jsCtx's heap. - calogFnRelease(callback); +static void onError(uint64_t contextId, const char *message, void *userData) { + (void)message; + (void)userData; + atomic_store(&errorCtxId, contextId); + atomic_fetch_add(&errorCount, 1); } -static void testCrossContextFromScript(void) { - CalogValueT result; - int32_t status; +static void pumpUntilDone(void) { + struct timespec ts = { 0, 500000 }; + int errorsBefore; + int i; - status = calogContextEval(jsCtx, "report(doubleIt(21))", &result); - CHECK(status == calogOkE, "JS script with a cross-context call ran without error"); - CHECK(atomic_load(&reportedValue) == DOUBLE_EXPECTED, "cross-context doubleIt result flowed back into the script"); - CHECK(atomic_load(&doubleItCtxId) == calogContextId(workerCtx), "doubleIt executed on the worker context's thread"); - CHECK(atomic_load(&reportedCtxId) == calogContextId(jsCtx), "report executed on the JS context's own thread"); + errorsBefore = atomic_load(&errorCount); + for (i = 0; i < PUMP_LIMIT; i++) { + calogPump(calog); + if (atomic_load(&scriptDone) || atomic_load(&errorCount) != errorsBefore) { + calogPump(calog); + return; + } + nanosleep(&ts, NULL); + } +} + + +static void testHostAndInlineNatives(void) { + CalogContextT *ctx; + + ctx = calogContextOpen(calog, &calogJsEngine); + CHECK(ctx != NULL, "opened a JS context"); + + atomic_store(&scriptDone, false); + calogContextEval(ctx, "report(42); reportInline(1); done();"); + pumpUntilDone(); + + CHECK(atomic_load(&reportedValue) == 42, "host native received the argument"); + CHECK(atomic_load(&reportedCtxId) == 0, "default native ran on the host thread (id 0)"); + CHECK(atomic_load(&inlineCtxId) == calogContextId(ctx), "inline native ran on the script's own thread"); + + calogContextClose(ctx); +} + + +static void testCrossThreadCallback(void) { + CalogContextT *ctx; + CalogFnT *callback; + CalogValueT arg; + CalogValueT result; + int32_t status; + + ctx = calogContextOpen(calog, &calogJsEngine); + atomic_store(&scriptDone, false); + atomic_store(&storedCb, NULL); + calogContextEval(ctx, "setCb(function(x) { return x + 100; }); done();"); + pumpUntilDone(); + + callback = atomic_load(&storedCb); + CHECK(callback != NULL, "a JS closure was captured as a CalogFnT"); + + calogValueInt(&arg, 7); + status = calogFnInvoke(callback, &arg, 1, &result); + CHECK(status == calogOkE && result.type == calogIntE && result.as.i == 107, "cross-thread callable invoke routed to the owner"); calogValueFree(&result); + calogValueFree(&arg); + calogFnRelease(callback); + + calogContextClose(ctx); } static void testScriptError(void) { - CalogValueT result; - int32_t status; + CalogContextT *ctx; + int32_t before; - status = calogContextEval(jsCtx, "@@@ not valid js @@@", &result); - CHECK(status != calogOkE && result.type == calogStringE, "script error surfaced through result, not a crash"); - calogValueFree(&result); + ctx = calogContextOpen(calog, &calogJsEngine); + before = atomic_load(&errorCount); + atomic_store(&scriptDone, false); + calogContextEval(ctx, "@@@ not valid js @@@"); + pumpUntilDone(); + + CHECK(atomic_load(&errorCount) == before + 1, "a fire-and-forget script error reached the error handler"); + CHECK(atomic_load(&errorCtxId) == calogContextId(ctx), "the error names the failing context"); + + calogContextClose(ctx); +} + + +static void testConcurrentContexts(void) { + CalogContextT *ctxs[3]; + struct timespec ts = { 0, 500000 }; + int32_t i; + + atomic_store(&bumpCount, 0); + for (i = 0; i < 3; i++) { + ctxs[i] = calogContextOpen(calog, &calogJsEngine); + calogContextEval(ctxs[i], "bump(); bump(); bump();"); + } + for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 9; i++) { + calogPump(calog); + nanosleep(&ts, NULL); + } + CHECK(atomic_load(&bumpCount) == 9, "three concurrent contexts all dispatched to the host thread"); + for (i = 0; i < 3; i++) { + calogContextClose(ctxs[i]); + } } int main(void) { - const char *exposeNames[3]; - CalogConfigT jsConfig; - - broker = calogCreate(); - if (broker == NULL) { - printf("broker create failed\n"); + calog = calogCreate(); + if (calog == NULL) { + printf("calog create failed\n"); return 1; } + calogSetErrorHandler(calog, onError, NULL); + calogRegister(calog, "report", nativeReport, NULL); + calogRegister(calog, "setCb", nativeSetCb, NULL); + calogRegister(calog, "done", nativeDone, NULL); + calogRegister(calog, "bump", nativeBump, NULL); + calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); - calogContextCreate(broker, NULL, NULL, &workerCtx); - - exposeNames[0] = "doubleIt"; - exposeNames[1] = "report"; - exposeNames[2] = "setCb"; - jsConfig.exposeNames = exposeNames; - jsConfig.exposeCount = 3; - calogContextCreate(broker, &calogJsEngine, &jsConfig, &jsCtx); - - // setCb is owned by jsCtx, so register it once jsCtx exists (for its id), before - // calogContextStart exposes the names on the thread. - calogRegister(broker, "doubleIt", nativeDoubleIt, NULL, calogContextId(workerCtx)); - calogRegister(broker, "report", nativeReport, NULL, 0); - calogRegister(broker, "setCb", nativeSetCb, NULL, calogContextId(jsCtx)); - - calogContextStart(workerCtx); - calogContextStart(jsCtx); - - testCrossContextFromScript(); + testHostAndInlineNatives(); + testCrossThreadCallback(); testScriptError(); - testCallbackAcrossContexts(); + testConcurrentContexts(); - calogDestroy(broker); + calogDestroy(calog); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); diff --git a/tests/testEngineLua.c b/tests/testEngineLua.c index a916eac..4828c2c 100644 --- a/tests/testEngineLua.c +++ b/tests/testEngineLua.c @@ -1,12 +1,10 @@ -// testEngineLua.c -- a real Lua interpreter running on a CalogContextT thread. -// -// Proves the CalogEngineT wiring: a Lua context creates its interpreter on its own -// thread (calogLuaEngine.createInterpreter), exposes broker natives there, and runs -// scripts via calogContextEval. The script calls one thread-agnostic native (report, -// owner 0, run inline on the Lua thread) and one native owned by a DIFFERENT -// context (doubleIt, owned by ctx2) -- the exposed-native trampoline routes that -// call through the broker to ctx2's thread and back, with no callback in the -// script. Built under ASan+UBSan; the threading core is covered by testActor. +// testEngineLua.c -- Lua on a context thread, driven the way a host drives calog: +// register natives, open a context, fire-and-forget a script, and calogPump on the +// host thread to service the script's native calls. Verifies host-thread dispatch, +// the inline escape hatch, the sec-10 cross-thread callback, the error handler, and +// concurrent contexts. Built under ASan+UBSan. + +#define _POSIX_C_SOURCE 200809L #include "calog.h" @@ -14,31 +12,41 @@ #include #include #include +#include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) -#define DOUBLE_EXPECTED 42 -#define PURE_EXPECTED 6 +#define PUMP_LIMIT 4000 // ~2s at 0.5ms/iter, a generous upper bound -static CalogT *broker = NULL; -static CalogContextT *luaCtx = NULL; -static CalogContextT *workerCtx = NULL; -static _Atomic int64_t reportedValue = 0; -static _Atomic uint32_t reportedCtxId = 0; -static _Atomic uint32_t doubleItCtxId = 0; -static CalogFnT *_Atomic storedCb = NULL; -static int32_t testsRun = 0; -static int32_t testsFailed = 0; +static CalogT *calog = NULL; +static _Atomic int64_t reportedValue = 0; +static _Atomic uint64_t reportedCtxId = 0xFFFFu; +static _Atomic uint64_t inlineCtxId = 0xFFFFu; +static _Atomic int32_t bumpCount = 0; +static _Atomic bool scriptDone = false; +static _Atomic int32_t errorCount = 0; +static _Atomic uint64_t errorCtxId = 0; +static CalogFnT *_Atomic storedCb = NULL; +static _Atomic int32_t matchCount = 0; +static _Atomic int32_t mismatchCount = 0; +static int32_t testsRun = 0; +static int32_t testsFailed = 0; static void checkImpl(bool condition, const char *message, const char *file, int32_t line); -static int32_t nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeCheckRuntime(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static void testCallbackAcrossContexts(void); -static void testCrossContextFromScript(void); -static void testFailedInterpreter(void); -static void testPureScript(void); +static void onError(uint64_t contextId, const char *message, void *userData); +static void pumpUntilDone(void); +static void testConcurrentContexts(void); +static void testContextLoad(void); +static void testCrossThreadCallback(void); +static void testHostAndInlineNatives(void); static void testScriptError(void); +static void testSingleThreadMultiRuntime(void); static void checkImpl(bool condition, const char *message, const char *file, int32_t line) { @@ -50,26 +58,60 @@ static void checkImpl(bool condition, const char *message, const char *file, int } -static int32_t nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { +static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; (void)userData; - // Owned by workerCtx: routing must run this on workerCtx's thread, so record - // the id it actually ran on. - atomic_store(&doubleItCtxId, calogCurrentId()); - if (argCount != 1 || args[0].type != calogIntE) { - return calogFail(result, calogErrArgE, "doubleIt expects one integer"); + atomic_fetch_add(&bumpCount, 1); + calogValueNil(result); + return calogOkE; +} + + +static int32_t nativeCheckRuntime(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + // userData is the runtime this native was registered under. Run from a script and + // serviced by calogPump, it must see calogCurrent() == that runtime -- which holds + // only if pump presents the pumping thread as the pumped runtime's host, so one + // thread can host several runtimes. + if (calogCurrent() == (CalogT *)userData) { + atomic_fetch_add(&matchCount, 1); + } else { + atomic_fetch_add(&mismatchCount, 1); } - calogValueInt(result, args[0].as.i * 2); + calogValueNil(result); + return calogOkE; +} + + +static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + (void)userData; + atomic_store(&scriptDone, true); + calogValueNil(result); return calogOkE; } static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)userData; + calogValueNil(result); if (argCount != 1 || args[0].type != calogIntE) { return calogFail(result, calogErrArgE, "report expects one integer"); } - atomic_store(&reportedCtxId, calogCurrentId()); + atomic_store(&reportedCtxId, calogCurrentId()); // should be 0 (host thread) atomic_store(&reportedValue, args[0].as.i); + return calogOkE; +} + + +static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + (void)userData; + atomic_store(&inlineCtxId, calogCurrentId()); // should be the script's context id calogValueNil(result); return calogOkE; } @@ -81,137 +123,215 @@ static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *res if (argCount != 1 || args[0].type != calogFnE) { return calogFail(result, calogErrArgE, "setCb expects one function"); } - // Runs on luaCtx's thread; the closure is owned by luaCtx. Retain it so it - // outlives this call (the trampoline frees the args afterward). calogFnRetain(args[0].as.fn); atomic_store(&storedCb, args[0].as.fn); return calogOkE; } -static void testCallbackAcrossContexts(void) { - CalogFnT *callback; - CalogValueT arg; - CalogValueT result; - int32_t status; +static void onError(uint64_t contextId, const char *message, void *userData) { + (void)message; + (void)userData; + atomic_store(&errorCtxId, contextId); + atomic_fetch_add(&errorCount, 1); +} - // A Lua closure is captured on luaCtx's thread via setCb (owned by luaCtx). - status = calogContextEval(luaCtx, "setCb(function(x) return x + 100 end)", &result); - CHECK(status == calogOkE, "captured a Lua closure as a CalogFnT on its owner thread"); - calogValueFree(&result); + +// Pump the host thread until the running script signals done (or errors), bounded. +static void pumpUntilDone(void) { + struct timespec ts = { 0, 500000 }; // 0.5 ms + int errorsBefore; + int i; + + errorsBefore = atomic_load(&errorCount); + for (i = 0; i < PUMP_LIMIT; i++) { + calogPump(calog); + if (atomic_load(&scriptDone) || atomic_load(&errorCount) != errorsBefore) { + calogPump(calog); // one more sweep to drain trailing calls + return; + } + nanosleep(&ts, NULL); + } +} + + +static void testHostAndInlineNatives(void) { + CalogContextT *ctx; + + ctx = calogContextOpen(calog, &calogLuaEngine); + CHECK(ctx != NULL, "opened a Lua context"); + + atomic_store(&scriptDone, false); + calogContextEval(ctx, "report(42); reportInline(1); done()"); + pumpUntilDone(); + + CHECK(atomic_load(&reportedValue) == 42, "host native received the argument"); + CHECK(atomic_load(&reportedCtxId) == 0, "default native ran on the host thread (id 0)"); + CHECK(atomic_load(&inlineCtxId) == calogContextId(ctx), "inline native ran on the script's own thread"); + + calogContextClose(ctx); +} + + +static void testCrossThreadCallback(void) { + CalogContextT *ctx; + CalogFnT *callback; + CalogValueT arg; + CalogValueT result; + int32_t status; + + ctx = calogContextOpen(calog, &calogLuaEngine); + atomic_store(&scriptDone, false); + atomic_store(&storedCb, NULL); + calogContextEval(ctx, "setCb(function(x) return x + 100 end); done()"); + pumpUntilDone(); callback = atomic_load(&storedCb); - CHECK(callback != NULL, "callback was stored"); + CHECK(callback != NULL, "a Lua closure was captured as a CalogFnT"); - // Invoke it from the MAIN thread: calogFnInvoke must marshal to luaCtx's thread - // (design.md sec 10) so the closure runs on its owner, never on the main thread. + // Invoke it from the host thread: it must marshal to the Lua context's thread + // (sec 10). calogFnInvoke blocks-and-pumps the host queue while it waits. calogValueInt(&arg, 7); status = calogFnInvoke(callback, &arg, 1, &result); - CHECK(status == calogOkE && result.type == calogIntE && result.as.i == 107, "cross-thread callable invoke routed to the owner context"); + CHECK(status == calogOkE && result.type == calogIntE && result.as.i == 107, "cross-thread callable invoke routed to the owner"); calogValueFree(&result); calogValueFree(&arg); - - // Drop the last reference from the main thread: the luaL_unref must run on - // luaCtx's thread, not here (ASan/TSan would flag an off-thread touch). calogFnRelease(callback); -} - -static void testCrossContextFromScript(void) { - CalogValueT result; - int32_t status; - - // doubleIt is owned by workerCtx; report is thread-agnostic. The script reads - // as plain synchronous code -- the cross-context hop is invisible to it. - status = calogContextEval(luaCtx, "report(doubleIt(21))", &result); - CHECK(status == calogOkE, "script with a cross-context call ran without error"); - CHECK(atomic_load(&reportedValue) == DOUBLE_EXPECTED, "cross-context doubleIt result flowed back into the script"); - CHECK(atomic_load(&doubleItCtxId) == calogContextId(workerCtx), "doubleIt executed on the worker context's thread"); - CHECK(atomic_load(&reportedCtxId) == calogContextId(luaCtx), "report executed on the Lua context's own thread"); - calogValueFree(&result); -} - - -static void testFailedInterpreter(void) { - CalogContextT *failCtx; - const char *badNames[1]; - CalogConfigT badConfig; - CalogValueT result; - int32_t status; - - // A config naming a native that was never registered makes createInterpreter - // fail on the context's thread (interp stays NULL). The eval must come back as - // an error rather than dereferencing the NULL interpreter. - badNames[0] = "noSuchNative"; - badConfig.exposeNames = badNames; - badConfig.exposeCount = 1; - calogContextCreate(broker, &calogLuaEngine, &badConfig, &failCtx); - calogContextStart(failCtx); - status = calogContextEval(failCtx, "report(1)", &result); - CHECK(status != calogOkE, "eval on a context whose interpreter failed to init returns an error, not a crash"); - calogValueFree(&result); - calogContextDestroy(failCtx); -} - - -static void testPureScript(void) { - CalogValueT result; - int32_t status; - - status = calogContextEval(luaCtx, "report(1 + 2 + 3)", &result); - CHECK(status == calogOkE && atomic_load(&reportedValue) == PURE_EXPECTED, "a second eval on the live interpreter ran"); - calogValueFree(&result); + calogContextClose(ctx); } static void testScriptError(void) { - CalogValueT result; - int32_t status; + CalogContextT *ctx; + int32_t before; - // A syntax error must come back as a failing status with the error in result - // (the single channel), and must not wedge the interpreter. - status = calogContextEval(luaCtx, "@@@ not valid lua @@@", &result); - CHECK(status != calogOkE && result.type == calogStringE, "script error surfaced through result, not a crash"); - calogValueFree(&result); + ctx = calogContextOpen(calog, &calogLuaEngine); + before = atomic_load(&errorCount); + atomic_store(&scriptDone, false); + calogContextEval(ctx, "@@@ not valid lua @@@"); + pumpUntilDone(); + + CHECK(atomic_load(&errorCount) == before + 1, "a fire-and-forget script error reached the error handler"); + CHECK(atomic_load(&errorCtxId) == calogContextId(ctx), "the error names the failing context"); + + calogContextClose(ctx); +} + + +static void testConcurrentContexts(void) { + CalogContextT *ctxs[3]; + struct timespec ts = { 0, 500000 }; + int32_t i; + + atomic_store(&bumpCount, 0); + for (i = 0; i < 3; i++) { + ctxs[i] = calogContextOpen(calog, &calogLuaEngine); + calogContextEval(ctxs[i], "bump(); bump(); bump()"); + } + for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 9; i++) { + calogPump(calog); + nanosleep(&ts, NULL); + } + CHECK(atomic_load(&bumpCount) == 9, "three concurrent contexts all dispatched to the host thread"); + for (i = 0; i < 3; i++) { + calogContextClose(ctxs[i]); + } +} + + +static void testContextLoad(void) { + CalogContextT *ctx; + struct timespec ts = { 0, 500000 }; + FILE *file; + int32_t i; + + // Registration makes the Lua engine (extension "lua") a load candidate. + calogRegisterEngine(calog, &calogLuaEngine); + + // A file whose extension matches a registered engine is found, loaded, and run. + file = fopen("calogLoadTest.lua", "wb"); + CHECK(file != NULL, "wrote a temporary .lua script"); + if (file != NULL) { + fputs("bump()", file); + fclose(file); + } + atomic_store(&bumpCount, 0); + ctx = calogContextLoad(calog, "calogLoadTest"); + CHECK(ctx != NULL, "calogContextLoad opened a context for the matching .lua file"); + for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 1; i++) { + calogPump(calog); + nanosleep(&ts, NULL); + } + CHECK(atomic_load(&bumpCount) == 1, "the loaded script ran on its new context"); + if (ctx != NULL) { + calogContextClose(ctx); + } + remove("calogLoadTest.lua"); + + // No file with a registered extension exists -> NULL. + ctx = calogContextLoad(calog, "calogNoSuchScript"); + CHECK(ctx == NULL, "calogContextLoad returns NULL when no file matches"); +} + + +static void testSingleThreadMultiRuntime(void) { + CalogT *calogB; + CalogContextT *ctxA; + CalogContextT *ctxB; + struct timespec ts = { 0, 500000 }; + int32_t i; + + // This one thread now hosts TWO runtimes. Each runs a script that calls a native + // registered under a different runtime; pumping each in turn must make calogCurrent() + // resolve to the runtime being pumped -- not whichever was created last. + calogB = calogCreate(); + CHECK(calogB != NULL, "created a second runtime on this same thread"); + calogRegister(calog, "checkRuntime", nativeCheckRuntime, calog); + calogRegister(calogB, "checkRuntime", nativeCheckRuntime, calogB); + + atomic_store(&matchCount, 0); + atomic_store(&mismatchCount, 0); + ctxA = calogContextOpen(calog, &calogLuaEngine); + ctxB = calogContextOpen(calogB, &calogLuaEngine); + calogContextEval(ctxA, "checkRuntime()"); + calogContextEval(ctxB, "checkRuntime()"); + + for (i = 0; i < PUMP_LIMIT && (atomic_load(&matchCount) + atomic_load(&mismatchCount)) < 2; i++) { + calogPump(calog); + calogPump(calogB); + nanosleep(&ts, NULL); + } + CHECK(atomic_load(&matchCount) == 2, "one thread pumped two runtimes; each native saw its own runtime"); + CHECK(atomic_load(&mismatchCount) == 0, "no native resolved to the wrong runtime while sharing a host thread"); + + calogContextClose(ctxA); + calogContextClose(ctxB); + calogDestroy(calogB); } int main(void) { - const char *exposeNames[3]; - CalogConfigT luaConfig; - - broker = calogCreate(); - if (broker == NULL) { - printf("broker create failed\n"); + calog = calogCreate(); + if (calog == NULL) { + printf("calog create failed\n"); return 1; } + calogSetErrorHandler(calog, onError, NULL); + calogRegister(calog, "report", nativeReport, NULL); + calogRegister(calog, "setCb", nativeSetCb, NULL); + calogRegister(calog, "done", nativeDone, NULL); + calogRegister(calog, "bump", nativeBump, NULL); + calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); - // Worker context hosts doubleIt; it has no interpreter (a pure native owner). - calogContextCreate(broker, NULL, NULL, &workerCtx); - - exposeNames[0] = "doubleIt"; - exposeNames[1] = "report"; - exposeNames[2] = "setCb"; - luaConfig.exposeNames = exposeNames; - luaConfig.exposeCount = 3; - calogContextCreate(broker, &calogLuaEngine, &luaConfig, &luaCtx); - - // setCb is owned by luaCtx, so register it once luaCtx exists (for its id), but - // before calogContextStart -- createInterpreter exposes the names on the thread. - calogRegister(broker, "doubleIt", nativeDoubleIt, NULL, calogContextId(workerCtx)); - calogRegister(broker, "report", nativeReport, NULL, 0); - calogRegister(broker, "setCb", nativeSetCb, NULL, calogContextId(luaCtx)); - - calogContextStart(workerCtx); - calogContextStart(luaCtx); - - testCrossContextFromScript(); - testPureScript(); + testHostAndInlineNatives(); + testCrossThreadCallback(); testScriptError(); - testFailedInterpreter(); - testCallbackAcrossContexts(); + testConcurrentContexts(); + testSingleThreadMultiRuntime(); + testContextLoad(); - calogDestroy(broker); + calogDestroy(calog); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); diff --git a/tests/testEngineMyBasic.c b/tests/testEngineMyBasic.c new file mode 100644 index 0000000..ea2d059 --- /dev/null +++ b/tests/testEngineMyBasic.c @@ -0,0 +1,114 @@ +// testEngineMyBasic.c -- my-basic on context threads under the actor model. Verifies a +// host native is serviced on the host thread, and that several my-basic contexts run +// concurrently without corruption -- my-basic keeps process-global state, so the engine +// serializes my-basic work with a lock; this is the test that would race without it. +// Built under ASan+UBSan (make test) and ThreadSanitizer (make tsanmb). + +#define _POSIX_C_SOURCE 200809L + +#include "calog.h" + +#include +#include +#include + +#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) + +#define PUMP_LIMIT 4000 +#define CONTEXT_COUNT 3 + +static CalogT *calog = NULL; +static _Atomic int32_t bumpCount = 0; +static _Atomic uint64_t bumpCtxId = 0xFFFFu; +static int32_t testsRun = 0; +static int32_t testsFailed = 0; + +static int32_t bump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static void checkImpl(bool condition, const char *message, const char *file, int32_t line); +static void testConcurrentContexts(void); +static void testHostNative(void); + + +static int32_t bump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + (void)userData; + atomic_store(&bumpCtxId, calogCurrentId()); + atomic_fetch_add(&bumpCount, 1); + calogValueNil(result); + return calogOkE; +} + + +static void checkImpl(bool condition, const char *message, const char *file, int32_t line) { + testsRun++; + if (!condition) { + testsFailed++; + printf("FAIL %s:%d %s\n", file, line, message); + } +} + + +static void testConcurrentContexts(void) { + CalogContextT *ctxs[CONTEXT_COUNT]; + struct timespec ts = { 0, 500000 }; + int32_t i; + + // The scripts allocate (list creation) as they run so the parallel execution paths + // churn my-basic's now-atomic allocation counter concurrently -- the case that + // raced before the vendored patch (see make tsanmb). + atomic_store(&bumpCount, 0); + for (i = 0; i < CONTEXT_COUNT; i++) { + ctxs[i] = calogContextOpen(calog, &calogMyBasicEngine); + calogContextEval(ctxs[i], "a = list(1, 2, 3)\nb = list(4, 5, 6)\nbump()"); + } + for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < CONTEXT_COUNT; i++) { + calogPump(calog); + nanosleep(&ts, NULL); + } + CHECK(atomic_load(&bumpCount) == CONTEXT_COUNT, "several concurrent my-basic contexts all reached the host native"); + for (i = 0; i < CONTEXT_COUNT; i++) { + calogContextClose(ctxs[i]); + } +} + + +static void testHostNative(void) { + CalogContextT *ctx; + struct timespec ts = { 0, 500000 }; + int32_t i; + + ctx = calogContextOpen(calog, &calogMyBasicEngine); + CHECK(ctx != NULL, "opened a my-basic context"); + atomic_store(&bumpCount, 0); + calogContextEval(ctx, "bump()"); + for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 1; i++) { + calogPump(calog); + nanosleep(&ts, NULL); + } + CHECK(atomic_load(&bumpCount) == 1, "the my-basic script's native call ran"); + CHECK(atomic_load(&bumpCtxId) == 0, "the native ran on the host thread (id 0)"); + calogContextClose(ctx); +} + + +int main(void) { + calog = calogCreate(); + if (calog == NULL) { + printf("calog create failed\n"); + return 1; + } + calogRegister(calog, "bump", bump, NULL); + + testHostNative(); + testConcurrentContexts(); + + calogDestroy(calog); + + printf("\n%d checks, %d failed\n", testsRun, testsFailed); + fflush(stdout); + if (testsFailed != 0) { + return 1; + } + return 0; +} diff --git a/tests/testEngineSquirrel.c b/tests/testEngineSquirrel.c index f1eb690..8a7c902 100644 --- a/tests/testEngineSquirrel.c +++ b/tests/testEngineSquirrel.c @@ -1,12 +1,9 @@ -// testEngineSquirrel.c -- a real Squirrel VM running on a CalogContextT thread. -// -// The third engine, validating that the CalogEngineT vtable + canonical CalogValueT make a -// new engine drop-in: the same actor core (calogContextCreate/calogContextEval) drives a -// Squirrel VM exactly as it drives Lua. The script calls a thread-agnostic native -// (report), a native owned by a different context (doubleIt, routed cross-thread -// by the exposed-native trampoline), and exercises string and array marshalling -// (reportStr, sumArr). Built under ASan+UBSan; the Squirrel core is linked in -// un-sanitized but its heap use is still tracked across the boundary. +// testEngineSquirrel.c -- Squirrel on a context thread, driven the host way: +// register natives, open a context, fire-and-forget a script, and calogPump on the +// host thread. Verifies host-thread dispatch, the inline escape hatch, the sec-10 +// cross-thread callback, the error handler, and concurrent contexts. + +#define _POSIX_C_SOURCE 200809L #include "calog.h" @@ -14,30 +11,35 @@ #include #include #include +#include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) -#define DOUBLE_EXPECTED 42 -#define SUM_EXPECTED 42 -#define REPORTSTR_CAP 64 +#define PUMP_LIMIT 4000 -static CalogT *broker = NULL; -static CalogContextT *squirrelCtx = NULL; -static CalogContextT *workerCtx = NULL; +static CalogT *calog = NULL; static _Atomic int64_t reportedValue = 0; -static _Atomic uint32_t reportedCtxId = 0; -static _Atomic uint32_t doubleItCtxId = 0; -static char reportedStr[REPORTSTR_CAP]; +static _Atomic uint64_t reportedCtxId = 0xFFFFu; +static _Atomic uint64_t inlineCtxId = 0xFFFFu; +static _Atomic int32_t bumpCount = 0; +static _Atomic bool scriptDone = false; +static _Atomic int32_t errorCount = 0; +static _Atomic uint64_t errorCtxId = 0; +static CalogFnT *_Atomic storedCb = NULL; static int32_t testsRun = 0; static int32_t testsFailed = 0; static void checkImpl(bool condition, const char *message, const char *file, int32_t line); -static int32_t nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static int32_t nativeReportStr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static int32_t nativeSumArr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); -static void testCrossContextFromScript(void); -static void testMarshalRoundTrip(void); +static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static void onError(uint64_t contextId, const char *message, void *userData); +static void pumpUntilDone(void); +static void testConcurrentContexts(void); +static void testCrossThreadCallback(void); +static void testHostAndInlineNatives(void); static void testScriptError(void); @@ -50,133 +52,187 @@ static void checkImpl(bool condition, const char *message, const char *file, int } -static int32_t nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { +static int32_t nativeBump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; (void)userData; - atomic_store(&doubleItCtxId, calogCurrentId()); - if (argCount != 1 || args[0].type != calogIntE) { - return calogFail(result, calogErrArgE, "doubleIt expects one integer"); - } - calogValueInt(result, args[0].as.i * 2); + atomic_fetch_add(&bumpCount, 1); + calogValueNil(result); + return calogOkE; +} + + +static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + (void)userData; + atomic_store(&scriptDone, true); + calogValueNil(result); return calogOkE; } static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)userData; + calogValueNil(result); if (argCount != 1 || args[0].type != calogIntE) { return calogFail(result, calogErrArgE, "report expects one integer"); } atomic_store(&reportedCtxId, calogCurrentId()); atomic_store(&reportedValue, args[0].as.i); + return calogOkE; +} + + +static int32_t nativeReportInline(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)args; + (void)argCount; + (void)userData; + atomic_store(&inlineCtxId, calogCurrentId()); calogValueNil(result); return calogOkE; } -static int32_t nativeReportStr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { +static int32_t nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)userData; - if (argCount != 1 || args[0].type != calogStringE) { - return calogFail(result, calogErrArgE, "reportStr expects one string"); - } - // Truncate defensively; the test strings are short. Synchronization with the - // reader is via the eval reply, which establishes happens-before. - snprintf(reportedStr, sizeof(reportedStr), "%s", args[0].as.s.bytes); calogValueNil(result); + if (argCount != 1 || args[0].type != calogFnE) { + return calogFail(result, calogErrArgE, "setCb expects one function"); + } + calogFnRetain(args[0].as.fn); + atomic_store(&storedCb, args[0].as.fn); return calogOkE; } -static int32_t nativeSumArr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { - CalogAggT *aggregate; - int64_t sum; - int64_t index; - +static void onError(uint64_t contextId, const char *message, void *userData) { + (void)message; (void)userData; - if (argCount != 1 || args[0].type != calogAggE) { - return calogFail(result, calogErrArgE, "sumArr expects one array"); - } - aggregate = args[0].as.agg; - sum = 0; - for (index = 0; index < aggregate->arrayCount; index++) { - if (aggregate->array[index].type != calogIntE) { - return calogFail(result, calogErrTypeE, "sumArr expects integer elements"); + atomic_store(&errorCtxId, contextId); + atomic_fetch_add(&errorCount, 1); +} + + +static void pumpUntilDone(void) { + struct timespec ts = { 0, 500000 }; + int errorsBefore; + int i; + + errorsBefore = atomic_load(&errorCount); + for (i = 0; i < PUMP_LIMIT; i++) { + calogPump(calog); + if (atomic_load(&scriptDone) || atomic_load(&errorCount) != errorsBefore) { + calogPump(calog); + return; } - sum += aggregate->array[index].as.i; + nanosleep(&ts, NULL); } - calogValueInt(result, sum); - return calogOkE; } -static void testCrossContextFromScript(void) { - CalogValueT result; - int32_t status; +static void testHostAndInlineNatives(void) { + CalogContextT *ctx; - status = calogContextEval(squirrelCtx, "report(doubleIt(21))", &result); - CHECK(status == calogOkE, "squirrel script with a cross-context call ran without error"); - CHECK(atomic_load(&reportedValue) == DOUBLE_EXPECTED, "cross-context doubleIt result flowed back into the script"); - CHECK(atomic_load(&doubleItCtxId) == calogContextId(workerCtx), "doubleIt executed on the worker context's thread"); - CHECK(atomic_load(&reportedCtxId) == calogContextId(squirrelCtx), "report executed on the Squirrel context's own thread"); - calogValueFree(&result); + ctx = calogContextOpen(calog, &calogSquirrelEngine); + CHECK(ctx != NULL, "opened a Squirrel context"); + + atomic_store(&scriptDone, false); + calogContextEval(ctx, "report(42); reportInline(1); done();"); + pumpUntilDone(); + + CHECK(atomic_load(&reportedValue) == 42, "host native received the argument"); + CHECK(atomic_load(&reportedCtxId) == 0, "default native ran on the host thread (id 0)"); + CHECK(atomic_load(&inlineCtxId) == calogContextId(ctx), "inline native ran on the script's own thread"); + + calogContextClose(ctx); } -static void testMarshalRoundTrip(void) { - CalogValueT result; - int32_t status; +static void testCrossThreadCallback(void) { + CalogContextT *ctx; + CalogFnT *callback; + CalogValueT arg; + CalogValueT result; + int32_t status; - status = calogContextEval(squirrelCtx, "reportStr(\"squirrel\")", &result); - CHECK(status == calogOkE && strcmp(reportedStr, "squirrel") == 0, "string marshalled from Squirrel into a native"); - calogValueFree(&result); + ctx = calogContextOpen(calog, &calogSquirrelEngine); + atomic_store(&scriptDone, false); + atomic_store(&storedCb, NULL); + calogContextEval(ctx, "setCb(function(x) { return x + 100; }); done();"); + pumpUntilDone(); - status = calogContextEval(squirrelCtx, "report(sumArr([10, 20, 12]))", &result); - CHECK(status == calogOkE && atomic_load(&reportedValue) == SUM_EXPECTED, "array marshalled from Squirrel into the hybrid aggregate"); + callback = atomic_load(&storedCb); + CHECK(callback != NULL, "a Squirrel closure was captured as a CalogFnT"); + + calogValueInt(&arg, 7); + status = calogFnInvoke(callback, &arg, 1, &result); + CHECK(status == calogOkE && result.type == calogIntE && result.as.i == 107, "cross-thread callable invoke routed to the owner"); calogValueFree(&result); + calogValueFree(&arg); + calogFnRelease(callback); + + calogContextClose(ctx); } static void testScriptError(void) { - CalogValueT result; - int32_t status; + CalogContextT *ctx; + int32_t before; - status = calogContextEval(squirrelCtx, "@@@ not valid squirrel @@@", &result); - CHECK(status != calogOkE && result.type == calogStringE, "script error surfaced through result, not a crash"); - calogValueFree(&result); + ctx = calogContextOpen(calog, &calogSquirrelEngine); + before = atomic_load(&errorCount); + atomic_store(&scriptDone, false); + calogContextEval(ctx, "@@@ not valid squirrel @@@"); + pumpUntilDone(); + + CHECK(atomic_load(&errorCount) == before + 1, "a fire-and-forget script error reached the error handler"); + CHECK(atomic_load(&errorCtxId) == calogContextId(ctx), "the error names the failing context"); + + calogContextClose(ctx); +} + + +static void testConcurrentContexts(void) { + CalogContextT *ctxs[3]; + struct timespec ts = { 0, 500000 }; + int32_t i; + + atomic_store(&bumpCount, 0); + for (i = 0; i < 3; i++) { + ctxs[i] = calogContextOpen(calog, &calogSquirrelEngine); + calogContextEval(ctxs[i], "bump(); bump(); bump();"); + } + for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 9; i++) { + calogPump(calog); + nanosleep(&ts, NULL); + } + CHECK(atomic_load(&bumpCount) == 9, "three concurrent contexts all dispatched to the host thread"); + for (i = 0; i < 3; i++) { + calogContextClose(ctxs[i]); + } } int main(void) { - const char *exposeNames[4]; - CalogConfigT squirrelConfig; - - broker = calogCreate(); - if (broker == NULL) { - printf("broker create failed\n"); + calog = calogCreate(); + if (calog == NULL) { + printf("calog create failed\n"); return 1; } + calogSetErrorHandler(calog, onError, NULL); + calogRegister(calog, "report", nativeReport, NULL); + calogRegister(calog, "setCb", nativeSetCb, NULL); + calogRegister(calog, "done", nativeDone, NULL); + calogRegister(calog, "bump", nativeBump, NULL); + calogRegisterInline(calog, "reportInline", nativeReportInline, NULL); - calogContextCreate(broker, NULL, NULL, &workerCtx); - calogRegister(broker, "doubleIt", nativeDoubleIt, NULL, calogContextId(workerCtx)); - calogRegister(broker, "report", nativeReport, NULL, 0); - calogRegister(broker, "reportStr", nativeReportStr, NULL, 0); - calogRegister(broker, "sumArr", nativeSumArr, NULL, 0); - - exposeNames[0] = "doubleIt"; - exposeNames[1] = "report"; - exposeNames[2] = "reportStr"; - exposeNames[3] = "sumArr"; - squirrelConfig.exposeNames = exposeNames; - squirrelConfig.exposeCount = 4; - calogContextCreate(broker, &calogSquirrelEngine, &squirrelConfig, &squirrelCtx); - - calogContextStart(workerCtx); - calogContextStart(squirrelCtx); - - testCrossContextFromScript(); - testMarshalRoundTrip(); + testHostAndInlineNatives(); + testCrossThreadCallback(); testScriptError(); + testConcurrentContexts(); - calogDestroy(broker); + calogDestroy(calog); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); diff --git a/tests/testJs.c b/tests/testJs.c index ed9c8b1..29f1958 100644 --- a/tests/testJs.c +++ b/tests/testJs.c @@ -178,9 +178,9 @@ int main(void) { printf("js context create failed\n"); return 1; } - calogRegister(broker, "record", nativeRecord, NULL, 0); - calogRegister(broker, "applyTo5", nativeApplyTo5, NULL, 0); - calogRegister(broker, "sumArr", nativeSumArr, NULL, 0); + calogRegister(broker, "record", nativeRecord, NULL); + calogRegister(broker, "applyTo5", nativeApplyTo5, NULL); + calogRegister(broker, "sumArr", nativeSumArr, NULL); calogJsExpose(jsctx, "record"); calogJsExpose(jsctx, "applyTo5"); calogJsExpose(jsctx, "sumArr"); diff --git a/tests/testLoad.c b/tests/testLoad.c new file mode 100644 index 0000000..b2c2b3b --- /dev/null +++ b/tests/testLoad.c @@ -0,0 +1,128 @@ +// testLoad.c -- calogContextLoad across all four engines. +// +// Links every engine (built with all CALOG_WITH_* flags, set by the Makefile), so +// calogRegisterBuiltinEngines auto-registers Lua, JS, Squirrel, and my-basic, and each +// language is loaded by file extension. Also the first exercise of my-basic under the +// actor/context-thread model. Verifies: each extension loads and runs its script; the +// first registered engine wins when several files share a base name; a base that names +// no file returns NULL. + +#define _POSIX_C_SOURCE 200809L + +#include "calog.h" + +#include +#include +#include +#include + +#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) + +#define PUMP_LIMIT 4000 // ~2s at 0.5ms/iter + +static CalogT *calog = NULL; +static _Atomic int64_t lastHit = 0; +static int32_t testsRun = 0; +static int32_t testsFailed = 0; + +static void checkImpl(bool condition, const char *message, const char *file, int32_t line); +static int32_t hit(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); +static int64_t loadAndRun(const char *base); +static void writeScript(const char *path, const char *content); + + +static void checkImpl(bool condition, const char *message, const char *file, int32_t line) { + testsRun++; + if (!condition) { + testsFailed++; + printf("FAIL %s:%d %s\n", file, line, message); + } +} + + +static int32_t hit(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { + (void)userData; + calogValueNil(result); + if (argCount >= 1 && args[0].type == calogIntE) { + atomic_store(&lastHit, args[0].as.i); + } + return calogOkE; +} + + +static void writeScript(const char *path, const char *content) { + FILE *file; + + file = fopen(path, "wb"); + if (file != NULL) { + fputs(content, file); + fclose(file); + } +} + + +// Load the script for base, pump until its hit() lands (or timeout), close, and return +// the reported value -- 0 if it never ran, -1 if no file matched at all. +static int64_t loadAndRun(const char *base) { + CalogContextT *context; + struct timespec ts = { 0, 500000 }; + int32_t i; + + atomic_store(&lastHit, 0); + context = calogContextLoad(calog, base); + if (context == NULL) { + return -1; + } + for (i = 0; i < PUMP_LIMIT && atomic_load(&lastHit) == 0; i++) { + calogPump(calog); + nanosleep(&ts, NULL); + } + calogContextClose(context); + return atomic_load(&lastHit); +} + + +int main(void) { + calog = calogCreate(); + if (calog == NULL) { + printf("calog create failed\n"); + return 1; + } + calogRegister(calog, "hit", hit, NULL); + calogRegisterBuiltinEngines(calog); // Lua, JS, Squirrel, my-basic (all linked) + + writeScript("clTest.lua", "hit(11)"); + CHECK(loadAndRun("clTest") == 11, "loaded and ran a .lua script by base name"); + remove("clTest.lua"); + + writeScript("clTest.js", "hit(12)"); + CHECK(loadAndRun("clTest") == 12, "loaded and ran a .js script by base name"); + remove("clTest.js"); + + writeScript("clTest.nut", "hit(13)"); + CHECK(loadAndRun("clTest") == 13, "loaded and ran a .nut Squirrel script by base name"); + remove("clTest.nut"); + + writeScript("clTest.bas", "hit(14)"); + CHECK(loadAndRun("clTest") == 14, "loaded and ran a .bas my-basic script by base name"); + remove("clTest.bas"); + + // First match wins: Lua is registered before my-basic, so .lua beats .bas. + writeScript("clPrio.lua", "hit(21)"); + writeScript("clPrio.bas", "hit(22)"); + CHECK(loadAndRun("clPrio") == 21, "first registered engine wins when several files share a base"); + remove("clPrio.lua"); + remove("clPrio.bas"); + + // No file with any registered extension exists -> NULL. + CHECK(calogContextLoad(calog, "clNoSuchBase") == NULL, "calogContextLoad returns NULL when nothing matches"); + + calogDestroy(calog); + + printf("\n%d checks, %d failed\n", testsRun, testsFailed); + fflush(stdout); + if (testsFailed != 0) { + return 1; + } + return 0; +} diff --git a/tests/testLua.c b/tests/testLua.c index 471a0ae..c2bfd3c 100644 --- a/tests/testLua.c +++ b/tests/testLua.c @@ -61,7 +61,7 @@ static int32_t nativeGetAdder(CalogValueT *args, int32_t argCount, CalogValueT * (void)args; (void)argCount; (void)userData; - status = calogFnCreate(&callable, nativeAdd, NULL, NULL, 0, 0); + status = calogFnCreate(&callable, broker, nativeAdd, NULL, NULL, 0); if (status != calogOkE) { return calogFail(result, status, "getAdder out of memory"); } @@ -204,10 +204,10 @@ int main(void) { printf("broker create failed\n"); return 1; } - calogRegister(broker, "add", nativeAdd, NULL, 0); - calogRegister(broker, "record", nativeRecord, NULL, 0); - calogRegister(broker, "makeList", nativeMakeList, NULL, 0); - calogRegister(broker, "getAdder", nativeGetAdder, NULL, 0); + calogRegister(broker, "add", nativeAdd, NULL); + calogRegister(broker, "record", nativeRecord, NULL); + calogRegister(broker, "makeList", nativeMakeList, NULL); + calogRegister(broker, "getAdder", nativeGetAdder, NULL); status = calogLuaCreate(&lua, broker, 1); if (status != calogOkE) { diff --git a/tests/testMyBasic.c b/tests/testMyBasic.c index 2b43bf2..3ad295a 100644 --- a/tests/testMyBasic.c +++ b/tests/testMyBasic.c @@ -249,8 +249,8 @@ static int32_t runProgram(const char *source) { if (status != calogOkE) { return status; } - calogRegister(broker, "callExport", nativeCallExport, basic, 0); - calogRegister(broker, "callEcho", nativeCallEcho, basic, 0); + calogRegister(broker, "callExport", nativeCallExport, basic); + calogRegister(broker, "callEcho", nativeCallEcho, basic); calogMyBasicExpose(basic, "add"); calogMyBasicExpose(basic, "record"); calogMyBasicExpose(basic, "makeList"); @@ -394,13 +394,13 @@ int main(void) { return 1; } - calogRegister(broker, "add", nativeAdd, NULL, 0); - calogRegister(broker, "record", nativeRecord, NULL, 0); - calogRegister(broker, "makeList", nativeMakeList, NULL, 0); - calogRegister(broker, "makeDict", nativeMakeDict, NULL, 0); - calogRegister(broker, "makeNested", nativeMakeNested, NULL, 0); - calogRegister(broker, "makeStr", nativeMakeStr, NULL, 0); - calogRegister(broker, "boom", nativeBoom, NULL, 0); + calogRegister(broker, "add", nativeAdd, NULL); + calogRegister(broker, "record", nativeRecord, NULL); + calogRegister(broker, "makeList", nativeMakeList, NULL); + calogRegister(broker, "makeDict", nativeMakeDict, NULL); + calogRegister(broker, "makeNested", nativeMakeNested, NULL); + calogRegister(broker, "makeStr", nativeMakeStr, NULL); + calogRegister(broker, "boom", nativeBoom, NULL); testNativeFromBasic(); testStringMarshal(); diff --git a/tests/testPolyglot.c b/tests/testPolyglot.c index a58974c..f46427d 100644 --- a/tests/testPolyglot.c +++ b/tests/testPolyglot.c @@ -127,8 +127,8 @@ int main(void) { printf("broker create failed\n"); return 1; } - calogRegister(broker, "add", nativeAdd, NULL, 0); - calogRegister(broker, "record", nativeRecord, NULL, 0); + calogRegister(broker, "add", nativeAdd, NULL); + calogRegister(broker, "record", nativeRecord, NULL); status = calogLuaCreate(&lua, broker, LUA_CTX_ID); if (status != calogOkE) { @@ -146,7 +146,7 @@ int main(void) { printf("lua export failed\n"); return 1; } - calogRegister(broker, "luaDouble", callableTrampoline, luaCallable, LUA_CTX_ID); + calogRegister(broker, "luaDouble", callableTrampoline, luaCallable); testSharedNative(); testBasicCallsLua(); diff --git a/tests/testSquirrel.c b/tests/testSquirrel.c index cb73a2c..69ce524 100644 --- a/tests/testSquirrel.c +++ b/tests/testSquirrel.c @@ -134,8 +134,8 @@ int main(void) { printf("squirrel context create failed\n"); return 1; } - calogRegister(broker, "record", nativeRecord, NULL, 0); - calogRegister(broker, "applyTo5", nativeApplyTo5, NULL, 0); + calogRegister(broker, "record", nativeRecord, NULL); + calogRegister(broker, "applyTo5", nativeApplyTo5, NULL); calogSquirrelExpose(sqctx, "record"); calogSquirrelExpose(sqctx, "applyTo5"); calogValueNil(&recorded); diff --git a/vendor/mybasic/myBasic.c b/vendor/mybasic/myBasic.c index b0f0d3c..658f07e 100644 --- a/vendor/mybasic/myBasic.c +++ b/vendor/mybasic/myBasic.c @@ -1328,7 +1328,11 @@ static void _resize_dynamic_buffer(_dynamic_buffer_t* buf, size_t es, size_t c); #define _MB_READ_MEM_TAG_SIZE(t) (*((mb_mem_tag_t*)((char*)(t) - _MB_MEM_TAG_SIZE))) #ifdef MB_ENABLE_ALLOC_STAT -static volatile size_t _mb_allocated = 0; +/* calog patch (see myBasic.c.orig): _Atomic instead of volatile so the global + * allocation counter is safe when independent interpreters run on separate threads; + * every += / -= / read below is then an atomic op. Lets calog run my-basic contexts in + * parallel with only lifecycle (mb_init/mb_dispose) serialized. */ +static _Atomic size_t _mb_allocated = 0; #else /* MB_ENABLE_ALLOC_STAT */ static const size_t _mb_allocated = (size_t)(~0); #endif /* MB_ENABLE_ALLOC_STAT */ diff --git a/vendor/mybasic/myBasic.c.orig b/vendor/mybasic/myBasic.c.orig new file mode 100644 index 0000000..b0f0d3c --- /dev/null +++ b/vendor/mybasic/myBasic.c.orig @@ -0,0 +1,19506 @@ +/* +** This source file is part of MY-BASIC +** +** For the latest info, see https://github.com/paladin-t/my_basic/ +** +** Copyright (C) 2011 - 2026 Tony Wang +** +** 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. +*/ + +#ifdef _MSC_VER +# ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +# endif /* _CRT_SECURE_NO_WARNINGS */ +#endif /* _MSC_VER */ + +#include "myBasic.h" +#if defined ARDUINO && !defined MB_CP_ARDUINO +# define MB_CP_ARDUINO +#endif /* ARDUINO && !MB_CP_ARDUINO */ +#ifdef MB_CP_ARDUINO +# ifndef MB_DISABLE_LOAD_FILE +# define MB_DISABLE_LOAD_FILE +# endif /* MB_DISABLE_LOAD_FILE */ +# ifndef MB_MANUAL_REAL_FORMATTING +# define MB_MANUAL_REAL_FORMATTING +# endif /* MB_MANUAL_REAL_FORMATTING */ +#endif /* MB_CP_ARDUINO */ +#ifdef MB_CP_VC +# include +# include +# include +#else /* MB_CP_VC */ +# include +#endif /* MB_CP_VC */ +#ifndef MB_CP_ARDUINO +# include +#endif /* MB_CP_ARDUINO */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef MB_CP_VC +# pragma warning(push) +# pragma warning(disable : 4127) +# pragma warning(disable : 4214) +# pragma warning(disable : 4305) +# pragma warning(disable : 4309) +# pragma warning(disable : 4805) +# pragma warning(disable : 4996) +#endif /* MB_CP_VC */ + +#ifdef MB_CP_CLANG +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-variable" +#endif /* MB_CP_CLANG */ + +#ifdef MB_CP_BORLANDC +# pragma warn -8004 +# pragma warn -8008 +# pragma warn -8012 +#endif /* MB_CP_BORLANDC */ + +#ifdef MB_COMPACT_MODE +# pragma pack(1) +#endif /* MB_COMPACT_MODE */ + +/* +** {======================================================== +** Data type declarations +*/ + +/** Macros */ + +/* Version information */ +#define MB_VER_MAJOR 1 +#define MB_VER_MINOR 2 +#define MB_VER_REVISION 2 +#define MB_VER_SUFFIX +#define MB_VERSION ((MB_VER_MAJOR * 0x01000000) + (MB_VER_MINOR * 0x00010000) + (MB_VER_REVISION)) +#define MB_MAKE_STRINGIZE(A) #A +#define MB_STRINGIZE(A) MB_MAKE_STRINGIZE(A) +#if MB_VER_REVISION == 0 +# define MB_VERSION_STRING MB_STRINGIZE(MB_VER_MAJOR.MB_VER_MINOR MB_VER_SUFFIX) +#else /* MB_VER_REVISION == 0 */ +# define MB_VERSION_STRING MB_STRINGIZE(MB_VER_MAJOR.MB_VER_MINOR.MB_VER_REVISION MB_VER_SUFFIX) +#endif /* MB_VER_REVISION == 0 */ + +/* Define as 1 to create hash table nodes lazily, 0 obligingly */ +#ifndef _LAZY_HASH_TABLE +# define _LAZY_HASH_TABLE 1 +#endif /* _LAZY_HASH_TABLE */ + +/* Define as 1 to treat warning as error, 0 just leave it */ +#ifndef _WARNING_AS_ERROR +# define _WARNING_AS_ERROR 0 +#endif /* _WARNING_AS_ERROR */ + +/* Define as 1 to automatically raise error during popping argument, 0 just return an error result */ +#ifndef _SIMPLE_ARG_ERROR +# define _SIMPLE_ARG_ERROR 0 +#endif /* _SIMPLE_ARG_ERROR */ + +/* Define as 1 to use a comma to PRINT a new line, 0 to use a semicolon */ +#ifndef _COMMA_AS_NEWLINE +# define _COMMA_AS_NEWLINE 0 +#endif /* _COMMA_AS_NEWLINE */ + +/* Define as 1 to enable multiline statement */ +#ifndef _MULTILINE_STATEMENT +# define _MULTILINE_STATEMENT 1 +#endif /* _MULTILINE_STATEMENT */ + +/* Hash table size */ +#ifndef _HT_ARRAY_SIZE_DEFAULT +# define _HT_ARRAY_SIZE_TINY 1 +# define _HT_ARRAY_SIZE_SMALL 193 +# define _HT_ARRAY_SIZE_MID 1543 +# define _HT_ARRAY_SIZE_BIG 12289 +# define _HT_ARRAY_SIZE_DEFAULT _HT_ARRAY_SIZE_SMALL +#endif /* _HT_ARRAY_SIZE_DEFAULT */ + +/* Max length of a single symbol */ +#ifndef _SINGLE_SYMBOL_MAX_LENGTH +# define _SINGLE_SYMBOL_MAX_LENGTH 128 +#endif /* _SINGLE_SYMBOL_MAX_LENGTH */ + +/* Buffer length of some string operations */ +#ifndef _INPUT_MAX_LENGTH +# define _INPUT_MAX_LENGTH 256 +#endif /* _INPUT_MAX_LENGTH */ +#ifndef _TEMP_FORMAT_MAX_LENGTH +# define _TEMP_FORMAT_MAX_LENGTH 32 +#endif /* _TEMP_FORMAT_MAX_LENGTH */ +#ifndef _LAMBDA_NAME_MAX_LENGTH +# define _LAMBDA_NAME_MAX_LENGTH 32 +#endif /* _LAMBDA_NAME_MAX_LENGTH */ + +/* Helper */ +#ifdef MB_COMPACT_MODE +# define _PACK1 : 1 +# define _PACK2 : 2 +# define _PACK8 : 8 +#else /* MB_COMPACT_MODE */ +# define _PACK1 +# define _PACK2 +# define _PACK8 +#endif /* MB_COMPACT_MODE */ + +#ifndef _UNALIGNED_ARG +# if defined MB_CP_VC && defined MB_OS_WIN64 +# ifdef MB_COMPACT_MODE +# define _UNALIGNED_ARG __unaligned +# else /* MB_COMPACT_MODE */ +# define _UNALIGNED_ARG +# endif /* MB_COMPACT_MODE */ +# else +# define _UNALIGNED_ARG +# endif +#endif /* _UNALIGNED_ARG */ + +#ifndef sgn +# define sgn(__v) ((__v) ? ((__v) > 0 ? 1 : -1) : 0) +#endif /* sgn */ + +#ifndef islower +# define islower(__c) ((__c) >= 'a' && (__c) <= 'z') +#endif /* islower */ +#ifndef toupper +# define toupper(__c) (islower(__c) ? ((__c) - 'a' + 'A') : (__c)) +#endif /* toupper */ + +#ifndef countof +# define countof(__a) (sizeof(__a) / sizeof(*(__a))) +#endif /* countof */ + +#ifndef _mb_check_exit +# define _mb_check_exit(__expr, __exit) do { if((__expr) != MB_FUNC_OK) goto __exit; } while(0) +#endif /* _mb_check_exit */ +#ifndef _mb_check_mark_exit +# define _mb_check_mark_exit(__expr, __result, __exit) do { __result = (__expr); if(__result != MB_FUNC_OK) goto __exit; } while(0) +#endif /* _mb_check_mark_exit */ + +/** Collections */ + +/* Collection functors */ +#define _OP_RESULT_NORMAL 0 +#define _OP_RESULT_DEL_NODE -1 + +typedef int (* _common_compare_t)(void*, void*); +typedef int (* _common_operation_t)(void*, void*); + +/* List */ +typedef _common_compare_t _ls_compare_t; +typedef _common_operation_t _ls_operation_t; + +typedef struct _ls_node_t { + void* data; + struct _ls_node_t* prev; + struct _ls_node_t* next; + void* extra; +} _ls_node_t; + +/* Dictionary */ +typedef unsigned (* _ht_hash_t)(void*, void*); +typedef _common_compare_t _ht_compare_t; +typedef _common_operation_t _ht_operation_t; + +typedef struct _ht_node_t { + _ls_operation_t free_extra; + _ht_compare_t compare; + _ht_hash_t hash; + unsigned array_size; + unsigned count; + _ls_node_t** array; +} _ht_node_t; + +/** Normal enum/struct/union/const, etc. */ + +#ifdef MB_ENABLE_FULL_ERROR +/* Error description text */ +MBCONST static const char* const _ERR_DESC[] = { + "No error", + /** Common */ + "Function already exists", + "Function does not exist", + "Not supported", + /** Parsing */ + "Failed to open file", + "Symbol too long", + "Invalid character", + "Invalid module", + "Duplicate IMPORT", + /** Running */ + "Empty program", + "Program too long", + "Syntax error", + "Out of memory", + "Overflow", + "Unexpected type", + "Invalid string", + "Integer expected", + "Number expected", + "String expected", + "Variable expected", + "Index out of bound", + "Cannot find with the specific index", + "Too many dimensions", + "Rank out of bound", + "Invalid identifier usage", + "Cannot assign to reserved word", + "Duplicate identifier", + "Incomplete structure", + "Label does not exist", + "No return point", + "Colon expected", + "Comma expected", + "Comma or semicolon expected", + "Open bracket expected", + "Close bracket expected", + "Too many nested", + "Failed to operation", + "Operator expected", + "Assign operator expected", + "THEN statement expected", + "ELSE statement expected", + "ENDIF statement expected", + "TO statement expected", + "NEXT statement expected", + "UNTIL statement expected", + "Loop variable expected", + "Jump label expected", + "Calculation error", + "Invalid expression", + "Divide by zero", + "Reached to wrong function", + "Cannot suspend in a routine", + "Cannot mix instructional and structured sub routines", + "Invalid routine", + "Routine expected", + "Duplicate routine", + "Invalid class", + "Class expected", + "Duplicate class", + "HASH and COMPARE must be provided together", + "Invalid lambda", + "Empty collection", + "List expected", + "Invalid iterator", + "Iterable expected", + "Collection expected", + "Collection or iterator expected", + "Referenced type expected", + /** Extended abort */ + "Extended abort" +}; + +mb_static_assert(countof(_ERR_DESC) == SE_COUNT); +#endif /* MB_ENABLE_FULL_ERROR */ + +/* Data type */ +typedef enum _data_e { + _DT_INVALID = -1, + _DT_NIL = 0, + _DT_UNKNOWN, + _DT_INT, + _DT_REAL, + _DT_STRING, + _DT_TYPE, + _DT_USERTYPE, +#ifdef MB_ENABLE_USERTYPE_REF + _DT_USERTYPE_REF, +#endif /* MB_ENABLE_USERTYPE_REF */ + _DT_FUNC, + _DT_VAR, + _DT_ARRAY, +#ifdef MB_ENABLE_COLLECTION_LIB + _DT_LIST, + _DT_LIST_IT, + _DT_DICT, + _DT_DICT_IT, +#endif /* MB_ENABLE_COLLECTION_LIB */ + _DT_LABEL, /* Label type, used for GOTO, GOSUB statement */ +#ifdef MB_ENABLE_CLASS + _DT_CLASS, /* Object instance */ +#endif /* MB_ENABLE_CLASS */ + _DT_ROUTINE, /* User defined sub routine in script */ +#ifdef MB_ENABLE_LAMBDA + _DT_OUTER_SCOPE, +#endif /* MB_ENABLE_LAMBDA */ + _DT_SEP, /* Separator */ +#ifdef MB_ENABLE_SOURCE_TRACE + _DT_PREV_IMPORT, + _DT_POST_IMPORT, +#endif /* MB_ENABLE_SOURCE_TRACE */ + _DT_EOS /* End of statement */ +} _data_e; + +#ifdef MB_ENABLE_COLLECTION_LIB +# define _HAS_REF_OBJ_LOCK +#endif /* MB_ENABLE_COLLECTION_LIB */ + +#ifdef _HAS_REF_OBJ_LOCK +typedef short _lock_t; +#endif /* _HAS_REF_OBJ_LOCK */ + +struct _ref_t; + +typedef void (* _unref_func_t)(struct _ref_t*, void*); + +#define _NONE_REF 1 + +#ifndef _ref_count_t +typedef unsigned _ref_count_t; +#endif /* _ref_count_t */ + +/* The reference structure should be always at the head of an object */ +typedef struct _ref_t { + _ref_count_t* count; + _ref_count_t* weak_count; + _unref_func_t on_unref; + _data_e type _PACK8; + struct mb_interpreter_t* s; +} _ref_t; + +typedef struct _gc_t { + _ht_node_t* table; + _ht_node_t* recursive_table; + _ht_node_t* collected_table; + _ht_node_t* valid_table; + unsigned char collecting; + bool_t disabled _PACK1; +} _gc_t; + +#ifdef MB_ENABLE_USERTYPE_REF +typedef struct _calculation_operator_info_t { + mb_meta_operator_t is; + mb_meta_operator_t add; + mb_meta_operator_t sub; + mb_meta_operator_t mul; + mb_meta_operator_t div; + mb_meta_operator_t neg; +} _calculation_operator_info_t; + +typedef struct _usertype_ref_t { + _ref_t ref; + void* usertype; + mb_dtor_func_t dtor; + mb_clone_func_t clone; + mb_hash_func_t hash; + mb_cmp_func_t cmp; + mb_fmt_func_t fmt; +#ifdef MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF + mb_alive_value_checker_t alive_checker; +#endif /* MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF */ + _calculation_operator_info_t* calc_operators; + mb_meta_func_t coll_func; + mb_meta_func_t generic_func; +} _usertype_ref_t; +#endif /* MB_ENABLE_USERTYPE_REF */ + +typedef struct _func_t { + char* name; + mb_func_t pointer; +} _func_t; + +#define _PATHING_NONE 0 +#define _PATHING_NORMAL 1 +#define _PATHING_UNKNOWN_FOR_NOT_FOUND 2 +#define _PATHING_UPVALUE 3 + +#define _PN(__b) ((!!(__b)) ? (_PATHING_NORMAL) : (_PATHING_NONE)) +#define _PU(__b) ((!!(__b)) ? (_PATHING_UNKNOWN_FOR_NOT_FOUND) : (_PATHING_NONE)) + +typedef struct _var_t { + char* name; + struct _object_t* data; +#ifdef MB_ENABLE_CLASS + unsigned char pathing _PACK2; + bool_t is_me _PACK1; +#endif /* MB_ENABLE_CLASS */ +} _var_t; + +typedef struct _array_t { +#ifdef MB_ENABLE_ARRAY_REF + _ref_t ref; +#endif /* MB_ENABLE_ARRAY_REF */ + char* name; + _data_e type; +#ifndef MB_SIMPLE_ARRAY + _data_e* types; +#endif /* MB_SIMPLE_ARRAY */ + void* raw; + unsigned count; + unsigned char dimension_count; + unsigned dimensions[MB_MAX_DIMENSION_COUNT]; +} _array_t; + +#ifdef MB_ENABLE_COLLECTION_LIB +typedef struct _array_helper_t { + struct mb_interpreter_t* s; + _array_t* array; + int index; +} _array_helper_t; +#endif /* MB_ENABLE_COLLECTION_LIB */ + +#ifdef MB_ENABLE_COLLECTION_LIB +typedef struct _list_t { + _ref_t ref; + _lock_t lock; + _ls_node_t* list; + _ls_node_t* cached_node; + int cached_index; + int_t count; + int_t* range_begin; +} _list_t; + +typedef struct _list_it_t { + _ref_t weak_ref; + _list_t* list; + bool_t locking _PACK1; + union { + _ls_node_t* node; + int_t ranging; + } curr; +} _list_it_t; + +typedef struct _dict_t { + _ref_t ref; + _lock_t lock; + _ht_node_t* dict; +} _dict_t; + +#define _INVALID_DICT_IT ((_ls_node_t*)(intptr_t)~0) + +typedef struct _dict_it_t { + _ref_t weak_ref; + _dict_t* dict; + bool_t locking _PACK1; + unsigned curr_bucket; + _ls_node_t* curr_node; +} _dict_it_t; + +typedef struct _keys_helper_t { + mb_value_t* keys; + int size; + int index; +} _keys_helper_t; +#endif /* MB_ENABLE_COLLECTION_LIB */ + +typedef struct _label_t { + char* name; + _ls_node_t* node; +} _label_t; + +#ifdef MB_ENABLE_CLASS +#define _META_LIST_MAX_DEPTH UINT_MAX + +#define _CLASS_ME "ME" + +#define _CLASS_HASH_FUNC "HASH" +#define _CLASS_COMPARE_FUNC "COMPARE" +#define _CLASS_TO_STRING_FUNC "TO_STRING" + +#define _CLASS_OVERRIDE_FMT "_%s" + +typedef struct _class_t { + _ref_t ref; + char* name; + struct _class_t* created_from; + _ls_node_t* meta_list; + struct _running_context_t* scope; + struct _routine_t* hash; + struct _routine_t* compare; + void* userdata; +} _class_t; +#endif /* MB_ENABLE_CLASS */ + +#ifdef MB_ENABLE_LAMBDA +typedef struct _running_context_ref_t { + _ref_t ref; + struct _running_context_ref_t* prev; + struct _running_context_t* scope; +} _running_context_ref_t; + +typedef struct _upvalue_scope_tuple_t { + struct mb_interpreter_t* s; +#ifdef MB_ENABLE_CLASS + _class_t* instance; +#endif /* MB_ENABLE_CLASS */ + struct _running_context_t* scope; + _running_context_ref_t* outer_scope; + struct _lambda_t* lambda; + _ht_node_t* filled; +} _upvalue_scope_tuple_t; + +typedef struct _lambda_t { + _ref_t ref; + struct _running_context_t* scope; + _ls_node_t* parameters; + _running_context_ref_t* outer_scope; + struct _running_context_t* overlapped; + _ht_node_t* upvalues; + _ls_node_t* entry; + _ls_node_t* end; +} _lambda_t; +#endif /* MB_ENABLE_LAMBDA */ + +typedef struct _routine_t { + union { + struct { + struct _running_context_t* scope; + _ls_node_t* parameters; + _ls_node_t* entry; + } basic; +#ifdef MB_ENABLE_LAMBDA + _lambda_t lambda; +#endif /* MB_ENABLE_LAMBDA */ + struct { + mb_routine_func_t entry; + } native; + } func; + char* name; +#ifdef MB_ENABLE_SOURCE_TRACE + char* source_file; +#endif /* MB_ENABLE_SOURCE_TRACE */ +#ifdef MB_ENABLE_CLASS + _class_t* instance; +#endif /* MB_ENABLE_CLASS */ + bool_t is_cloned _PACK1; + mb_routine_type_e type; +} _routine_t; + +#ifdef MB_ENABLE_SOURCE_TRACE +typedef struct _import_info_t { + char* source_file; +} _import_info_t; +#endif /* MB_ENABLE_SOURCE_TRACE */ + +typedef union _raw_u { mb_data_e e; char c; int_t i; real_t r; void* p; mb_val_bytes_t b; } _raw_u; + +typedef unsigned char _raw_t[sizeof(_raw_u)]; + +typedef struct _object_t { + _data_e type; + union { + int_t integer; + real_t float_point; + char* string; + mb_data_e type; + void* usertype; +#ifdef MB_ENABLE_USERTYPE_REF + _usertype_ref_t* usertype_ref; +#endif /* MB_ENABLE_USERTYPE_REF */ + _func_t* func; + _var_t* variable; + _array_t* array; +#ifdef MB_ENABLE_COLLECTION_LIB + _list_t* list; + _list_it_t* list_it; + _dict_t* dict; + _dict_it_t* dict_it; +#endif /* MB_ENABLE_COLLECTION_LIB */ + _label_t* label; +#ifdef MB_ENABLE_CLASS + _class_t* instance; +#endif /* MB_ENABLE_CLASS */ + _routine_t* routine; + char separator; +#ifdef MB_ENABLE_SOURCE_TRACE + _import_info_t* import_info; +#endif /* MB_ENABLE_SOURCE_TRACE */ + mb_val_bytes_t bytes; + void* pointer; + _raw_t raw; + } data; + bool_t is_ref _PACK1; +#ifdef MB_PREFER_SPEED + bool_t is_const _PACK1; +#endif /* MB_PREFER_SPEED */ +#ifdef MB_ENABLE_SOURCE_TRACE + int source_pos; + unsigned short source_row; + unsigned short source_col; +#else /* MB_ENABLE_SOURCE_TRACE */ + char source_pos _PACK1; +#endif /* MB_ENABLE_SOURCE_TRACE */ +} _object_t; + +#ifdef MB_ENABLE_MODULE +typedef struct _module_func_t { + char* module; + mb_func_t func; +} _module_func_t; +#endif /* MB_ENABLE_MODULE */ + +typedef struct _dynamic_buffer_t { + char bytes[_TEMP_FORMAT_MAX_LENGTH]; + union { + char* charp; +#if defined MB_CP_VC && defined MB_ENABLE_UNICODE + wchar_t* wcharp; +#endif /* MB_CP_VC && MB_ENABLE_UNICODE */ + } pointer; + size_t size; +} _dynamic_buffer_t; + +#define _MB_MEM_TAG_SIZE (sizeof(mb_mem_tag_t)) + +MBAPI size_t MB_SIZEOF_4BYTES = 4; +MBAPI size_t MB_SIZEOF_8BYTES = 8; +MBAPI size_t MB_SIZEOF_32BYTES = 32; +MBAPI size_t MB_SIZEOF_64BYTES = 64; +MBAPI size_t MB_SIZEOF_128BYTES = 128; +MBAPI size_t MB_SIZEOF_256BYTES = 256; +MBAPI size_t MB_SIZEOF_512BYTES = 512; +#ifdef MB_ENABLE_ALLOC_STAT +MBAPI size_t MB_SIZEOF_INT = _MB_MEM_TAG_SIZE + sizeof(int); +MBAPI size_t MB_SIZEOF_PTR = _MB_MEM_TAG_SIZE + sizeof(intptr_t); +MBAPI size_t MB_SIZEOF_LSN = _MB_MEM_TAG_SIZE + sizeof(_ls_node_t); +MBAPI size_t MB_SIZEOF_HTN = _MB_MEM_TAG_SIZE + sizeof(_ht_node_t); +MBAPI size_t MB_SIZEOF_HTA = _MB_MEM_TAG_SIZE + sizeof(_ht_node_t*) * _HT_ARRAY_SIZE_DEFAULT; +MBAPI size_t MB_SIZEOF_OBJ = _MB_MEM_TAG_SIZE + sizeof(_object_t); +#ifdef MB_ENABLE_USERTYPE_REF +MBAPI size_t MB_SIZEOF_UTR = _MB_MEM_TAG_SIZE + sizeof(_usertype_ref_t); +#endif /* MB_ENABLE_USERTYPE_REF */ +MBAPI size_t MB_SIZEOF_FUN = _MB_MEM_TAG_SIZE + sizeof(_func_t); +MBAPI size_t MB_SIZEOF_VAR = _MB_MEM_TAG_SIZE + sizeof(_var_t); +MBAPI size_t MB_SIZEOF_ARR = _MB_MEM_TAG_SIZE + sizeof(_array_t); +#ifdef MB_ENABLE_COLLECTION_LIB +MBAPI size_t MB_SIZEOF_LST = _MB_MEM_TAG_SIZE + sizeof(_list_t); +MBAPI size_t MB_SIZEOF_DCT = _MB_MEM_TAG_SIZE + sizeof(_dict_t); +#endif /* MB_ENABLE_COLLECTION_LIB */ +MBAPI size_t MB_SIZEOF_LBL = _MB_MEM_TAG_SIZE + sizeof(_label_t); +#ifdef MB_ENABLE_CLASS +MBAPI size_t MB_SIZEOF_CLS = _MB_MEM_TAG_SIZE + sizeof(_class_t); +#endif /* MB_ENABLE_CLASS */ +MBAPI size_t MB_SIZEOF_RTN = _MB_MEM_TAG_SIZE + sizeof(_routine_t); +#else /* MB_ENABLE_ALLOC_STAT */ +MBAPI size_t MB_SIZEOF_INT = sizeof(int); +MBAPI size_t MB_SIZEOF_PTR = sizeof(intptr_t); +MBAPI size_t MB_SIZEOF_LSN = sizeof(_ls_node_t); +MBAPI size_t MB_SIZEOF_HTN = sizeof(_ht_node_t); +MBAPI size_t MB_SIZEOF_HTA = sizeof(_ht_node_t*) * _HT_ARRAY_SIZE_DEFAULT; +MBAPI size_t MB_SIZEOF_OBJ = sizeof(_object_t); +#ifdef MB_ENABLE_USERTYPE_REF +MBAPI size_t MB_SIZEOF_UTR = sizeof(_usertype_ref_t); +#endif /* MB_ENABLE_USERTYPE_REF */ +MBAPI size_t MB_SIZEOF_FUN = sizeof(_func_t); +MBAPI size_t MB_SIZEOF_VAR = sizeof(_var_t); +MBAPI size_t MB_SIZEOF_ARR = sizeof(_array_t); +#ifdef MB_ENABLE_COLLECTION_LIB +MBAPI size_t MB_SIZEOF_LST = sizeof(_list_t); +MBAPI size_t MB_SIZEOF_DCT = sizeof(_dict_t); +#endif /* MB_ENABLE_COLLECTION_LIB */ +MBAPI size_t MB_SIZEOF_LBL = sizeof(_label_t); +#ifdef MB_ENABLE_CLASS +MBAPI size_t MB_SIZEOF_CLS = sizeof(_class_t); +#endif /* MB_ENABLE_CLASS */ +MBAPI size_t MB_SIZEOF_RTN = sizeof(_routine_t); +#endif /* MB_ENABLE_ALLOC_STAT */ + +#ifndef _CONST_PART1 +# ifdef MB_PREFER_SPEED +# define _CONST_PART1 false, false, +# else /* MB_PREFER_SPEED */ +# define _CONST_PART1 false, +# endif /* MB_PREFER_SPEED */ +#endif /* _CONST_PART1 */ +#ifndef _CONST_PART2 +# ifdef MB_ENABLE_SOURCE_TRACE +# define _CONST_PART2 0, 0, 0 +# else /* MB_ENABLE_SOURCE_TRACE */ +# define _CONST_PART2 0 +# endif /* MB_ENABLE_SOURCE_TRACE */ +#endif /* _CONST_PART2 */ +#ifndef _CONST_TAIL +# define _CONST_TAIL _CONST_PART1 _CONST_PART2 +#endif /* _CONST_TAIL */ + +MBCONST static const _object_t _OBJ_INT_UNIT = { _DT_INT, (int_t)1, _CONST_TAIL }; +MBCONST static const _object_t _OBJ_INT_ZERO = { _DT_INT, (int_t)0, _CONST_TAIL }; +#define _MAKE_NIL(__o) do { memset((__o), 0, sizeof(_object_t)); (__o)->type = _DT_NIL; } while(0) + +static _object_t* _OBJ_BOOL_TRUE = 0; +static _object_t* _OBJ_BOOL_FALSE = 0; + +#ifdef MB_ENABLE_CLASS +MBCONST static const _object_t _OBJ_UNKNOWN = { _DT_UNKNOWN, (int_t)0, _CONST_TAIL }; +MBCONST static const _ls_node_t _LS_NODE_UNKNOWN = { (void*)&_OBJ_UNKNOWN, 0, 0, 0 }; +#endif /* MB_ENABLE_CLASS */ + +#define _VAR_ARGS_STR "..." + +#define _IS_VAR_ARGS(__v) ((__v) == &_VAR_ARGS) + +#ifdef MB_ENABLE_CLASS +MBCONST static const _var_t _VAR_ARGS = { _VAR_ARGS_STR, 0, 0, 0 }; +#else /* MB_ENABLE_CLASS */ +MBCONST static const _var_t _VAR_ARGS = { _VAR_ARGS_STR, 0 }; +#endif /* MB_ENABLE_CLASS */ + +/* Parsing context */ +#define _CLASS_STATE_NONE 0 +#define _CLASS_STATE_PROC 1 + +MBCONST static const char _MULTI_LINE_COMMENT_PREFIX[] = "'["; +MBCONST static const char _MULTI_LINE_COMMENT_POSTFIX[] = "']"; + +MBCONST static const char _UNICODE_OPEN_QUOTE[] = { 0xe2, 0x80, 0x9c }; +MBCONST static const char _UNICODE_CLOSE_QUOTE[] = { 0xe2, 0x80, 0x9d }; + +typedef enum _parsing_state_e { + _PS_NORMAL, + _PS_STRING, + _PS_COMMENT, + _PS_MULTI_LINE_COMMENT +} _parsing_state_e; + +typedef enum _symbol_state_e { + _SS_IDENTIFIER, + _SS_OPERATOR +} _symbol_state_e; + +#define _ROUTINE_STATE_NONE 0 +#define _ROUTINE_STATE_DEF 1 +#define _ROUTINE_STATE_PARAMS 2 + +typedef struct _parsing_context_t { + _ls_node_t* imported; + char current_char; + char current_symbol[_SINGLE_SYMBOL_MAX_LENGTH + 1]; + int current_symbol_nonius; + int current_symbol_contains_accessor; + _object_t* last_symbol; + int multi_line_comment_count; + _parsing_state_e parsing_state; + _symbol_state_e symbol_state; +#ifdef MB_ENABLE_CLASS + unsigned short class_state; +#endif /* MB_ENABLE_CLASS */ + unsigned short routine_state; + unsigned short routine_params_state; + int parsing_pos; + unsigned short parsing_row; + unsigned short parsing_col; +} _parsing_context_t; + +/* Running context */ +#define _SCOPE_META_ROOT (1 << 0) +#define _SCOPE_META_REF (1 << 1) + +#define _INFINITY_CALC_DEPTH -1 + +typedef struct _running_context_t { + struct _running_context_t* prev; + unsigned meta; + _ht_node_t* var_dict; + struct _running_context_t* ref; + _var_t* next_loop_var; + mb_value_t intermediate_value; + bool_t donot_ref_intermediate_value; + int calc_depth; +#ifdef MB_ENABLE_LAMBDA + _ls_node_t* refered_lambdas; +#endif /* MB_ENABLE_LAMBDA */ +} _running_context_t; + +/* Expression processing utilities */ +typedef struct _tuple3_t { + void* e1; + void* e2; + void* e3; +} _tuple3_t; + +/* Interpreter tag */ +#define _JMP_NIL 0x00 +#define _JMP_INS 0x01 +#define _JMP_STR 0x02 + +#define _NO_EAT_COMMA 2 + +typedef struct mb_interpreter_t { + /** Fundamental */ +#ifdef MB_ENABLE_FORK + struct mb_interpreter_t* forked_from; + _running_context_t* forked_context; + _ls_node_t* all_forked; +#endif /* MB_ENABLE_FORK */ + bool_t valid _PACK1; + void* userdata; + _ls_node_t* ast; + /** Memory management */ + _gc_t gc; + _ls_node_t* edge_destroy_objects; + _ls_node_t* lazy_destroy_objects; + /** Scripting interface and module */ + _ht_node_t* local_func_dict; + _ht_node_t* global_func_dict; +#ifdef MB_ENABLE_MODULE + _ht_node_t* module_func_dict; + char* with_module; + _ls_node_t* using_modules; +#endif /* MB_ENABLE_MODULE */ + /** Parsing and interpreting */ + char* source_file; + _parsing_context_t* parsing_context; + _running_context_t* running_context; + int run_count; + bool_t has_run _PACK1; + _ls_node_t* var_args; +#ifdef MB_ENABLE_USERTYPE_REF + _object_t* usertype_ref_ahead; +#endif /* MB_ENABLE_USERTYPE_REF */ + unsigned char jump_set; +#ifdef MB_ENABLE_CLASS + _class_t* last_instance; + bool_t calling _PACK1; +#endif /* MB_ENABLE_CLASS */ + _routine_t* last_routine; + _ls_node_t* sub_stack; + _ls_node_t* suspent_point; + int schedule_suspend_tag; + int_t no_eat_comma_mark; + _ls_node_t* skip_to_eoi; + _ls_node_t* in_neg_expr; +#ifdef MB_ENABLE_STACK_TRACE + _ls_node_t* stack_frames; +#endif /* MB_ENABLE_STACK_TRACE */ +#if _MULTILINE_STATEMENT + _ls_node_t* multiline_enabled; +#endif /* _MULTILINE_STATEMENT */ + /** Error handling */ + bool_t handled_error _PACK1; + mb_error_e last_error; + char* last_error_file; + int last_error_pos; + unsigned short last_error_row; + unsigned short last_error_col; + /** Handlers */ + mb_alive_checker_t alive_check_handler; + mb_debug_stepped_handler_t debug_prev_stepped_handler; + mb_debug_stepped_handler_t debug_post_stepped_handler; + mb_error_handler_t error_handler; + mb_print_func_t printer; + mb_input_func_t inputer; + mb_import_handler_t import_handler; +} mb_interpreter_t; + +/* Operations */ +MBCONST static const char _PRECEDE_TABLE[20][20] = { /* Operator priority table */ + /* + - * / MOD ^ ( ) = > < >= <= == <> AND OR NOT NEG IS */ + { '>', '>', '<', '<', '<', '<', '<', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>' }, /* + */ + { '>', '>', '<', '<', '<', '<', '<', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>' }, /* - */ + { '>', '>', '>', '>', '>', '<', '<', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>' }, /* * */ + { '>', '>', '>', '>', '>', '<', '<', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>' }, /* / */ + { '>', '>', '<', '<', '>', '<', '<', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>' }, /* MOD */ + { '>', '>', '>', '>', '>', '>', '<', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>' }, /* ^ */ + { '<', '<', '<', '<', '<', '<', '<', '=', ' ', '<', '<', '<', '<', '<', '<', '<', '<', '<', '<', '<' }, /* ( */ + { '>', '>', '>', '>', '>', '>', ' ', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>', '>' }, /* ) */ + { '<', '<', '<', '<', '<', '<', '<', ' ', '=', '<', '<', '<', '<', '<', '<', '<', '<', '<', '<', '<' }, /* = */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', ' ', ' ', ' ', ' ', ' ', ' ', '>', '>', '>', '>', '>' }, /* > */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', ' ', ' ', ' ', ' ', ' ', ' ', '>', '>', '>', '>', '>' }, /* < */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', ' ', ' ', ' ', ' ', ' ', ' ', '>', '>', '>', '>', '>' }, /* >= */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', ' ', ' ', ' ', ' ', ' ', ' ', '>', '>', '>', '>', '>' }, /* <= */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', ' ', ' ', ' ', ' ', ' ', ' ', '>', '>', '>', '>', '>' }, /* == */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', ' ', ' ', ' ', ' ', ' ', ' ', '>', '>', '>', '>', '>' }, /* <> */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', '<', '<', '<', '<', '<', '<', '>', '>', '<', '>', '>' }, /* AND */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', '<', '<', '<', '<', '<', '<', '>', '>', '<', '>', '>' }, /* OR */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', '<', '<', '<', '<', '<', '<', '>', '>', '>', '>', '>' }, /* NOT */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', '<', '<', '<', '<', '<', '<', '<', '<', '<', '=', '<' }, /* NEG */ + { '<', '<', '<', '<', '<', '<', '<', '>', '>', '<', '<', '<', '<', '<', '<', '>', '>', '<', '>', '>' } /* IS */ +}; + +static _object_t* _exp_assign = 0; + +#define _copy_bytes(__l, __r) do { memcpy((__l), (__r), sizeof(mb_val_bytes_t)); } while(0) + +#define _set_real_with_hex(__r, __i) \ + do { \ + if(sizeof(__r) == sizeof(unsigned char)) { \ + union { unsigned char i; real_t r; } __u; \ + __u.i = __i; \ + __r = __u.r; \ + } else if(sizeof(__r) == sizeof(unsigned short)) { \ + union { unsigned short i; real_t r; } __u; \ + __u.i = __i; \ + __r = __u.r; \ + } else if(sizeof(__r) == sizeof(unsigned)) { \ + union { unsigned i; real_t r; } __u; \ + __u.i = __i; \ + __r = __u.r; \ + } else if(sizeof(__r) == sizeof(unsigned long)) { \ + union { unsigned long i; real_t r; } __u; \ + __u.i = __i; \ + __r = __u.r; \ + } else if(sizeof(__r) == sizeof(unsigned long long)) { \ + union { unsigned long long i; real_t r; } __u; \ + __u.i = __i; \ + __r = __u.r; \ + } else { \ + mb_assert(0 && "Invalid real number precision."); \ + } \ + } while(0) + +#if MB_CONVERT_TO_INT_LEVEL == MB_CONVERT_TO_INT_LEVEL_NONE +# define _convert_to_int_if_posible(__o) do { ((void)(__o)); } while(0) +#else /* MB_CONVERT_TO_INT_LEVEL == MB_CONVERT_TO_INT_LEVEL_NONE */ +# define _convert_to_int_if_posible(__o) \ + do { \ + if((__o)->type == _DT_REAL && (real_t)(int_t)(__o)->data.float_point == (__o)->data.float_point) { \ + (__o)->type = _DT_INT; \ + (__o)->data.integer = (int_t)(__o)->data.float_point; \ + } \ + } while(0) +#endif /* MB_CONVERT_TO_INT_LEVEL == MB_CONVERT_TO_INT_LEVEL_NONE */ + +#define _instruct_head(__tuple) \ + _object_t opndv1; \ + _object_t opndv2; \ + _tuple3_t* tpptr = (_tuple3_t*)(*__tuple); \ + _object_t* opnd1 = (_object_t*)(tpptr->e1); \ + _object_t* opnd2 = (_object_t*)(tpptr->e2); \ + _object_t* val = (_object_t*)(tpptr->e3); +#define _instruct_common(__tuple) \ + _instruct_head(__tuple) \ + opndv1.type = (opnd1->type == _DT_INT || (opnd1->type == _DT_VAR && opnd1->data.variable->data->type == _DT_INT)) ? \ + _DT_INT : _DT_REAL; \ + opndv1.data = opnd1->type == _DT_VAR ? opnd1->data.variable->data->data : opnd1->data; \ + opndv2.type = (opnd2->type == _DT_INT || (opnd2->type == _DT_VAR && opnd2->data.variable->data->type == _DT_INT)) ? \ + _DT_INT : _DT_REAL; \ + opndv2.data = opnd2->type == _DT_VAR ? opnd2->data.variable->data->data : opnd2->data; +#define _instruct_fun_num_num(__optr, __tuple) \ + do { \ + _instruct_common(__tuple) \ + if(opndv1.type == _DT_INT && opndv2.type == _DT_INT) { \ + val->type = _DT_REAL; \ + val->data.float_point = (real_t)__optr((real_t)opndv1.data.integer, (real_t)opndv2.data.integer); \ + } else { \ + val->type = _DT_REAL; \ + val->data.float_point = (real_t)__optr( \ + opndv1.type == _DT_INT ? opndv1.data.integer : opndv1.data.float_point, \ + opndv2.type == _DT_INT ? opndv2.data.integer : opndv2.data.float_point); \ + } \ + _convert_to_int_if_posible(val); \ + } while(0) +#define _instruct_bool_op_bool(__optr, __tuple) \ + do { \ + _instruct_common(__tuple) \ + if(opndv1.type == _DT_NIL) { opndv1.type = _DT_INT; opndv1.data.integer = 0; } \ + else if(opndv1.type != _DT_INT && opndv1.type != _DT_REAL) { opndv1.type = _DT_INT; opndv1.data.integer = 1; } \ + if(opndv2.type == _DT_NIL) { opndv2.type = _DT_INT; opndv2.data.integer = 0; } \ + else if(opndv2.type != _DT_INT && opndv2.type != _DT_REAL) { opndv2.type = _DT_INT; opndv2.data.integer = 1; } \ + if(opndv1.type == _DT_INT && opndv2.type == _DT_INT) { \ + if((real_t)(opndv1.data.integer __optr opndv2.data.integer) == ((real_t)opndv1.data.integer __optr (real_t)opndv2.data.integer)) { \ + val->type = _DT_INT; \ + val->data.integer = opndv1.data.integer __optr opndv2.data.integer; \ + } else { \ + val->type = _DT_REAL; \ + val->data.float_point = (real_t)((real_t)opndv1.data.integer __optr (real_t)opndv2.data.integer); \ + } \ + } else { \ + val->type = _DT_REAL; \ + val->data.float_point = (real_t) \ + ((opndv1.type == _DT_INT ? opndv1.data.integer : opndv1.data.float_point) __optr \ + (opndv2.type == _DT_INT ? opndv2.data.integer : opndv2.data.float_point)); \ + } \ + _convert_to_int_if_posible(val); \ + } while(0) +#define _instruct_int_op_int(__optr, __tuple) \ + do { \ + _instruct_common(__tuple) \ + if(opndv1.type == _DT_INT && opndv2.type == _DT_INT) { \ + val->type = _DT_INT; \ + val->data.integer = opndv1.data.integer __optr opndv2.data.integer; \ + } else { \ + val->type = _DT_INT; \ + val->data.integer = \ + ((opndv1.type == _DT_INT ? opndv1.data.integer : (int_t)(opndv1.data.float_point)) __optr \ + (opndv2.type == _DT_INT ? opndv2.data.integer : (int_t)(opndv2.data.float_point))); \ + } \ + } while(0) +#define _instruct_num_op_num(__optr, __tuple) \ + do { \ + _instruct_common(__tuple) \ + if(opndv1.type == _DT_INT && opndv2.type == _DT_INT) { \ + if((real_t)(opndv1.data.integer __optr opndv2.data.integer) == ((real_t)opndv1.data.integer __optr (real_t)opndv2.data.integer)) { \ + val->type = _DT_INT; \ + val->data.integer = opndv1.data.integer __optr opndv2.data.integer; \ + } else { \ + val->type = _DT_REAL; \ + val->data.float_point = (real_t)((real_t)opndv1.data.integer __optr (real_t)opndv2.data.integer); \ + } \ + } else { \ + val->type = _DT_REAL; \ + val->data.float_point = (real_t) \ + ((opndv1.type == _DT_INT ? opndv1.data.integer : opndv1.data.float_point) __optr \ + (opndv2.type == _DT_INT ? opndv2.data.integer : opndv2.data.float_point)); \ + } \ + _convert_to_int_if_posible(val); \ + } while(0) +#define _instruct_obj_op_obj(__optr, __tuple) \ + do { \ + _instruct_head(__tuple); \ + opndv1.type = opnd1->type == _DT_VAR ? opnd1->data.variable->data->type : opnd1->type; \ + opndv1.data = opnd1->type == _DT_VAR ? opnd1->data.variable->data->data : opnd1->data; \ + opndv2.type = opnd2->type == _DT_VAR ? opnd2->data.variable->data->type : opnd2->type; \ + opndv2.data = opnd2->type == _DT_VAR ? opnd2->data.variable->data->data : opnd2->data; \ + val->type = _DT_INT; \ + if(opndv1.type == opndv2.type) { \ + val->data.integer = (int_t)(mb_memcmp(&opndv1.data, &opndv2.data, sizeof(_raw_t)) __optr 0); \ + } else { \ + val->data.integer = (int_t)(opndv1.type __optr opndv2.type); \ + } \ + } while(0) +#define _instruct_connect_strings(__tuple) \ + do { \ + char* _str1 = 0; \ + char* _str2 = 0; \ + size_t _len1 = 0; \ + size_t _len2 = 0; \ + _tuple3_t* tpptr = (_tuple3_t*)(*__tuple); \ + _object_t* opnd1 = (_object_t*)(tpptr->e1); \ + _object_t* opnd2 = (_object_t*)(tpptr->e2); \ + _object_t* val = (_object_t*)(tpptr->e3); \ + val->type = _DT_STRING; \ + if(val->data.string) { \ + safe_free(val->data.string); \ + } \ + _str1 = _extract_string(opnd1); \ + _str2 = _extract_string(opnd2); \ + _len1 = mb_strlen(_str1); \ + _len2 = mb_strlen(_str2); \ + val->data.string = (char*)mb_malloc(_len1 + _len2 + 1); \ + memcpy(val->data.string, _str1, _len1); \ + memcpy(val->data.string + _len1, _str2, _len2); \ + val->data.string[_len1 + _len2] = '\0'; \ + } while(0) +#define _instruct_compare_strings(__optr, __tuple) \ + do { \ + char* _str1 = 0; \ + char* _str2 = 0; \ + _tuple3_t* tpptr = (_tuple3_t*)(*__tuple); \ + _object_t* opnd1 = (_object_t*)(tpptr->e1); \ + _object_t* opnd2 = (_object_t*)(tpptr->e2); \ + _object_t* val = (_object_t*)(tpptr->e3); \ + val->type = _DT_INT; \ + _str1 = _extract_string(opnd1); \ + _str2 = _extract_string(opnd2); \ + val->data.integer = strcmp(_str1, _str2) __optr 0; \ + } while(0) +#ifdef MB_ENABLE_USERTYPE_REF +# if !defined _instruct_obj_meta_obj +# define _instruct_obj_meta_obj(__s, __tuple, __optr, __result, __exit) \ + do { \ + _tuple3_t* tpptr = (_tuple3_t*)(*__tuple); \ + _object_t* opnd1 = (_object_t*)(tpptr->e1); \ + _object_t* opnd2 = (_object_t*)(tpptr->e2); \ + _object_t* retval = (_object_t*)(tpptr->e3); \ + if(opnd1->type == _DT_VAR) opnd1 = opnd1->data.variable->data; \ + if(opnd2->type == _DT_VAR) opnd2 = opnd2->data.variable->data; \ + { \ + mb_value_t vfst, vscd; \ + mb_value_t ret; \ + mb_make_nil(vfst); \ + mb_make_nil(vscd); \ + mb_make_nil(ret); \ + if(opnd1->type == _DT_USERTYPE_REF && opnd1->data.usertype_ref->calc_operators && opnd1->data.usertype_ref->calc_operators->__optr) { \ + _internal_object_to_public_value(opnd1, &vfst); \ + _internal_object_to_public_value(opnd2, &vscd); \ + __result = opnd1->data.usertype_ref->calc_operators->__optr((__s), (__tuple), &vfst, &vscd, &ret); \ + _public_value_to_internal_object(&ret, retval); \ + goto __exit; \ + } else if(opnd2->type == _DT_USERTYPE_REF && opnd2->data.usertype_ref->calc_operators && opnd2->data.usertype_ref->calc_operators->__optr) { \ + _internal_object_to_public_value(opnd1, &vfst); \ + _internal_object_to_public_value(opnd2, &vscd); \ + __result = opnd2->data.usertype_ref->calc_operators->__optr((__s), (__tuple), &vfst, &vscd, &ret); \ + _public_value_to_internal_object(&ret, retval); \ + goto __exit; \ + } \ + } \ + } while(0) +# endif /* _instruct_obj_meta_obj */ +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifndef _instruct_obj_meta_obj +# define _instruct_obj_meta_obj(__s, __tuple, __optr, __result, __exit) do { ((void)(__s)); ((void)(__tuple)); ((void)(__result)); } while(0) +#endif /* _instruct_obj_meta_obj */ +#define _proc_div_by_zero(__s, __tuple, __cast, __exit, __result, __kind) \ + do { \ + _instruct_common(__tuple) \ + if((opndv2.type == _DT_INT && opndv2.data.integer == 0) || (opndv2.type == _DT_REAL && (__cast)opndv2.data.float_point == 0)) { \ + if((opndv1.type == _DT_INT && opndv1.data.integer == 0) || (opndv1.type == _DT_REAL && opndv1.data.float_point == 0)) { \ + val->type = _DT_REAL; \ + _set_real_with_hex(val->data.float_point, MB_FNAN); \ + } else { \ + val->type = _DT_REAL; \ + _set_real_with_hex(val->data.float_point, MB_FINF); \ + } \ + _handle_error_on_obj((__s), __kind, (__s)->source_file, ((__tuple) && *(__tuple)) ? ((_object_t*)(((_tuple3_t*)(*(__tuple)))->e1)) : 0, MB_FUNC_WARNING, __exit, __result); \ + } \ + } while(0) + +#define _set_tuple3_result(__l, __r) \ + do { \ + _object_t* val = (_object_t*)(((_tuple3_t*)(*(__l)))->e3); \ + val->type = _DT_INT; \ + val->data.integer = __r; \ + } while(0) + +#define _math_calculate_fun_real(__s, __l, __a, __f, __exit, __result) \ + do { \ + switch((__a).type) { \ + case MB_DT_INT: \ + (__a).value.float_point = (real_t)__f((real_t)(__a).value.integer); \ + (__a).type = MB_DT_REAL; \ + break; \ + case MB_DT_REAL: \ + (__a).value.float_point = (real_t)__f((__a).value.float_point); \ + break; \ + default: \ + _handle_error_on_obj(__s, SE_RN_NUMBER_EXPECTED, (__s)->source_file, ((__l) && *(__l)) ? ((_object_t*)(((_tuple3_t*)(*(__l)))->e1)) : 0, MB_FUNC_WARNING, __exit, __result); \ + break; \ + } \ + mb_convert_to_int_if_posible(__a); \ + } while(0) + +#define _using_jump_set_of_instructional(__s, __obj, __exit, __result) \ + do { \ + if((__s)->jump_set & (~_JMP_INS)) { \ + _handle_error_on_obj(__s, SE_RN_CANNOT_MIX_INSTRUCTIONAL_AND_STRUCTURED, (__s)->source_file, DON(__obj), MB_FUNC_ERR, __exit, __result); \ + } else { \ + (__s)->jump_set |= _JMP_INS; \ + } \ + } while(0) +#define _using_jump_set_of_structured(__s, __obj, __exit, __result) \ + do { \ + if((__s)->jump_set & (~_JMP_STR)) { \ + _handle_error_on_obj(__s, SE_RN_CANNOT_MIX_INSTRUCTIONAL_AND_STRUCTURED, (__s)->source_file, DON(__obj), MB_FUNC_ERR, __exit, __result); \ + } else { \ + (__s)->jump_set |= _JMP_STR; \ + } \ + } while(0) + +/* ========================================================} */ + +/* +** {======================================================== +** Private function declarations +*/ + +/** List operations */ + +static int _ls_cmp_data(void* node, void* info); +static int _ls_cmp_extra(void* node, void* info); +static int _ls_cmp_extra_object(void* node, void* info); +static int _ls_cmp_extra_string(void* node, void* info); +#ifdef MB_ENABLE_MODULE +static int _ls_cmp_module_func(void* node, void* info); +#endif /* MB_ENABLE_MODULE */ + +static _ls_node_t* _ls_create_node(void* data); +static _ls_node_t* _ls_create(void); +static _ls_node_t* _ls_find(_ls_node_t* list, void* data, _ls_compare_t cmp, int* idx); +static _ls_node_t* _ls_back(_ls_node_t* node); +static _ls_node_t* _ls_pushback(_ls_node_t* list, void* data); +static void* _ls_popback(_ls_node_t* list); +static _ls_node_t* _ls_front(_ls_node_t* node); +static void* _ls_popfront(_ls_node_t* list); +static _ls_node_t* _ls_insert_at(_ls_node_t* list, int index, void* data); +static unsigned _ls_remove(_ls_node_t* list, _ls_node_t* node, _ls_operation_t op); +static unsigned _ls_try_remove(_ls_node_t* list, void* info, _ls_compare_t cmp, _ls_operation_t op); +static unsigned _ls_foreach(_ls_node_t* list, _ls_operation_t op); +static _ls_node_t* _ls_sort(_ls_node_t* _UNALIGNED_ARG * list, _ls_compare_t cmp); +static unsigned _ls_count(_ls_node_t* list); +static bool_t _ls_empty(_ls_node_t* list); +static void _ls_clear(_ls_node_t* list); +static void _ls_destroy(_ls_node_t* list); +static int _ls_free_extra(void* data, void* extra); +#define _LS_FOREACH(L, O, P, E) \ + do { \ + _ls_node_t* __lst = L; \ + int __opresult = _OP_RESULT_NORMAL; \ + _ls_node_t* __tmp = 0; \ + mb_assert(L); \ + __lst = __lst->next; \ + while(__lst) { \ + if(P != 0) { \ + P(__lst->data, __lst->extra, E); \ + } \ + if(O != 0) { \ + __opresult = O(__lst->data, __lst->extra); \ + } \ + __tmp = __lst; \ + __lst = __lst->next; \ + if(_OP_RESULT_DEL_NODE == __opresult) { \ + __tmp->prev->next = __lst; \ + if(__lst) { \ + __lst->prev = __tmp->prev; \ + } \ + safe_free(__tmp); \ + (L)->data = (char*)(L)->data - 1; \ + } \ + } \ + } while(0) + +/** Dictionary operations */ + +static unsigned _ht_hash_object(void* ht, void* d); +static unsigned _ht_hash_string(void* ht, void* d); +static unsigned _ht_hash_intptr(void* ht, void* d); +static unsigned _ht_hash_ref(void* ht, void* d); + +static int _ht_cmp_object(void* d1, void* d2); +static int _ht_cmp_string(void* d1, void* d2); +static int _ht_cmp_intptr(void* d1, void* d2); +static int _ht_cmp_ref(void* d1, void* d2); + +static _ht_node_t* _ht_create(unsigned size, _ht_compare_t cmp, _ht_hash_t hs, _ls_operation_t freeextra); +static _ls_node_t* _ht_find(_ht_node_t* ht, void* key); +static unsigned _ht_set_or_insert(_ht_node_t* ht, void* key, void* value); +static unsigned _ht_remove(_ht_node_t* ht, void* key, _ls_compare_t cmp); +static unsigned _ht_foreach(_ht_node_t* ht, _ht_operation_t op); +static unsigned _ht_count(_ht_node_t* ht); +static void _ht_clear(_ht_node_t* ht); +static void _ht_destroy(_ht_node_t* ht); +static int _ht_remove_existing(void* data, void* extra, _ht_node_t* ht); +#define _HT_FOREACH(H, O, P, E) \ + do { \ + _ls_node_t* __bucket = 0; \ + unsigned __ul = 0; \ + if((H)->array) { \ + for(__ul = 0; __ul < (H)->array_size; ++__ul) { \ + __bucket = (H)->array[__ul]; \ + if(__bucket) { \ + _LS_FOREACH(__bucket, O, P, E); \ + } \ + } \ + } \ + } while(0) + +/** Memory manipulations */ + +static void _init_dynamic_buffer(_dynamic_buffer_t* buf); +static void _dispose_dynamic_buffer(_dynamic_buffer_t* buf); +static size_t _countof_dynamic_buffer(_dynamic_buffer_t* buf, size_t es); +static void _resize_dynamic_buffer(_dynamic_buffer_t* buf, size_t es, size_t c); + +#define _INIT_BUF(b) do { _init_dynamic_buffer(&(b)); } while(0) +#define _DISPOSE_BUF(b) do { _dispose_dynamic_buffer(&(b)); } while(0) +#define _CHARS_OF_BUF(b) (_countof_dynamic_buffer((&b), sizeof(char))) +#define _RESIZE_CHAR_BUF(b, c) do { _resize_dynamic_buffer(&(b), sizeof(char), (c)); } while(0) +#define _HEAP_CHAR_BUF(b) (((b).pointer.charp != (b).bytes) ? ((b).pointer.charp) : (mb_memdup((b).pointer.charp, (unsigned)(b).size))) +#define _CHAR_BUF_PTR(b) ((b).pointer.charp) +#if defined MB_CP_VC && defined MB_ENABLE_UNICODE +#define _WCHARS_OF_BUF(b) (_countof_dynamic_buffer((&b), sizeof(wchar_t))) +#define _RESIZE_WCHAR_BUF(b, c) do { _resize_dynamic_buffer(&(b), sizeof(wchar_t), (c)); } while(0) +#define _WCHAR_BUF_PTR(b) ((b).pointer.wcharp) +#endif /* MB_CP_VC && MB_ENABLE_UNICODE */ + +#define _MB_CHECK_MEM_TAG_SIZE(y, s) ((y)(mb_mem_tag_t)(s) == (s)) +#define _MB_WRITE_MEM_TAG_SIZE(t, s) (*((mb_mem_tag_t*)((char*)(t) - _MB_MEM_TAG_SIZE)) = (mb_mem_tag_t)(s)) +#define _MB_READ_MEM_TAG_SIZE(t) (*((mb_mem_tag_t*)((char*)(t) - _MB_MEM_TAG_SIZE))) + +#ifdef MB_ENABLE_ALLOC_STAT +static volatile size_t _mb_allocated = 0; +#else /* MB_ENABLE_ALLOC_STAT */ +static const size_t _mb_allocated = (size_t)(~0); +#endif /* MB_ENABLE_ALLOC_STAT */ + +static mb_string_measure_func_t _mb_strlen_func = 0; +static mb_memory_allocate_func_t _mb_allocate_func = 0; +static mb_memory_free_func_t _mb_free_func = 0; + +static void* mb_malloc(size_t s); +static void mb_free(void* p); + +static int mb_memcmp(void* l, void* r, size_t s); +static size_t mb_memtest(void* p, size_t s); + +static size_t mb_strlen(const char* p); +static char* mb_strdup(const char* p, size_t s); +static char* mb_strupr(char* s); + +#define safe_free(__p) do { if(__p) { mb_free(__p); __p = 0; } else { mb_assert(0 && "Memory already released."); } } while(0) + +static bool_t mb_is_little_endian(void); + +/** Unicode handling */ + +#if defined MB_CP_VC && defined MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING +static int mb_bytes_to_wchar(const char* sz, wchar_t** out, size_t size); +static int mb_bytes_to_wchar_ansi(const char* sz, wchar_t** out, size_t size); +static int mb_wchar_to_bytes(const wchar_t* sz, char** out, size_t size); +#endif /* MB_CP_VC && MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING */ + +static int mb_uu_getbom(const char** ch); +#ifdef MB_ENABLE_UNICODE +static int mb_uu_ischar(const char* ch); +static int mb_uu_strlen(const char* ch); +static int mb_uu_substr(const char* ch, int begin, int count, char** o); +#endif /* MB_ENABLE_UNICODE */ + +/** Expression processing */ + +#ifndef _ONCALC /* Uprootable stub */ +# define _ONCALC(__s, __tuple, __optr, __result, __exit) do { ((void)(__s)); ((void)(__tuple)); ((void)(__result)); } while(0) +#endif /* _ONCALC */ +#ifndef _ONNEG /* Uprootable stub */ +# define _ONNEG(__s, __l, __arg, __result, __exit) do { ((void)(__s)); ((void)(__l)); ((void)(__arg)); ((void)(__result)); } while(0) +#endif /* _ONNEG */ + +#ifndef _ONCOND /* Uprootable stub */ +# define _ONCOND(__s, __o, __v) do { ((void)(__s)); ((void)(__o)); ((void)(__v)); } while(0) +#endif /* _ONCOND */ + +static bool_t _is_operator(mb_func_t op); +static bool_t _is_flow(mb_func_t op); +static bool_t _is_unary(mb_func_t op); +static bool_t _is_binary(mb_func_t op); +static char _get_priority(mb_func_t op1, mb_func_t op2); +static int _get_priority_index(mb_func_t op); +static _object_t* _operate_operand(mb_interpreter_t* s, _object_t* optr, _object_t* opnd1, _object_t* opnd2, int* status); +static bool_t _is_expression_terminal(mb_interpreter_t* s, _object_t* obj); +static bool_t _is_unexpected_calc_type(mb_interpreter_t* s, _object_t* obj); +static bool_t _is_referenced_calc_type(mb_interpreter_t* s, _object_t* obj); +static int _calc_expression(mb_interpreter_t* s, _ls_node_t** l, _object_t** val); + +#ifndef _PREVCALL /* Uprootable stub */ +# define _PREVCALL(__s, __l, __r) do { ((void)(__s)); ((void)(__l)); ((void)(__r)); } while(0) +#endif /* _PREVCALL */ +#ifndef _POSTCALL /* Uprootable stub */ +# define _POSTCALL(__s, __l, __r) do { ((void)(__s)); ((void)(__l)); ((void)(__r)); } while(0) +#endif /* _POSTCALL */ + +static _ls_node_t* _push_var_args(mb_interpreter_t* s); +static void _pop_var_args(mb_interpreter_t* s, _ls_node_t* last_var_args); +static int _pop_arg(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, unsigned* ia, _routine_t* r, mb_pop_routine_arg_func_t pop_arg, _ls_node_t* args, mb_value_t* arg); +static int _proc_args(mb_interpreter_t* s, _ls_node_t** l, _running_context_t* running, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg, bool_t proc_ref, _ls_node_t* args); +static int _eval_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg); +static int _eval_script_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg); +#ifdef MB_ENABLE_LAMBDA +static int _eval_lambda_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg); +#endif /* MB_ENABLE_LAMBDA */ +static int _eval_native_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg); +static int _has_routine_lex_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r); +static int _pop_routine_lex_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r, mb_value_t* val); +static int _has_routine_fun_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r); +static int _pop_routine_fun_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r, mb_value_t* val); +static bool_t _is_print_terminal(mb_interpreter_t* s, _object_t* obj); +static mb_meta_status_e _try_overridden(mb_interpreter_t* s, void** l, mb_value_t* d, const char* f, mb_meta_func_e t); + +/** Handlers */ + +#if _WARNING_AS_ERROR +# define _handle_error_now(__s, __err, __f, __result) \ + do { \ + _set_current_error((__s), (__err), (__f)); \ + if((__s)->error_handler) { \ + if((__s)->handled_error) \ + break; \ + (__s)->handled_error = true; \ + ((__s)->error_handler)((__s), (__s)->last_error, (char*)mb_get_error_desc((__s)->last_error), \ + (__s)->last_error_file, \ + (__s)->parsing_context && !(__s)->run_count ? (__s)->parsing_context->parsing_pos : (__s)->last_error_pos, \ + (__s)->parsing_context && !(__s)->run_count ? (__s)->parsing_context->parsing_row : (__s)->last_error_row, \ + (__s)->parsing_context && !(__s)->run_count ? (__s)->parsing_context->parsing_col : (__s)->last_error_col, \ + (__result)); \ + } \ + } while(0) +# define _handle_error_at_pos(__s, __err, __f, __pos, __row, __col, __ret, __exit, __result) \ + do { \ + if(_set_current_error((__s), (__err), (__f))) { \ + _set_error_pos((__s), (__pos), (__row), (__col)); \ + __result = (__ret); \ + } \ + goto __exit; \ + } while(0) +#else /* _WARNING_AS_ERROR */ +# define _handle_error_now(__s, __err, __f, __result) \ + do { \ + _set_current_error((__s), (__err), (__f)); \ + if((__s)->error_handler) { \ + if((__s)->handled_error) \ + break; \ + (__s)->handled_error = true; \ + ((__s)->error_handler)((__s), (__s)->last_error, (char*)mb_get_error_desc((__s)->last_error), \ + (__s)->last_error_file, \ + (__s)->parsing_context && !(__s)->run_count ? (__s)->parsing_context->parsing_pos : (__s)->last_error_pos, \ + (__s)->parsing_context && !(__s)->run_count ? (__s)->parsing_context->parsing_row : (__s)->last_error_row, \ + (__s)->parsing_context && !(__s)->run_count ? (__s)->parsing_context->parsing_col : (__s)->last_error_col, \ + (__result)); \ + } \ + if((__result) == MB_FUNC_WARNING) { \ + (__s)->last_error = SE_NO_ERR; \ + (__s)->last_error_file = 0; \ + (__s)->handled_error = false; \ + } \ + } while(0) +# define _handle_error_at_pos(__s, __err, __f, __pos, __row, __col, __ret, __exit, __result) \ + do { \ + if(_set_current_error((__s), (__err), (__f))) { \ + _set_error_pos((__s), (__pos), (__row), (__col)); \ + if((__ret) != MB_FUNC_WARNING) { \ + __result = (__ret); \ + } \ + } \ + goto __exit; \ + } while(0) +#endif /* _WARNING_AS_ERROR */ +#ifdef MB_ENABLE_SOURCE_TRACE +# define _handle_error_on_obj(__s, __err, __f, __obj, __ret, __exit, __result) \ + do { \ + if(__obj) { \ + _handle_error_at_pos((__s), (__err), (__f), (__obj)->source_pos, (__obj)->source_row, (__obj)->source_col, (__ret), __exit, (__result)); \ + } else { \ + _handle_error_at_pos((__s), (__err), (__f), 0, 0, 0, (__ret), __exit, (__result)); \ + } \ + } while(0) +#else /* MB_ENABLE_SOURCE_TRACE */ +# define _handle_error_on_obj(__s, __err, __f, __obj, __ret, __exit, __result) \ + do { ((void)(__obj)); _handle_error_at_pos((__s), (__err), (__f), 0, 0, 0, (__ret), __exit, (__result)); } while(0) +#endif /* MB_ENABLE_SOURCE_TRACE */ + +#define _OUTTER_SCOPE(__s) ((__s)->prev ? (__s)->prev : (__s)) + +static bool_t _set_current_error(mb_interpreter_t* s, mb_error_e err, char* f); + +static mb_print_func_t _get_printer(mb_interpreter_t* s); +static mb_input_func_t _get_inputer(mb_interpreter_t* s); + +static int _standard_printer(mb_interpreter_t* s, const char* fmt, ...); + +static void _print_string(mb_interpreter_t* s, _object_t* obj); + +/** Parsing helpers */ + +static char* _load_file(mb_interpreter_t* s, const char* f, const char* prefix, bool_t importing); +static void _end_of_file(_parsing_context_t* context); + +#define _ZERO_CHAR '\0' +#define _NEWLINE_CHAR '\n' +#define _RETURN_CHAR '\r' +#define _STRING_POSTFIX_CHAR '$' +#define _DUMMY_ASSIGN_CHAR "#" +#define _INVALID_CLASS_CHAR 0x18 +#define _INVALID_ROUTINE_CHAR 0x1B + +#define _REMARK_STR "REM" + +static bool_t _is_blank_char(char c); +static bool_t _is_eof_char(char c); +static bool_t _is_newline_char(char c); +static bool_t _is_separator_char(char c); +static bool_t _is_bracket_char(char c); +static bool_t _is_quotation_char(char c); +static bool_t _is_comment_char(char c); +static bool_t _is_accessor_char(char c); +static bool_t _is_numeric_char(char c); +static bool_t _is_identifier_char(char c); +static bool_t _is_operator_char(char c); +static bool_t _is_exponential_char(char c); +static bool_t _is_using_at_char(char c); +static bool_t _is_exponent_prefix(char* s, int begin, int end); + +static int _append_char_to_symbol(mb_interpreter_t* s, char c); +#ifdef MB_ENABLE_UNICODE_ID +static int _append_uu_char_to_symbol(mb_interpreter_t* s, const char* str, int n); +#endif /* MB_ENABLE_UNICODE_ID */ +static int _cut_symbol(mb_interpreter_t* s, int pos, unsigned short row, unsigned short col); +static int _append_symbol(mb_interpreter_t* s, char* sym, bool_t* delsym, int pos, unsigned short row, unsigned short col); +static int _create_symbol(mb_interpreter_t* s, _ls_node_t* l, char* sym, _object_t** obj, _ls_node_t*** asgn, bool_t* delsym); +static _data_e _get_symbol_type(mb_interpreter_t* s, char* sym, _raw_t* value); +static int _parse_char(mb_interpreter_t* s, const char* str, int n, int pos, unsigned short row, unsigned short col); +static void _set_error_pos(mb_interpreter_t* s, int pos, unsigned short row, unsigned short col); +static char* _prev_import(mb_interpreter_t* s, char* lf, int* pos, unsigned short* row, unsigned short* col); +static char* _post_import(mb_interpreter_t* s, char* lf, int* pos, unsigned short* row, unsigned short* col); + +/** Object processors */ + +#define DON(__o) ((__o) ? ((_object_t*)((__o)->data)) : 0) +#define DON2(__a) (((__a) && *(__a)) ? (_object_t*)((*((_ls_node_t**)(__a)))->data) : 0) +#define TON(__t) (((__t) && *(__t)) ? (_object_t*)(((_tuple3_t*)(*(__t)))->e1) : 0) + +#define _IS_EOS(__o) (__o && ((_object_t*)(__o))->type == _DT_EOS) +#define _IS_SEP(__o, __c) (((_object_t*)(__o))->type == _DT_SEP && ((_object_t*)(__o))->data.separator == __c) +#define _IS_FUNC(__o, __f) (((_object_t*)(__o))->type == _DT_FUNC && ((_object_t*)(__o))->data.func->pointer == __f) +#define _IS_UNARY_FUNC(__o) (((_object_t*)(__o))->type == _DT_FUNC && _is_unary(((_object_t*)(__o))->data.func->pointer)) +#define _IS_VAR(__o) ((__o) && ((_object_t*)(__o))->type == _DT_VAR) +#ifdef MB_ENABLE_COLLECTION_LIB +# define _IS_LIST(__o) ((__o) && ((_object_t*)(__o))->type == _DT_LIST) +# define _IS_DICT(__o) ((__o) && ((_object_t*)(__o))->type == _DT_DICT) +# define _IS_COLL(__o) (_IS_LIST(__o) || _IS_DICT(__o)) +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS +# define _IS_CLASS(__o) ((__o) && ((_object_t*)(__o))->type == _DT_CLASS) +# define _GET_CLASS(__o) ((!__o) ? 0 : (_IS_CLASS(__o) ? (__o) : (_IS_VAR(__o) && _IS_CLASS((__o)->data.variable->data) ? (__o)->data.variable->data : 0))) +# define _IS_ME(__v) (!!(__v)->is_me) +#else /* MB_ENABLE_CLASS */ +# define _IS_ME(__v) false +#endif /* MB_ENABLE_CLASS */ +#define _IS_ROUTINE(__o) ((__o) && ((_object_t*)(__o))->type == _DT_ROUTINE) +#define _GET_ROUTINE(__o) ((!__o) ? 0 : (_IS_ROUTINE(__o) ? (__o) : (_IS_VAR(__o) && _IS_ROUTINE((__o)->data.variable->data) ? (__o)->data.variable->data : 0))) + +#ifdef MB_ENABLE_USERTYPE_REF +# define _REF_USERTYPE_REF(__o) \ + case _DT_USERTYPE_REF: \ + _ref(&(__o)->data.usertype_ref->ref, (__o)->data.usertype_ref); \ + break; +# define _UNREF_USERTYPE_REF(__o) \ + case _DT_USERTYPE_REF: \ + _unref(&(__o)->data.usertype_ref->ref, (__o)->data.usertype_ref); \ + break; +# define _ADDGC_USERTYPE_REF(__o, __g) \ + case _DT_USERTYPE_REF: \ + _gc_add(&(__o)->data.usertype_ref->ref, (__o)->data.usertype_ref, (__g)); \ + break; +#else /* MB_ENABLE_USERTYPE_REF */ +# define _REF_USERTYPE_REF(__o) { (void)(__o); } +# define _UNREF_USERTYPE_REF(__o) { (void)(__o); } +# define _ADDGC_USERTYPE_REF(__o, __g) { (void)(__o); (void)(__g); } +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_ARRAY_REF +# define _REF_ARRAY(__o) \ + case _DT_ARRAY: \ + if(!(__o)->is_ref) \ + _ref(&(__o)->data.array->ref, (__o)->data.array); \ + break; +# define _UNREF_ARRAY(__o) \ + case _DT_ARRAY: \ + if(!(__o)->is_ref) \ + _unref(&(__o)->data.array->ref, (__o)->data.array); \ + break; +# define _ADDGC_ARRAY(__o, __g) \ + case _DT_ARRAY: \ + if(!(__o)->is_ref) \ + _gc_add(&(__o)->data.array->ref, (__o)->data.array, (__g)); \ + break; +#else /* MB_ENABLE_ARRAY_REF */ +# define _REF_ARRAY(__o) case _DT_ARRAY: { (void)(__o); } break; +# define _UNREF_ARRAY(__o) case _DT_ARRAY: { (void)(__o); } break; +# define _ADDGC_ARRAY(__o, __g) case _DT_ARRAY: { (void)(__o); (void)(__g); } break; +# define _DESTROY_ARRAY(__o) \ + case _DT_ARRAY: \ + if(!(__o)->is_ref) \ + _destroy_array((__o)->data.array); \ + break; +#endif /* MB_ENABLE_ARRAY_REF */ +#ifdef MB_ENABLE_COLLECTION_LIB +# define _REF_COLL(__o) \ + case _DT_LIST: \ + _ref(&(__o)->data.list->ref, (__o)->data.list); \ + break; \ + case _DT_DICT: \ + _ref(&(__o)->data.dict->ref, (__o)->data.dict); \ + break; +# define _UNREF_COLL(__o) \ + case _DT_LIST: \ + _unref(&(__o)->data.list->ref, (__o)->data.list); \ + break; \ + case _DT_DICT: \ + _unref(&(__o)->data.dict->ref, (__o)->data.dict); \ + break; +# define _ADDGC_COLL(__o, __g) \ + case _DT_LIST: \ + _gc_add(&(__o)->data.list->ref, (__o)->data.list, (__g)); \ + break; \ + case _DT_DICT: \ + _gc_add(&(__o)->data.dict->ref, (__o)->data.dict, (__g)); \ + break; +# define _UNREF_COLL_IT(__o) \ + case _DT_LIST_IT: \ + _destroy_list_it((__o)->data.list_it); \ + break; \ + case _DT_DICT_IT: \ + _destroy_dict_it((__o)->data.dict_it); \ + break; +# define _ADDGC_COLL_IT(__o, __g) \ + case _DT_LIST_IT: \ + _destroy_list_it((__o)->data.list_it); \ + break; \ + case _DT_DICT_IT: \ + _destroy_dict_it((__o)->data.dict_it); \ + break; +#else /* MB_ENABLE_COLLECTION_LIB */ +# define _REF_COLL(__o) { (void)(__o); } +# define _UNREF_COLL(__o) { (void)(__o); } +# define _ADDGC_COLL(__o, __g) { (void)(__o); (void)(__g); } +# define _UNREF_COLL_IT(__o) { (void)(__o); } +# define _ADDGC_COLL_IT(__o, __g) { (void)(__o); (void)(__g); } +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS +# define _REF_CLASS(__o) \ + case _DT_CLASS: \ + _ref(&(__o)->data.instance->ref, (__o)->data.instance); \ + break; +# define _UNREF_CLASS(__o) \ + case _DT_CLASS: \ + if(!(__o)->is_ref) \ + _unref(&(__o)->data.instance->ref, (__o)->data.instance); \ + break; +# define _ADDGC_CLASS(__o, __g) \ + case _DT_CLASS: \ + if(!(__o)->is_ref) \ + _gc_add(&(__o)->data.instance->ref, (__o)->data.instance, (__g)); \ + break; +#else /* MB_ENABLE_CLASS */ +# define _REF_CLASS(__o) { (void)(__o); } +# define _UNREF_CLASS(__o) { (void)(__o); } +# define _ADDGC_CLASS(__o, __g) { (void)(__o); (void)(__g); } +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_LAMBDA +# define _REF_ROUTINE(__o) \ + case _DT_ROUTINE: \ + if(!(__o)->is_ref && (__o)->data.routine->type == MB_RT_LAMBDA) \ + _ref(&(__o)->data.routine->func.lambda.ref, (__o)->data.routine); \ + break; +# define _UNREF_ROUTINE(__o) \ + case _DT_ROUTINE: \ + if(!(__o)->is_ref && (__o)->data.routine->type == MB_RT_LAMBDA) \ + _unref(&(__o)->data.routine->func.lambda.ref, (__o)->data.routine); \ + else if(!(__o)->is_ref && (__o)->data.routine->type != MB_RT_LAMBDA) \ + _destroy_routine(0, (__o)->data.routine); \ + break; +# define _ADDGC_ROUTINE(__o, __g, __r) \ + case _DT_ROUTINE: \ + if(!(__o)->is_ref && (__o)->data.routine->type == MB_RT_LAMBDA) \ + _gc_add(&(__o)->data.routine->func.lambda.ref, (__o)->data.routine, (__g)); \ + else if((__r) && !(__o)->is_ref && (__o)->data.routine->type != MB_RT_LAMBDA) \ + _dispose_object(__o); \ + break; +# define _COLL_ROUTINE(__o) \ + do { \ + if((__o)->type == _DT_ROUTINE && (__o)->data.routine->type != MB_RT_LAMBDA) \ + (__o)->is_ref = true; \ + } while(0) +#else /* MB_ENABLE_LAMBDA */ +# define _REF_ROUTINE(__o) case _DT_ROUTINE: { (void)(__o); } break; +# define _UNREF_ROUTINE(__o) case _DT_ROUTINE: { (void)(__o); } break; +# define _ADDGC_ROUTINE(__o, __g, __r) \ + case _DT_ROUTINE: \ + ((void)(__g)); \ + ((void)(__r)); \ + _dispose_object(__o); \ + break; +# define _COLL_ROUTINE(__o) \ + do { \ + if((__o)->type == _DT_ROUTINE) \ + (__o)->is_ref = true; \ + } while(0) +#endif /* MB_ENABLE_LAMBDA */ +#define _ADDGC_STRING(__o) \ + case _DT_STRING: \ + _dispose_object(__o); \ + break; +#define _REF(__o) \ + switch((__o)->type) { \ + _REF_USERTYPE_REF(__o) \ + _REF_ARRAY(__o) \ + _REF_COLL(__o) \ + _REF_CLASS(__o) \ + _REF_ROUTINE(__o) \ + default: break; \ + } +#define _UNREF(__o) \ + switch((__o)->type) { \ + _UNREF_USERTYPE_REF(__o) \ + _UNREF_ARRAY(__o) \ + _UNREF_COLL(__o) \ + _UNREF_CLASS(__o) \ + _UNREF_ROUTINE(__o) \ + default: break; \ + } +#define _ADDGC(__o, __g, __r) \ + if(!(__o)->data.pointer || !_ht_find((__g)->collected_table, (__o)->data.pointer)) { \ + switch((__o)->type) { \ + _ADDGC_USERTYPE_REF(__o, __g) \ + _ADDGC_ARRAY(__o, __g) \ + _ADDGC_COLL(__o, __g) \ + _ADDGC_COLL_IT(__o, __g) \ + _ADDGC_CLASS(__o, __g) \ + _ADDGC_ROUTINE(__o, __g, __r) \ + _ADDGC_STRING(__o) \ + default: break; \ + } \ + } +#ifndef _GCNOW /* Uprootable stub */ +# define _GCNOW(__s) (!!(__s)) +#endif /* _GCNOW */ +#ifndef _PREPAREGC /* Uprootable stub */ +# define _PREPAREGC(__s, __g) do { ((void)(__s)); ((void)(__g)); } while(0) +#endif /* _PREPAREGC */ +#ifndef _PREVGC /* Uprootable stub */ +# define _PREVGC(__s, __g) do { ((void)(__s)); ((void)(__g)); } while(0) +#endif /* _PREVGC */ +#ifndef _POSTGC /* Uprootable stub */ +# define _POSTGC(__s, __g) do { ((void)(__s)); ((void)(__g)); } while(0) +#endif /* _POSTGC */ + +static int_t _get_size_of(_data_e type); +static bool_t _try_get_value(_object_t* obj, mb_value_u* val, _data_e expected); + +static bool_t _is_nil(void* obj); +static bool_t _is_number(void* obj); +static bool_t _is_string(void* obj); +static char* _extract_string(_object_t* obj); +#ifdef MB_MANUAL_REAL_FORMATTING +static void _real_to_str(real_t r, char* str, size_t size, size_t afterpoint); +#endif /* MB_MANUAL_REAL_FORMATTING */ +static void _real_to_str_std(real_t r, char* str, size_t size); +#ifndef mb_realtostr +# define mb_realtostr(__r, __s, __z) _real_to_str_std((__r), (__s), (__z)) +#endif /* mb_realtostr */ + +#ifdef _HAS_REF_OBJ_LOCK +static bool_t _lock_ref_object(_lock_t* lk, _ref_t* ref, void* obj); +static bool_t _unlock_ref_object(_lock_t* lk, _ref_t* ref, void* obj); +static bool_t _write_on_ref_object(_lock_t* lk, _ref_t* ref, void* obj); +#endif /* _HAS_REF_OBJ_LOCK */ + +static bool_t _is_ref(_object_t* obj); +static _ref_count_t _ref(_ref_t* ref, void* data); +static bool_t _unref(_ref_t* ref, void* data); +static _ref_count_t _weak_ref(_ref_t* ref, void* data, _ref_t* weak); +static bool_t _weak_unref(_ref_t* weak); +static void _create_ref(_ref_t* ref, _unref_func_t dtor, _data_e t, mb_interpreter_t* s); +static void _destroy_ref(_ref_t* ref); + +static void _gc_add(_ref_t* ref, void* data, _gc_t* gc); +static unsigned _gc_remove(_ref_t* ref, void* data, _gc_t* gc); +static int _gc_add_reachable(void* data, void* extra, void* h); +static int _gc_add_reachable_both(void* data, void* extra, void* h); +#ifdef MB_ENABLE_FORK +static int _gc_get_reachable_in_forked(void* data, void* extra, _ht_node_t* valid); +#endif /* MB_ENABLE_FORK */ +static void _gc_get_reachable(mb_interpreter_t* s, _ht_node_t* ht, _running_context_t* end); +static void _gc_alive_marker(mb_interpreter_t* s, void* h, mb_value_t val); +static int _gc_destroy_garbage_in_list(void* data, void* extra, _gc_t* gc); +static int _gc_destroy_garbage_in_dict(void* data, void* extra, _gc_t* gc); +#ifdef MB_ENABLE_CLASS +static int _gc_destroy_garbage_in_class(void* data, void* extra, _gc_t* gc); +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_LAMBDA +static int _gc_destroy_garbage_in_lambda(void* data, void* extra, _gc_t* gc); +static void _gc_destroy_garbage_in_outer_scope(_running_context_ref_t* p, _gc_t* gc); +#endif /* MB_ENABLE_LAMBDA */ +static int _gc_destroy_garbage(void* data, void* extra, _gc_t* gc); +#ifdef MB_ENABLE_CLASS +static int _gc_destroy_garbage_class(void* data, void* extra, _gc_t* gc); +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_CLASS +static int _gc_destroy_garbage_outer_scope(void* data, void* extra, _gc_t* gc); +#endif /* MB_ENABLE_CLASS */ +static void _gc_swap_tables(mb_interpreter_t* s); +static void _gc_try_trigger(mb_interpreter_t* s); +static void _gc_collect_garbage(mb_interpreter_t* s, int depth); + +#ifdef MB_ENABLE_USERTYPE_REF +static _usertype_ref_t* _create_usertype_ref(mb_interpreter_t* s, void* val, mb_dtor_func_t un, mb_clone_func_t cl, mb_hash_func_t hs, mb_cmp_func_t cp, mb_fmt_func_t ft); +static void _destroy_usertype_ref(_usertype_ref_t* c); +static void _unref_usertype_ref(_ref_t* ref, void* data); +static void _clone_usertype_ref(_usertype_ref_t* src, _object_t* tgt); +static bool_t _try_call_func_on_usertype_ref(mb_interpreter_t* s, _ls_node_t** ast, _object_t* obj, _ls_node_t* pathed, int* ret); +#endif /* MB_ENABLE_USERTYPE_REF */ + +static _array_t* _create_array(mb_interpreter_t* s, const char* n, _data_e t); +static void _destroy_array(_array_t* arr); +static void _init_array(_array_t* arr); +static _array_t* _clone_array(mb_interpreter_t* s, _array_t* arr); +static int _get_array_pos(mb_interpreter_t* s, _array_t* arr, int* d, int c); +static int _get_array_index(mb_interpreter_t* s, _ls_node_t** l, _object_t* c, unsigned* index, bool_t* literally); +static bool_t _get_array_elem(mb_interpreter_t* s, _array_t* arr, unsigned index, mb_value_u* val, _data_e* type); +static int _set_array_elem(mb_interpreter_t* s, _ls_node_t* ast, _array_t* arr, unsigned index, mb_value_u* val, _data_e* type); +static void _clear_array(_array_t* arr); +static bool_t _is_array(void* obj); +#ifdef MB_ENABLE_ARRAY_REF +static void _unref_array(_ref_t* ref, void* data); +#endif /* MB_ENABLE_ARRAY_REF */ + +#ifdef MB_ENABLE_COLLECTION_LIB +static _list_t* _create_list(mb_interpreter_t* s); +static void _destroy_list(_list_t* c); +static _dict_t* _create_dict(mb_interpreter_t* s); +static void _destroy_dict(_dict_t* c); +static _list_it_t* _create_list_it(_list_t* coll, bool_t lock); +static bool_t _destroy_list_it(_list_it_t* it); +static _list_it_t* _move_list_it_next(_list_it_t* it); +static _dict_it_t* _create_dict_it(_dict_t* coll, bool_t lock); +static bool_t _destroy_dict_it(_dict_it_t* it); +static _dict_it_t* _move_dict_it_next(_dict_it_t* it); +static void _unref_list(_ref_t* ref, void* data); +static void _unref_dict(_ref_t* ref, void* data); +static bool_t _push_list(_list_t* coll, mb_value_t* val, _object_t* oarg); +static bool_t _pop_list(_list_t* coll, mb_value_t* val, mb_interpreter_t* s); +static bool_t _insert_list(_list_t* coll, int_t idx, mb_value_t* val, _object_t** oval); +static bool_t _set_list(_list_t* coll, int_t idx, mb_value_t* val, _object_t** oval); +static bool_t _remove_at_list(_list_t* coll, int_t idx); +static _ls_node_t* _node_at_list(_list_t* coll, int index); +static bool_t _at_list(_list_t* coll, int_t idx, mb_value_t* oval); +static bool_t _find_list(_list_t* coll, mb_value_t* val, int* idx); +static void _clear_list(_list_t* coll); +static void _sort_list(_list_t* coll); +static void _invalidate_list_cache(_list_t* coll); +static void _fill_ranged(_list_t* coll); +static bool_t _set_dict(_dict_t* coll, mb_value_t* key, mb_value_t* val, _object_t* okey, _object_t* oval); +static bool_t _remove_dict(_dict_t* coll, mb_value_t* key); +static bool_t _find_dict(_dict_t* coll, mb_value_t* val, mb_value_t* oval); +static void _clear_dict(_dict_t* coll); +static bool_t _invalid_list_it(_list_it_t* it); +static bool_t _invalid_dict_it(_dict_it_t* it); +static bool_t _assign_with_it(_object_t* tgt, _object_t* src); +static bool_t _try_purge_it(mb_interpreter_t* s, mb_value_t* val, _object_t* obj); +static int _clone_to_list(void* data, void* extra, _list_t* coll); +static int _clone_to_dict(void* data, void* extra, _dict_t* coll); +static int _copy_list_to_array(void* data, void* extra, _array_helper_t* h); +static int _copy_keys_to_value_array(void* data, void* extra, _keys_helper_t* h); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +#ifdef MB_ENABLE_CLASS +typedef int (* _class_scope_walker_t)(void*, void*, void*); +typedef bool_t (* _class_meta_walker_t)(_class_t*, void*, void*); +static void _init_class(mb_interpreter_t* s, _class_t* instance, char* n); +static void _begin_class(mb_interpreter_t* s); +static bool_t _end_class(mb_interpreter_t* s); +static void _unref_class(_ref_t* ref, void* data); +static void _destroy_class(_class_t* c); +static bool_t _traverse_class(_class_t* c, _class_scope_walker_t scope_walker, _class_meta_walker_t meta_walker, unsigned meta_depth, bool_t meta_walk_on_self, void* extra_data, void* ret); +static bool_t _link_meta_class(mb_interpreter_t* s, _class_t* derived, _class_t* base); +static void _unlink_meta_class(mb_interpreter_t* s, _class_t* derived); +static int _unlink_meta_instance(void* data, void* extra, _class_t* derived); +static int _clone_clsss_field(void* data, void* extra, void* n); +static bool_t _clone_class_meta_link(_class_t* meta, void* n, void* ret); +static int _search_class_meta_function(mb_interpreter_t* s, _class_t* instance, const char* n, _routine_t* _UNALIGNED_ARG * f); +static int _search_class_hash_and_compare_functions(mb_interpreter_t* s, _class_t* instance); +static bool_t _is_a_class(_class_t* instance, void* m, void* ret); +static bool_t _add_class_meta_reachable(_class_t* meta, void* ht, void* ret); +#ifdef MB_ENABLE_COLLECTION_LIB +static int _reflect_class_field(void* data, void* extra, void* d); +#endif /* MB_ENABLE_COLLECTION_LIB */ +static int _format_class_to_string(mb_interpreter_t* s, void** l, _class_t* instance, _object_t* out, bool_t* got_tostr); +static _class_t* _reflect_string_to_class(mb_interpreter_t* s, const char* n, mb_value_t* arg); +static bool_t _is_valid_class_accessor_following_routine(mb_interpreter_t* s, _var_t* var, _ls_node_t* ast, _ls_node_t** out); +#endif /* MB_ENABLE_CLASS */ +static void _init_routine(mb_interpreter_t* s, _routine_t* routine, char* n, mb_routine_func_t f); +static int _begin_routine(mb_interpreter_t* s); +static bool_t _end_routine(mb_interpreter_t* s); +static void _begin_routine_definition(mb_interpreter_t* s); +static void _begin_routine_parameter_list(mb_interpreter_t* s); +static void _end_routine_parameter_list(mb_interpreter_t* s); +static _object_t* _duplicate_parameter(void* data, void* extra, _running_context_t* running); +static _routine_t* _clone_routine(_routine_t* sub, void* c, bool_t toupval); +#ifdef MB_ENABLE_LAMBDA +static _running_context_t* _init_lambda(mb_interpreter_t* s, _routine_t* routine); +static void _unref_routine(_ref_t* ref, void* data); +static void _destroy_routine(mb_interpreter_t* s, _routine_t* r); +static void _mark_upvalue(mb_interpreter_t* s, _lambda_t* lambda, _object_t* obj, const char* n); +static void _try_mark_upvalue(mb_interpreter_t* s, _routine_t* r, _object_t* obj); +static _running_context_ref_t* _create_outer_scope(mb_interpreter_t* s); +static void _unref_outer_scope(_ref_t* ref, void* data); +static void _destroy_outer_scope(_running_context_ref_t* p); +static int _do_nothing_on_ht_for_lambda(void* data, void* extra); +static int _fill_with_upvalue(void* data, void* extra, _upvalue_scope_tuple_t* tuple); +static int _remove_filled_upvalue(void* data, void* extra, _ht_node_t* ht); +static int _fill_outer_scope(void* data, void* extra, _upvalue_scope_tuple_t* tuple); +static int _remove_this_lambda_from_upvalue(void* data, void* extra, _routine_t* routine); +static _running_context_t* _link_lambda_scope_chain(mb_interpreter_t* s, _lambda_t* lambda, bool_t weak); +static _running_context_t* _unlink_lambda_scope_chain(mb_interpreter_t* s, _lambda_t* lambda, bool_t weak); +static bool_t _is_valid_lambda_body_node(mb_interpreter_t* s, _lambda_t* lambda, _object_t* obj); +#endif /* MB_ENABLE_LAMBDA */ +#ifdef MB_ENABLE_CLASS +static _running_context_t* _reference_scope_by_class(mb_interpreter_t* s, _running_context_t* p, _class_t* c); +static _running_context_t* _push_scope_by_class(mb_interpreter_t* s, _running_context_t* p); +static _ls_node_t* _search_identifier_in_class(mb_interpreter_t* s, _class_t* instance, const char* n, _ht_node_t** ht, _running_context_t** sp); +#endif /* MB_ENABLE_CLASS */ +static _running_context_t* _reference_scope_by_routine(mb_interpreter_t* s, _running_context_t* p, _routine_t* r); +static _running_context_t* _push_weak_scope_by_routine(mb_interpreter_t* s, _running_context_t* p, _routine_t* r); +static _running_context_t* _push_scope_by_routine(mb_interpreter_t* s, _running_context_t* p); +static void _destroy_scope(mb_interpreter_t* s, _running_context_t* p); +static _running_context_t* _pop_weak_scope(mb_interpreter_t* s, _running_context_t* p); +static _running_context_t* _pop_scope(mb_interpreter_t* s, bool_t tidy); +static void _out_of_scope(mb_interpreter_t* s, _running_context_t* running, void* instance, _routine_t* routine, bool_t lose); +static _running_context_t* _find_scope(mb_interpreter_t* s, _running_context_t* p); +static _running_context_t* _get_root_scope(_running_context_t* scope); +#ifdef MB_ENABLE_LAMBDA +static _running_context_ref_t* _get_root_ref_scope(_running_context_ref_t* scope); +#endif /* MB_ENABLE_LAMBDA */ +static _running_context_t* _get_scope_to_add_routine(mb_interpreter_t* s); +static _ls_node_t* _search_identifier_in_scope_chain(mb_interpreter_t* s, _running_context_t* scope, const char* n, int fp, _ht_node_t** ht, _running_context_t** sp); +static _array_t* _search_array_in_scope_chain(mb_interpreter_t* s, _array_t* i, _object_t** o); +static _var_t* _search_var_in_scope_chain(mb_interpreter_t* s, _var_t* i, _object_t** o); +static _ls_node_t* _search_identifier_accessor(mb_interpreter_t* s, _running_context_t* scope, const char* n, _ht_node_t** ht, _running_context_t** sp, bool_t unknown_for_not_found); + +static _var_t* _create_var(_object_t** oobj, const char* n, size_t ns, bool_t dup_name); +static int _retrieve_var(void* data, void* extra, void* t); +static _object_t* _create_object(void); +static int _clone_object(mb_interpreter_t* s, _object_t* obj, _object_t* tgt, bool_t toupval, bool_t deep); +static int _dispose_object(_object_t* obj); +static int _destroy_object(void* data, void* extra); +static int _destroy_object_with_extra(void* data, void* extra); +static int _destroy_object_not_compile_time(void* data, void* extra); +static int _destroy_object_capsule_only(void* data, void* extra); +static int _do_nothing_on_object(void* data, void* extra); +static int _lose_object(void* data, void* extra, _running_context_t* running); +static int _remove_source_object(void* data, void* extra); +static int _destroy_memory(void* data, void* extra); +static int _compare_numbers(const _object_t* first, const _object_t* second); +static bool_t _is_internal_object(_object_t* obj); +static _data_e _public_type_to_internal_type(mb_data_e t); +static mb_data_e _internal_type_to_public_type(_data_e t); +static int _public_value_to_internal_object(mb_value_t* pbl, _object_t* itn); +static int _internal_object_to_public_value(_object_t* itn, mb_value_t* pbl); +static int _create_internal_object_from_public_value(mb_value_t* pbl, _object_t** itn); +static int _compare_public_value_and_internal_object(mb_value_t* pbl, _object_t* itn); +static void _try_clear_intermediate_value(void* data, void* extra, mb_interpreter_t* s); +static void _remove_if_exists(void* data, void* extra, _ls_node_t* ls); +static void _destroy_var_arg(void* data, void* extra, _gc_t* gc); +static void _destroy_edge_objects(mb_interpreter_t* s); +static void _mark_edge_destroy_string(mb_interpreter_t* s, char* ch); +static void _destroy_lazy_objects(mb_interpreter_t* s); +static void _mark_lazy_destroy_string(mb_interpreter_t* s, char* ch); +static void _assign_public_value(mb_interpreter_t* s, mb_value_t* tgt, mb_value_t* src, bool_t pit); +static void _swap_public_value(mb_value_t* tgt, mb_value_t* src); +static int _clear_scope_chain(mb_interpreter_t* s); +static int _dispose_scope_chain(mb_interpreter_t* s); +static void _tidy_scope_chain(mb_interpreter_t* s); +static void _collect_intermediate_value_in_scope(_running_context_t* running, void* data); +#ifdef MB_ENABLE_FORK +static void _collect_intermediate_value_in_scope_chain(void* data, void* extra, void* d); +#endif /* MB_ENABLE_FORK */ +static void _collect_intermediate_value(_ref_t* ref, void* data); +static void _mark_dangling_intermediate_value(mb_interpreter_t* s, _running_context_t* running); +static _object_t* _eval_var_in_print(mb_interpreter_t* s, _object_t** val_ptr, _ls_node_t** ast, _object_t* obj); + +/** Interpretation */ + +static int _prev_stepped(mb_interpreter_t* s, _ls_node_t* ast); +static int _post_stepped(mb_interpreter_t* s, _ls_node_t* ast); +static int _execute_statement(mb_interpreter_t* s, _ls_node_t** l, bool_t force_next); +static int _common_end_looping(mb_interpreter_t* s, _ls_node_t** l); +static int _common_keep_looping(mb_interpreter_t* s, _ls_node_t** l, _var_t* var_loop); +static int _execute_normal_for_loop(mb_interpreter_t* s, _ls_node_t** l, _var_t* var_loop); +#ifdef MB_ENABLE_COLLECTION_LIB +static int _execute_ranged_for_loop(mb_interpreter_t* s, _ls_node_t** l, _var_t* var_loop); +#endif /* MB_ENABLE_COLLECTION_LIB */ +static int _skip_to(mb_interpreter_t* s, _ls_node_t** l, mb_func_t f, _data_e t); +static bool_t _skip_single_line_struct(_ls_node_t** ast, mb_func_t func); +static int _skip_if_chunk(mb_interpreter_t* s, _ls_node_t** l); +static int _skip_struct(mb_interpreter_t* s, _ls_node_t** l, mb_func_t open_func, mb_func_t post_open_func, mb_func_t close_func); +static bool_t _multiline_statement(mb_interpreter_t* s); + +static _running_context_t* _create_running_context(bool_t create_var_dict); +static _parsing_context_t* _reset_parsing_context(_parsing_context_t* context); +static void _destroy_parsing_context(_parsing_context_t* _UNALIGNED_ARG * context); + +/** Interface processors */ + +#ifdef MB_ENABLE_MODULE +static _module_func_t* _create_module_func(mb_interpreter_t* s, mb_func_t f); +static int _ls_destroy_module_func(void* data, void* extra); +static int _ht_destroy_module_func_list(void* data, void* extra); +#endif /* MB_ENABLE_MODULE */ +static char* _generate_func_name(mb_interpreter_t* s, char* n, bool_t with_mod); +static int _register_func(mb_interpreter_t* s, char* n, mb_func_t f, bool_t local); +static int _remove_func(mb_interpreter_t* s, char* n, bool_t local); +static _ls_node_t* _find_func(mb_interpreter_t* s, char* n, bool_t* mod); + +static int _open_constant(mb_interpreter_t* s); +static int _close_constant(mb_interpreter_t* s); +static int _open_core_lib(mb_interpreter_t* s); +static int _close_core_lib(mb_interpreter_t* s); +static int _open_std_lib(mb_interpreter_t* s); +static int _close_std_lib(mb_interpreter_t* s); +#ifdef MB_ENABLE_COLLECTION_LIB +static int _open_coll_lib(mb_interpreter_t* s); +static int _close_coll_lib(mb_interpreter_t* s); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +/* ========================================================} */ + +/* +** {======================================================== +** Lib declarations +*/ + +/** Macro */ + +#ifdef MB_CP_VC +# if MB_CP_VC < 1300 +# define MB_FUNC 0 +# else /* MB_CP_VC < 1300 */ +# define MB_FUNC __FUNCTION__ +# endif /* MB_CP_VC < 1300 */ +#elif defined MB_CP_BORLANDC +# define MB_FUNC __FUNC__ +#elif defined MB_CP_PELLESC +# define MB_FUNC __func__ +#else /* MB_CP_VC */ +# define MB_FUNC __FUNCTION__ +#endif /* MB_CP_VC */ + +#ifdef MB_CP_VC +# if MB_CP_VC < 1300 +# define _do_nothing(__s, __l, __exit, __result) \ + do { \ + _ls_node_t* ast = 0; static int i = 0; ++i; \ + printf("Unaccessable function called %d times.\n", i); \ + ast = (_ls_node_t*)(*(__l)); \ + _handle_error_on_obj((__s), SE_RN_REACHED_TO_WRONG_FUNCTION, (__s)->source_file, DON(ast), MB_FUNC_ERR, __exit, (__result)); \ + } while(0) +# endif /* MB_CP_VC < 1300 */ +#endif /* MB_CP_VC */ +#ifndef _do_nothing +# define _do_nothing(__s, __l, __exit, __result) \ + do { \ + _ls_node_t* ast = (_ls_node_t*)(*(__l)); \ + _handle_error_on_obj((__s), SE_RN_REACHED_TO_WRONG_FUNCTION, (char*)MB_FUNC, DON(ast), MB_FUNC_ERR, __exit, (__result)); \ + } while(0); +#endif /* _do_nothing */ + +/** Core lib */ + +static int _core_dummy_assign(mb_interpreter_t* s, void** l); +static int _core_add(mb_interpreter_t* s, void** l); +static int _core_min(mb_interpreter_t* s, void** l); +static int _core_mul(mb_interpreter_t* s, void** l); +static int _core_div(mb_interpreter_t* s, void** l); +static int _core_mod(mb_interpreter_t* s, void** l); +static int _core_pow(mb_interpreter_t* s, void** l); +static int _core_open_bracket(mb_interpreter_t* s, void** l); +static int _core_close_bracket(mb_interpreter_t* s, void** l); +static int _core_neg(mb_interpreter_t* s, void** l); +static int _core_equal(mb_interpreter_t* s, void** l); +static int _core_less(mb_interpreter_t* s, void** l); +static int _core_greater(mb_interpreter_t* s, void** l); +static int _core_less_equal(mb_interpreter_t* s, void** l); +static int _core_greater_equal(mb_interpreter_t* s, void** l); +static int _core_not_equal(mb_interpreter_t* s, void** l); +static int _core_and(mb_interpreter_t* s, void** l); +static int _core_or(mb_interpreter_t* s, void** l); +static int _core_not(mb_interpreter_t* s, void** l); +static int _core_is(mb_interpreter_t* s, void** l); +static int _core_let(mb_interpreter_t* s, void** l); +static int _core_dim(mb_interpreter_t* s, void** l); +static int _core_if(mb_interpreter_t* s, void** l); +static int _core_then(mb_interpreter_t* s, void** l); +static int _core_elseif(mb_interpreter_t* s, void** l); +static int _core_else(mb_interpreter_t* s, void** l); +static int _core_endif(mb_interpreter_t* s, void** l); +static int _core_for(mb_interpreter_t* s, void** l); +#ifdef MB_ENABLE_COLLECTION_LIB +static int _core_in(mb_interpreter_t* s, void** l); +#endif /* MB_ENABLE_COLLECTION_LIB */ +static int _core_to(mb_interpreter_t* s, void** l); +static int _core_step(mb_interpreter_t* s, void** l); +static int _core_next(mb_interpreter_t* s, void** l); +static int _core_while(mb_interpreter_t* s, void** l); +static int _core_wend(mb_interpreter_t* s, void** l); +static int _core_do(mb_interpreter_t* s, void** l); +static int _core_until(mb_interpreter_t* s, void** l); +static int _core_exit(mb_interpreter_t* s, void** l); +static int _core_goto(mb_interpreter_t* s, void** l); +static int _core_gosub(mb_interpreter_t* s, void** l); +static int _core_return(mb_interpreter_t* s, void** l); +static int _core_call(mb_interpreter_t* s, void** l); +static int _core_def(mb_interpreter_t* s, void** l); +static int _core_enddef(mb_interpreter_t* s, void** l); +static int _core_args(mb_interpreter_t* s, void** l); +#ifdef MB_ENABLE_CLASS +static int _core_class(mb_interpreter_t* s, void** l); +static int _core_endclass(mb_interpreter_t* s, void** l); +static int _core_new(mb_interpreter_t* s, void** l); +static int _core_var(mb_interpreter_t* s, void** l); +static int _core_reflect(mb_interpreter_t* s, void** l); +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_LAMBDA +static int _core_lambda(mb_interpreter_t* s, void** l); +#endif /* MB_ENABLE_LAMBDA */ +#ifdef MB_ENABLE_ALLOC_STAT +static int _core_mem(mb_interpreter_t* s, void** l); +#endif /* MB_ENABLE_ALLOC_STAT */ +static int _core_type(mb_interpreter_t* s, void** l); +static int _core_import(mb_interpreter_t* s, void** l); +static int _core_end(mb_interpreter_t* s, void** l); + +/** Standard lib */ + +static int _std_abs(mb_interpreter_t* s, void** l); +static int _std_sgn(mb_interpreter_t* s, void** l); +static int _std_sqr(mb_interpreter_t* s, void** l); +static int _std_floor(mb_interpreter_t* s, void** l); +static int _std_ceil(mb_interpreter_t* s, void** l); +static int _std_fix(mb_interpreter_t* s, void** l); +static int _std_round(mb_interpreter_t* s, void** l); +static int _std_srnd(mb_interpreter_t* s, void** l); +static int _std_rnd(mb_interpreter_t* s, void** l); +static int _std_sin(mb_interpreter_t* s, void** l); +static int _std_cos(mb_interpreter_t* s, void** l); +static int _std_tan(mb_interpreter_t* s, void** l); +static int _std_asin(mb_interpreter_t* s, void** l); +static int _std_acos(mb_interpreter_t* s, void** l); +static int _std_atan(mb_interpreter_t* s, void** l); +static int _std_exp(mb_interpreter_t* s, void** l); +static int _std_log(mb_interpreter_t* s, void** l); +static int _std_asc(mb_interpreter_t* s, void** l); +static int _std_chr(mb_interpreter_t* s, void** l); +static int _std_left(mb_interpreter_t* s, void** l); +static int _std_mid(mb_interpreter_t* s, void** l); +static int _std_right(mb_interpreter_t* s, void** l); +static int _std_str(mb_interpreter_t* s, void** l); +static int _std_val(mb_interpreter_t* s, void** l); +static int _std_len(mb_interpreter_t* s, void** l); +static int _std_get(mb_interpreter_t* s, void** l); +static int _std_set(mb_interpreter_t* s, void** l); +static int _std_print(mb_interpreter_t* s, void** l); +static int _std_input(mb_interpreter_t* s, void** l); + +/** Collection lib */ + +#ifdef MB_ENABLE_COLLECTION_LIB +static int _coll_list(mb_interpreter_t* s, void** l); +static int _coll_dict(mb_interpreter_t* s, void** l); +static int _coll_push(mb_interpreter_t* s, void** l); +static int _coll_pop(mb_interpreter_t* s, void** l); +static int _coll_back(mb_interpreter_t* s, void** l); +static int _coll_insert(mb_interpreter_t* s, void** l); +static int _coll_sort(mb_interpreter_t* s, void** l); +static int _coll_exists(mb_interpreter_t* s, void** l); +static int _coll_index_of(mb_interpreter_t* s, void** l); +static int _coll_remove(mb_interpreter_t* s, void** l); +static int _coll_clear(mb_interpreter_t* s, void** l); +static int _coll_clone(mb_interpreter_t* s, void** l); +static int _coll_to_array(mb_interpreter_t* s, void** l); +static int _coll_iterator(mb_interpreter_t* s, void** l); +static int _coll_move_next(mb_interpreter_t* s, void** l); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +/** Lib information */ + +#define _CORE_ID_TYPE "TYPE" + +MBCONST static const _func_t _core_libs[] = { + { _DUMMY_ASSIGN_CHAR, _core_dummy_assign }, + { "+", _core_add }, + { "-", _core_min }, + { "*", _core_mul }, + { "/", _core_div }, + { "MOD", _core_mod }, + { "^", _core_pow }, + { "(", _core_open_bracket }, + { ")", _core_close_bracket }, + { 0, _core_neg }, + + { "=", _core_equal }, + { "<", _core_less }, + { ">", _core_greater }, + { "<=", _core_less_equal }, + { ">=", _core_greater_equal }, + { "<>", _core_not_equal }, + + { "AND", _core_and }, + { "OR", _core_or }, + { "NOT", _core_not }, + + { "IS", _core_is }, + + { "LET", _core_let }, + { "DIM", _core_dim }, + + { "IF", _core_if }, + { "THEN", _core_then }, + { "ELSEIF", _core_elseif }, + { "ELSE", _core_else }, + { "ENDIF", _core_endif }, + + { "FOR", _core_for }, +#ifdef MB_ENABLE_COLLECTION_LIB + { "IN", _core_in }, +#endif /* MB_ENABLE_COLLECTION_LIB */ + { "TO", _core_to }, + { "STEP", _core_step }, + { "NEXT", _core_next }, + { "WHILE", _core_while }, + { "WEND", _core_wend }, + { "DO", _core_do }, + { "UNTIL", _core_until }, + + { "EXIT", _core_exit }, + { "GOTO", _core_goto }, + { "GOSUB", _core_gosub }, + { "RETURN", _core_return }, + + { "CALL", _core_call }, + { "DEF", _core_def }, + { "ENDDEF", _core_enddef }, + { _VAR_ARGS_STR, _core_args }, + +#ifdef MB_ENABLE_CLASS + { "CLASS", _core_class }, + { "ENDCLASS", _core_endclass }, + { "NEW", _core_new }, + { "VAR", _core_var }, + { "REFLECT", _core_reflect }, +#endif /* MB_ENABLE_CLASS */ + +#ifdef MB_ENABLE_LAMBDA + { "LAMBDA", _core_lambda }, +#ifdef MB_LAMBDA_ALIAS + { MB_LAMBDA_ALIAS, _core_lambda }, +#endif /* MB_LAMBDA_ALIAS */ +#endif /* MB_ENABLE_LAMBDA */ + +#ifdef MB_ENABLE_ALLOC_STAT + { "MEM", _core_mem }, +#endif /* MB_ENABLE_ALLOC_STAT */ + + { _CORE_ID_TYPE, _core_type }, + { "IMPORT", _core_import }, + { "END", _core_end } +}; + +#define _STD_ID_VAL "VAL" +#define _STD_ID_LEN "LEN" +#define _STD_ID_GET "GET" +#define _STD_ID_SET "SET" + +MBCONST static const _func_t _std_libs[] = { + { "ABS", _std_abs }, + { "SGN", _std_sgn }, + { "SQR", _std_sqr }, + { "FLOOR", _std_floor }, + { "CEIL", _std_ceil }, + { "FIX", _std_fix }, + { "ROUND", _std_round }, + { "SRND", _std_srnd }, + { "RND", _std_rnd }, + { "SIN", _std_sin }, + { "COS", _std_cos }, + { "TAN", _std_tan }, + { "ASIN", _std_asin }, + { "ACOS", _std_acos }, + { "ATAN", _std_atan }, + { "EXP", _std_exp }, + { "LOG", _std_log }, + + { "ASC", _std_asc }, + { "CHR", _std_chr }, + { "LEFT", _std_left }, + { "MID", _std_mid }, + { "RIGHT", _std_right }, + { "STR", _std_str }, + { _STD_ID_VAL, _std_val }, + + { _STD_ID_LEN, _std_len }, + { _STD_ID_GET, _std_get }, + { _STD_ID_SET, _std_set }, + + { "PRINT", _std_print }, + { "INPUT", _std_input } +}; + +#ifdef MB_ENABLE_COLLECTION_LIB +# define _COLL_ID_LIST "LIST" +# define _COLL_ID_DICT "DICT" +# define _COLL_ID_PUSH "PUSH" +# define _COLL_ID_POP "POP" +# define _COLL_ID_BACK "BACK" +# define _COLL_ID_INSERT "INSERT" +# define _COLL_ID_SORT "SORT" +# define _COLL_ID_EXISTS "EXISTS" +# define _COLL_ID_INDEX_OF "INDEX_OF" +# define _COLL_ID_REMOVE "REMOVE" +# define _COLL_ID_CLEAR "CLEAR" +# define _COLL_ID_CLONE "CLONE" +# define _COLL_ID_TO_ARRAY "TO_ARRAY" +# define _COLL_ID_ITERATOR "ITERATOR" +# define _COLL_ID_MOVE_NEXT "MOVE_NEXT" + +MBCONST static const _func_t _coll_libs[] = { + { _COLL_ID_LIST, _coll_list }, + { _COLL_ID_DICT, _coll_dict }, + { _COLL_ID_PUSH, _coll_push }, + { _COLL_ID_POP, _coll_pop }, + { _COLL_ID_BACK, _coll_back }, + { _COLL_ID_INSERT, _coll_insert }, + { _COLL_ID_SORT, _coll_sort }, + { _COLL_ID_EXISTS, _coll_exists }, + { _COLL_ID_INDEX_OF, _coll_index_of }, + { _COLL_ID_REMOVE, _coll_remove }, + { _COLL_ID_CLEAR, _coll_clear }, + { _COLL_ID_CLONE, _coll_clone }, + { _COLL_ID_TO_ARRAY, _coll_to_array }, + { _COLL_ID_ITERATOR, _coll_iterator }, + { _COLL_ID_MOVE_NEXT, _coll_move_next } +}; +#endif /* MB_ENABLE_COLLECTION_LIB */ + +/* ========================================================} */ + +/* +** {======================================================== +** Private function definitions +*/ + +/** List operations */ + +static int _ls_cmp_data(void* node, void* info) { + _ls_node_t* n = (_ls_node_t*)node; + + return (n->data == info) ? 0 : 1; +} + +static int _ls_cmp_extra(void* node, void* info) { + _ls_node_t* n = (_ls_node_t*)node; + + return (n->extra == info) ? 0 : 1; +} + +static int _ls_cmp_extra_object(void* node, void* info) { + _ls_node_t* n = (_ls_node_t*)node; + + return _ht_cmp_object(n->extra, info); +} + +static int _ls_cmp_extra_string(void* node, void* info) { + _ls_node_t* n = (_ls_node_t*)node; + char* s1 = (char*)n->extra; + char* s2 = (char*)info; + + return strcmp(s1, s2); +} + +#ifdef MB_ENABLE_MODULE +static int _ls_cmp_module_func(void* node, void* info) { + _module_func_t* m = (_module_func_t*)node; + mb_interpreter_t* s = (mb_interpreter_t*)info; + + return strcmp(m->module, s->with_module); +} +#endif /* MB_ENABLE_MODULE */ + +static _ls_node_t* _ls_create_node(void* data) { + _ls_node_t* result = 0; + + result = (_ls_node_t*)mb_malloc(sizeof(_ls_node_t)); + mb_assert(result); + memset(result, 0, sizeof(_ls_node_t)); + result->data = data; + + return result; +} + +static _ls_node_t* _ls_create(void) { + _ls_node_t* result = 0; + + result = _ls_create_node(0); + + return result; +} + +static _ls_node_t* _ls_find(_ls_node_t* list, void* data, _ls_compare_t cmp, int* idx) { + _ls_node_t* result = 0; + + mb_assert(list && data && cmp); + + if(idx) *idx = 0; + + list = list->next; + while(list) { + if(!cmp(list->data, data)) { + result = list; + + break; + } + list = list->next; + if(idx) ++*idx; + } + + return result; +} + +static _ls_node_t* _ls_back(_ls_node_t* node) { + _ls_node_t* result = node; + + result = result->prev; + + return result; +} + +static _ls_node_t* _ls_pushback(_ls_node_t* list, void* data) { + _ls_node_t* result = 0; + _ls_node_t* tmp = 0; + + mb_assert(list); + + result = _ls_create_node(data); + + tmp = _ls_back(list); + if(!tmp) + tmp = list; + tmp->next = result; + result->prev = tmp; + list->prev = result; + list->data = (char*)list->data + 1; + + return result; +} + +static void* _ls_popback(_ls_node_t* list) { + void* result = 0; + _ls_node_t* tmp = 0; + + mb_assert(list); + + tmp = _ls_back(list); + if(tmp) { + result = tmp->data; + if(list != tmp->prev) + list->prev = tmp->prev; + else + list->prev = 0; + tmp->prev->next = 0; + safe_free(tmp); + list->data = (char*)list->data - 1; + } + + return result; +} + +static _ls_node_t* _ls_front(_ls_node_t* node) { + _ls_node_t* result = node; + + result = result->next; + + return result; +} + +static void* _ls_popfront(_ls_node_t* list) { + void* result = 0; + _ls_node_t* tmp = 0; + + mb_assert(list); + + tmp = _ls_front(list); + if(tmp) { + result = tmp->data; + list->next = tmp->next; + if(tmp->next) + tmp->next->prev = list; + if(!list->next) + list->prev = 0; + tmp->prev = tmp->next = 0; + safe_free(tmp); + list->data = (char*)list->data - 1; + } + + return result; +} + +static _ls_node_t* _ls_insert_at(_ls_node_t* list, int index, void* data) { + _ls_node_t* result = 0; + _ls_node_t* tmp = 0; + + mb_assert(list); + + tmp = list->next; + while(tmp && index) { + tmp = tmp->next; + --index; + } + if(!tmp) { + if(index == 0) + result = _ls_pushback(list, data); + } else { + result = _ls_create_node(data); + tmp->prev->next = result; + result->prev = tmp->prev; + result->next = tmp; + tmp->prev = result; + } + list->data = (char*)list->data + 1; + + return result; +} + +static unsigned _ls_remove(_ls_node_t* list, _ls_node_t* node, _ls_operation_t op) { + unsigned result = 0; + + mb_assert(list && node); + + if(node->prev) + node->prev->next = node->next; + if(node->next) + node->next->prev = node->prev; + if(list->prev == node) + list->prev = node->prev; + if(list->prev == list) + list->prev = 0; + if(op) + op(node->data, node->extra); + safe_free(node); + ++result; + list->data = (char*)list->data - 1; + + return result; +} + +static unsigned _ls_try_remove(_ls_node_t* list, void* info, _ls_compare_t cmp, _ls_operation_t op) { + unsigned result = 0; + _ls_node_t* tmp = 0; + + mb_assert(list && cmp); + + tmp = list->next; + while(tmp) { + if(cmp(tmp, info) == 0) { + if(tmp->prev) + tmp->prev->next = tmp->next; + if(tmp->next) + tmp->next->prev = tmp->prev; + if(list->prev == tmp) + list->prev = tmp->prev; + if(list->prev == list) + list->prev = 0; + if(op) + op(tmp->data, tmp->extra); + safe_free(tmp); + ++result; + list->data = (char*)list->data - 1; + + break; + } + tmp = tmp->next; + } + + return result; +} + +static unsigned _ls_foreach(_ls_node_t* list, _ls_operation_t op) { + unsigned idx = 0; + int opresult = _OP_RESULT_NORMAL; + _ls_node_t* node = 0; + _ls_node_t* tmp = 0; + + mb_assert(list && op); + + node = list->next; + while(node) { + opresult = op(node->data, node->extra); + ++idx; + tmp = node; + node = node->next; + + if(_OP_RESULT_DEL_NODE == opresult) { + tmp->prev->next = node; + if(node) + node->prev = tmp->prev; + safe_free(tmp); + list->data = (char*)list->data - 1; + } + } + + return idx; +} + +static _ls_node_t* _ls_sort(_ls_node_t* _UNALIGNED_ARG * list, _ls_compare_t cmp) { + /* Copyright 2001 Simon Tatham, http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.c */ + bool_t is_circular = false, is_double = true; + _ls_node_t* p, * q, * e, * tail, * oldhead; + int insize, nmerges, psize, qsize, i; + _ls_node_t* lst = 0; + + mb_assert(list && *list && cmp); + + lst = *list; + if(lst) lst = lst->next; + + if(!lst) + return 0; + + insize = 1; + + while(1) { + p = lst; + oldhead = lst; + lst = 0; + tail = 0; + + nmerges = 0; + + while(p) { + nmerges++; + q = p; + psize = 0; + for(i = 0; i < insize; i++) { + psize++; + if(is_circular) + q = (q->next == oldhead ? 0 : q->next); + else + q = q->next; + if(!q) + break; + } + + qsize = insize; + + while(psize > 0 || (qsize > 0 && q)) { + if(psize == 0) { + e = q; q = q->next; qsize--; + if(is_circular && q == oldhead) q = 0; + } else if(qsize == 0 || !q) { + e = p; p = p->next; psize--; + if(is_circular && p == oldhead) p = 0; + } else if(cmp(p->data, q->data) <= 0) { + e = p; p = p->next; psize--; + if(is_circular && p == oldhead) p = 0; + } else { + e = q; q = q->next; qsize--; + if(is_circular && q == oldhead) q = 0; + } + + if(tail) + tail->next = e; + else + lst = e; + if(is_double) + e->prev = tail; + tail = e; + } + + p = q; + } + if(is_circular) { + tail->next = lst; + if(is_double) + lst->prev = tail; + } else { + tail->next = 0; + } + + if(nmerges <= 1) { + (*list)->next = lst; + (*list)->prev = tail; + + lst->prev = *list; + + return *list; + } + + insize *= 2; + } +} + +static unsigned _ls_count(_ls_node_t* list) { + union { void* p; unsigned u; } tmp; + + mb_assert(list); + + tmp.p = list->data; + + return tmp.u; +} + +static bool_t _ls_empty(_ls_node_t* list) { + bool_t result = false; + + mb_assert(list); + + result = 0 == list->next; + + return result; +} + +static void _ls_clear(_ls_node_t* list) { + _ls_node_t* tmp = 0; + + mb_assert(list); + + list->data = 0; + + tmp = list; + list = list->next; + tmp->next = 0; + tmp->prev = 0; + + while(list) { + tmp = list; + list = list->next; + safe_free(tmp); + } +} + +static void _ls_destroy(_ls_node_t* list) { + _ls_clear(list); + safe_free(list); +} + +static int _ls_free_extra(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + mb_unrefvar(data); + + mb_assert(extra); + + safe_free(extra); + + return result; +} + +/** Dictionary operations */ + +static unsigned _ht_hash_object(void* ht, void* d) { + unsigned result = 0; + _ht_node_t* self = (_ht_node_t*)ht; + _object_t* o = (_object_t*)d; + size_t i = 0; + unsigned h = 0; +#ifdef MB_ENABLE_CLASS + _object_t val; +#endif /* MB_ENABLE_CLASS */ + + mb_assert(ht); + + h = o->type; + switch(o->type) { + case _DT_STRING: + h = 5 * h + _ht_hash_string(ht, o->data.string); + if(self->array_size == 1) + result = 0; + else + result = h % self->array_size; + + break; +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + if(o->data.instance->hash) { + mb_interpreter_t* s = o->data.instance->ref.s; + _ls_node_t ast; + _ls_node_t* tmp = * + mb_value_t va[1]; + mb_make_nil(va[0]); + memset(&ast, 0, sizeof(_ls_node_t)); + if(_eval_routine(s, &tmp, va, 1, o->data.instance->hash, _has_routine_fun_arg, _pop_routine_fun_arg) == MB_FUNC_OK) { + _MAKE_NIL(&val); + _public_value_to_internal_object(&s->running_context->intermediate_value, &val); + if(val.type != _DT_INT) { + int ignored = MB_FUNC_OK; + _handle_error_on_obj(s, SE_RN_INTEGER_EXPECTED, s->source_file, o, MB_FUNC_ERR, _exit, ignored); + } + + o = &val; + } + } + + goto _default; +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + if(o->data.usertype_ref->hash) { + if(self->array_size == 1) { + result = 0; + } else { + h = 5 * h + o->data.usertype_ref->hash(o->data.usertype_ref->ref.s, o->data.usertype_ref->usertype); + result = h % self->array_size; + } + + break; + } + + goto _default; +#endif /* MB_ENABLE_USERTYPE_REF */ + default: +#if defined MB_ENABLE_CLASS || defined MB_ENABLE_USERTYPE_REF +_default: +#endif /* MB_ENABLE_CLASS || MB_ENABLE_USERTYPE_REF */ + if(self->array_size == 1) { + result = 0; + } else { + for(i = 0; i < sizeof(_raw_t); ++i) + h = 5 * h + o->data.raw[i]; + result = h % self->array_size; + } + + break; + } + + goto _exit; /* Avoid an unreferenced label warning */ + +_exit: + return result; +} + +static unsigned _ht_hash_string(void* ht, void* d) { + unsigned result = 0; + _ht_node_t* self = (_ht_node_t*)ht; + char* s = (char*)d; + unsigned h = 0; + + mb_assert(ht); + + if(self->array_size == 1) { + result = 0; + } else { + for( ; *s; ++s) + h = 5 * h + *s; + result = h % self->array_size; + } + + return result; +} + +static unsigned _ht_hash_intptr(void* ht, void* d) { + unsigned result = 0; + _ht_node_t* self = (_ht_node_t*)ht; + + mb_assert(ht); + + if(self->array_size == 1) { + result = 0; + } else { + uintptr_t u = (uintptr_t)d; + result = (unsigned)(u % self->array_size); + } + + return result; +} + +static unsigned _ht_hash_ref(void* ht, void* d) { + unsigned result = 0; + _ht_node_t* self = (_ht_node_t*)ht; + _ref_t* ref = (_ref_t*)d; + + mb_assert(ht); + + if(self->array_size == 1) { + result = 0; + } else { + uintptr_t u = (uintptr_t)ref; + result = (unsigned)(u % self->array_size); + } + + return result; +} + +static int _ht_cmp_object(void* d1, void* d2) { + _object_t* o1 = (_object_t*)d1; + _object_t* o2 = (_object_t*)d2; + int i = 0; +#ifdef MB_ENABLE_CLASS + _routine_t* cmp = 0; + _object_t val; + bool_t fst = true; +#endif /* MB_ENABLE_CLASS */ + + if(o1->type < o2->type) + return -1; + else if(o1->type > o2->type) + return 1; + + switch(o1->type) { + case _DT_STRING: + return _ht_cmp_string(o1->data.string, o2->data.string); +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + if(o1->data.instance->compare) { + cmp = o1->data.instance->compare; + fst = true; + } else if(o2->data.instance->compare) { + cmp = o2->data.instance->compare; + fst = false; + } + if(cmp) { + mb_interpreter_t* s = o1->data.instance->ref.s; + _ls_node_t ast; + _ls_node_t* tmp = * + mb_value_t va[1]; + mb_make_nil(va[0]); + _internal_object_to_public_value(fst ? o2 : o1, &va[0]); + memset(&ast, 0, sizeof(_ls_node_t)); + if(_eval_routine(s, &tmp, va, 1, cmp, _has_routine_fun_arg, _pop_routine_fun_arg) == MB_FUNC_OK) { + _MAKE_NIL(&val); + _public_value_to_internal_object(&s->running_context->intermediate_value, &val); + if(val.type != _DT_INT) { + int ignored = MB_FUNC_OK; + _handle_error_on_obj(s, SE_RN_INTEGER_EXPECTED, s->source_file, o1, MB_FUNC_ERR, _exit, ignored); + } + + return (int)(fst ? val.data.integer : -val.data.integer); + } + } + + goto _default; +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + if(o1->data.usertype_ref->cmp) + return o1->data.usertype_ref->cmp(o1->data.usertype_ref->ref.s, o1->data.usertype_ref->usertype, o2->data.usertype_ref->usertype); + else if(o2->data.usertype_ref->cmp) + return o2->data.usertype_ref->cmp(o1->data.usertype_ref->ref.s, o1->data.usertype_ref->usertype, o2->data.usertype_ref->usertype); + + goto _default; +#endif /* MB_ENABLE_USERTYPE_REF */ + default: +#if defined MB_ENABLE_CLASS || defined MB_ENABLE_USERTYPE_REF +_default: +#endif /* MB_ENABLE_CLASS || MB_ENABLE_USERTYPE_REF */ + if(mb_is_little_endian()) { + for(i = (int)sizeof(_raw_t) - 1; i >= 0; --i) { + if(o1->data.raw[i] < o2->data.raw[i]) + return -1; + else if(o1->data.raw[i] > o2->data.raw[i]) + return 1; + } + } else { + for(i = 0; i < (int)sizeof(_raw_t); ++i) { + if(o1->data.raw[i] < o2->data.raw[i]) + return -1; + else if(o1->data.raw[i] > o2->data.raw[i]) + return 1; + } + } + + break; + } + + goto _exit; /* Avoid an unreferenced label warning */ + +_exit: + return 0; +} + +static int _ht_cmp_string(void* d1, void* d2) { + char* s1 = (char*)d1; + char* s2 = (char*)d2; + + return strcmp(s1, s2); +} + +static int _ht_cmp_intptr(void* d1, void* d2) { + intptr_t i1 = (intptr_t)d1; + intptr_t i2 = (intptr_t)d2; + + if(i1 < i2) + return -1; + else if(i1 > i2) + return 1; + + return 0; +} + +static int _ht_cmp_ref(void* d1, void* d2) { + _ref_t* r1 = (_ref_t*)d1; + _ref_t* r2 = (_ref_t*)d2; + intptr_t i = (intptr_t)r1 - (intptr_t)r2; + + if(i < 0) + return -1; + else if(i > 0) + return 1; + + return 0; +} + +static _ht_node_t* _ht_create(unsigned size, _ht_compare_t cmp, _ht_hash_t hs, _ls_operation_t freeextra) { + const unsigned array_size = size ? size : _HT_ARRAY_SIZE_DEFAULT; + _ht_node_t* result = 0; + unsigned ul = 0; + + if(!cmp) + cmp = _ht_cmp_intptr; + if(!hs) + hs = _ht_hash_intptr; + + result = (_ht_node_t*)mb_malloc(sizeof(_ht_node_t)); + result->free_extra = freeextra; + result->compare = cmp; + result->hash = hs; + result->array_size = array_size; + result->count = 0; +#if _LAZY_HASH_TABLE + mb_unrefvar(ul); + + result->array = 0; +#else /* _LAZY_HASH_TABLE */ + result->array = (_ls_node_t**)mb_malloc(sizeof(_ls_node_t*) * result->array_size); + for(ul = 0; ul < result->array_size; ++ul) + result->array[ul] = _ls_create(); +#endif /* _LAZY_HASH_TABLE */ + + return result; +} + +static _ls_node_t* _ht_find(_ht_node_t* ht, void* key) { + _ls_node_t* result = 0; + _ls_node_t* bucket = 0; + unsigned hash_code = 0; + + mb_assert(ht && key); + + hash_code = ht->hash(ht, key); + if(ht->array && ht->array[hash_code]) { + bucket = ht->array[hash_code]; + bucket = bucket->next; + } + while(bucket) { + if(ht->compare(bucket->extra, key) == 0) { + result = bucket; + + break; + } + bucket = bucket->next; + } + + return result; +} + +static unsigned _ht_set_or_insert(_ht_node_t* ht, void* key, void* value) { + unsigned result = 0; + _ls_node_t* bucket = 0; + unsigned hash_code = 0; + unsigned ul = 0; + + mb_assert(ht && key); + + bucket = _ht_find(ht, key); + if(bucket) { /* Update */ + bucket->data = value; + ++result; + } else { /* Insert */ + hash_code = ht->hash(ht, key); + if(!ht->array) { + ht->array = (_ls_node_t**)mb_malloc(sizeof(_ls_node_t*) * ht->array_size); + for(ul = 0; ul < ht->array_size; ++ul) + ht->array[ul] = 0; + } + if(!ht->array[hash_code]) + ht->array[hash_code] = _ls_create(); + bucket = ht->array[hash_code]; + bucket = _ls_pushback(bucket, value); + mb_assert(bucket); + bucket->extra = key; + ++ht->count; + ++result; + } + + return result; +} + +static unsigned _ht_remove(_ht_node_t* ht, void* key, _ls_compare_t cmp) { + unsigned result = 0; + unsigned hash_code = 0; + _ls_node_t* bucket = 0; + + mb_assert(ht && key); + + if(!cmp) + cmp = _ls_cmp_extra; + + bucket = _ht_find(ht, key); + hash_code = ht->hash(ht, key); + if(ht->array && ht->array[hash_code]) { + bucket = ht->array[hash_code]; + result = _ls_try_remove(bucket, key, cmp, ht->free_extra); + ht->count -= result; + } + + return result; +} + +static unsigned _ht_foreach(_ht_node_t* ht, _ht_operation_t op) { + unsigned result = 0; + _ls_node_t* bucket = 0; + unsigned ul = 0; + + if(ht->array) { + for(ul = 0; ul < ht->array_size; ++ul) { + bucket = ht->array[ul]; + if(bucket) + result += _ls_foreach(bucket, op); + } + } + + return result; +} + +static unsigned _ht_count(_ht_node_t* ht) { + mb_assert(ht); + + return ht->count; +} + +static void _ht_clear(_ht_node_t* ht) { + unsigned ul = 0; + + mb_assert(ht); + + if(ht->array) { + for(ul = 0; ul < ht->array_size; ++ul) { + if(ht->array[ul]) + _ls_clear(ht->array[ul]); + } + ht->count = 0; + } +} + +static void _ht_destroy(_ht_node_t* ht) { + unsigned ul = 0; + + mb_assert(ht); + + if(ht->array) { + if(ht->free_extra) + _ht_foreach(ht, ht->free_extra); + + for(ul = 0; ul < ht->array_size; ++ul) { + if(ht->array[ul]) + _ls_destroy(ht->array[ul]); + } + + safe_free(ht->array); + } + safe_free(ht); +} + +static int _ht_remove_existing(void* data, void* extra, _ht_node_t* ht) { + int result = _OP_RESULT_NORMAL; + mb_unrefvar(data); + + if(_ht_find(ht, extra)) + _ht_remove(ht, extra, 0); + + return result; +} + +/** Memory manipulations */ + +/* Initialize a chunk of resizable dynamic buffer */ +static void _init_dynamic_buffer(_dynamic_buffer_t* buf) { + mb_assert(buf); + + memset(buf->bytes, 0, sizeof(buf->bytes)); + buf->pointer.charp = buf->bytes; + buf->size = sizeof(buf->bytes); +} + +/* Dispose a chunk of resizable dynamic buffer */ +static void _dispose_dynamic_buffer(_dynamic_buffer_t* buf) { + mb_assert(buf); + + if(buf->pointer.charp != buf->bytes) { + safe_free(buf->pointer.charp); + } + buf->pointer.charp = 0; + buf->size = 0; +} + +/* Get the element count of a chunk of resizable dynamic buffer */ +static size_t _countof_dynamic_buffer(_dynamic_buffer_t* buf, size_t es) { + mb_assert(buf); + + return buf->size / es; +} + +/* Resize a chunk of resizable dynamic buffer */ +static void _resize_dynamic_buffer(_dynamic_buffer_t* buf, size_t es, size_t c) { + size_t as = es * c; + + mb_assert(buf); + + if(as > buf->size) { + if(buf->pointer.charp != buf->bytes) { + safe_free(buf->pointer.charp); + } + buf->pointer.charp = (char*)mb_malloc(as); + buf->size = as; + } +} + +/* Allocate a chunk of memory with a specific size */ +static void* mb_malloc(size_t s) { + char* ret = 0; + size_t rs = s; + +#ifdef MB_ENABLE_ALLOC_STAT + if(!_MB_CHECK_MEM_TAG_SIZE(size_t, s)) + return 0; + rs += _MB_MEM_TAG_SIZE; +#endif /* MB_ENABLE_ALLOC_STAT */ + if(_mb_allocate_func) + ret = _mb_allocate_func((unsigned)rs); + else + ret = (char*)malloc(rs); + mb_assert(ret); +#ifdef MB_ENABLE_ALLOC_STAT + _mb_allocated += s; + ret += _MB_MEM_TAG_SIZE; + _MB_WRITE_MEM_TAG_SIZE(ret, s); +#endif /* MB_ENABLE_ALLOC_STAT */ + + return (void*)ret; +} + +/* Free a chunk of memory */ +static void mb_free(void* p) { + mb_assert(p); + +#ifdef MB_ENABLE_ALLOC_STAT + do { + size_t os = _MB_READ_MEM_TAG_SIZE(p); + _mb_allocated -= os; + p = (char*)p - _MB_MEM_TAG_SIZE; + } while(0); +#endif /* MB_ENABLE_ALLOC_STAT */ + if(_mb_free_func) + _mb_free_func((char*)p); + else + free(p); +} + +/* Compare two chunks of memory */ +static int mb_memcmp(void* l, void* r, size_t s) { + unsigned char* lc = (unsigned char*)l; + unsigned char* rc = (unsigned char*)r; + int i = 0; + + if(mb_is_little_endian()) { + for(i = (int)s - 1; i >= 0; i--) { + if(lc[i] < rc[i]) + return -1; + else if(lc[i] > rc[i]) + return 1; + } + } else { + for(i = 0; i < (int)s; i++) { + if(lc[i] < rc[i]) + return -1; + else if(lc[i] > rc[i]) + return 1; + } + } + + return 0; +} + +/* Detect whether a chunk of memory contains any non-zero byte */ +static size_t mb_memtest(void* p, size_t s) { + size_t result = 0; + size_t i = 0; + + for(i = 0; i < s; i++) + result += ((unsigned char*)p)[i]; + + return result; +} + +/* Get the length of a string */ +static size_t mb_strlen(const char* s) { + if(_mb_strlen_func) + return _mb_strlen_func(s); + + return strlen(s); +} + +/* Duplicate a string */ +static char* mb_strdup(const char* p, size_t s) { +#ifdef MB_ENABLE_ALLOC_STAT + if(!s) { + s = _MB_READ_MEM_TAG_SIZE(p); + } + + return mb_memdup(p, (unsigned)s); +#else /* MB_ENABLE_ALLOC_STAT */ + if(s) + return mb_memdup(p, (unsigned)s); + + return mb_memdup(p, (unsigned)(mb_strlen(p) + 1)); +#endif /* MB_ENABLE_ALLOC_STAT */ +} + +/* Change a string to upper case */ +static char* mb_strupr(char* s) { + char* t = s; + + while(*s) { + *s = toupper(*s); + ++s; + } + + return t; +} + +/* Determine whether it's running on a little endian platform */ +static bool_t mb_is_little_endian(void) { + int i = 1; + + return ((char*)&i)[0] == 1; +} + +/** Unicode handling */ + +#if defined MB_CP_VC && defined MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING +/* Map a UTF8 character string to a UTF16 (wide character) string */ +static int mb_bytes_to_wchar(const char* sz, wchar_t** out, size_t size) { + int result = MultiByteToWideChar(CP_UTF8, 0, sz, -1, 0, 0); + if((int)size >= result) + MultiByteToWideChar(CP_UTF8, 0, sz, -1, *out, result); + + return result; +} + +/* Map an ANSI character string to a UTF16 (wide character) string */ +static int mb_bytes_to_wchar_ansi(const char* sz, wchar_t** out, size_t size) { + int result = MultiByteToWideChar(CP_ACP, 0, sz, -1, 0, 0); + if((int)size >= result) + MultiByteToWideChar(CP_ACP, 0, sz, -1, *out, result); + + return result; +} + +/* Map a UTF16 (wide character) string to a UTF8 character string */ +static int mb_wchar_to_bytes(const wchar_t* sz, char** out, size_t size) { + int result = WideCharToMultiByte(CP_UTF8, 0, sz, -1, 0, 0, 0, 0); + if((int)size >= result) + WideCharToMultiByte(CP_UTF8, 0, sz, -1, *out, result, 0, 0); + + return result; +} +#endif /* MB_CP_VC && MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING */ + +/* Determine whether a string begins with a BOM, and ignore it */ +static int mb_uu_getbom(const char** ch) { +#ifdef __cplusplus + signed char** ptr = (signed char**)ch; +#else /* __cplusplus */ + char** ptr = (char**)ch; +#endif /* __cplusplus */ + + if(!ptr || !(*ptr)) + return 0; + + if((*ptr)[0] == -17 && (*ptr)[1] == -69 && (*ptr)[2] == -65) { + *ptr += 3; + + return 3; + } else if((*ptr)[0] == -2 && (*ptr)[1] == -1) { + *ptr += 2; + + return 2; + } + + return 0; +} + +#ifdef MB_ENABLE_UNICODE +/* Determine whether a buffer starts with a UTF8 encoded character, and return taken byte count */ +static int mb_uu_ischar(const char* ch) { + /* Copyright 2008, 2009 Bjoern Hoehrmann, http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ */ +#define _TAKE(__ch, __c, __r) do { __c = *__ch++; __r++; } while(0) +#define _COPY(__ch, __c, __r, __cp) do { _TAKE(__ch, __c, __r); __cp = (__cp << 6) | ((unsigned char)__c & 0x3Fu); } while(0) +#define _TRANS(__m, __cp, __g) do { __cp &= ((__g[(unsigned char)c] & __m) != 0); } while(0) +#define _TAIL(__ch, __c, __r, __cp, __g) do { _COPY(__ch, __c, __r, __cp); _TRANS(0x70, __cp, __g); } while(0) + + MBCONST static const unsigned char range[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 + }; + + int result = 0; + unsigned codepoint = 0; + unsigned char type = 0; + char c = 0; + + if(!ch) + return 0; + + _TAKE(ch, c, result); + if(!(c & 0x80)) { + codepoint = (unsigned char)c; + + return 1; + } + + type = range[(unsigned char)c]; + codepoint = (0xFF >> type) & (unsigned char)c; + + switch(type) { + case 2: _TAIL(ch, c, result, codepoint, range); return result; + case 3: _TAIL(ch, c, result, codepoint, range); _TAIL(ch, c, result, codepoint, range); return result; + case 4: _COPY(ch, c, result, codepoint); _TRANS(0x50, codepoint, range); _TAIL(ch, c, result, codepoint, range); return result; + case 5: _COPY(ch, c, result, codepoint); _TRANS(0x10, codepoint, range); _TAIL(ch, c, result, codepoint, range); _TAIL(ch, c, result, codepoint, range); return result; + case 6: _TAIL(ch, c, result, codepoint, range); _TAIL(ch, c, result, codepoint, range); _TAIL(ch, c, result, codepoint, range); return result; + case 10: _COPY(ch, c, result, codepoint); _TRANS(0x20, codepoint, range); _TAIL(ch, c, result, codepoint, range); return result; + case 11: _COPY(ch, c, result, codepoint); _TRANS(0x60, codepoint, range); _TAIL(ch, c, result, codepoint, range); _TAIL(ch, c, result, codepoint, range); return result; + default: return 0; + } + +#undef _TAKE +#undef _COPY +#undef _TRANS +#undef _TAIL +} + +/* Tell how many UTF8 character are there in a string */ +static int mb_uu_strlen(const char* ch) { + int result = 0; + + if(!ch) + return 0; + + while(*ch) { + int t = mb_uu_ischar(ch); + if(t <= 0) + return t; + ch += t; + result++; + } + + return result; +} + +/* Retrieve a sub string of a UTF8 string */ +static int mb_uu_substr(const char* ch, int begin, int count, char** o) { + int cnt = 0; + const char* b = 0; + const char* e = 0; + int l = 0; + + if(!ch || begin < 0 || count <= 0 || !o) + return -1; + + while(*ch) { + int t = mb_uu_ischar(ch); + if(t <= 0) + return t; + if(cnt == begin) { + b = ch; + + break; + } + ch += t; + cnt++; + } + + while(*ch) { + int t = mb_uu_ischar(ch); + if(t <= 0) + return t; + if(cnt == begin + count) { + e = ch; + + break; + } + ch += t; + e = ch; + cnt++; + } + + if(!(*ch) && (cnt != begin + count)) + return -1; + + l = (int)(e - b); + *o = (char*)mb_malloc(l + 1); + memcpy(*o, b, l); + (*o)[l] = _ZERO_CHAR; + + return l; +} +#endif /* MB_ENABLE_UNICODE */ + +/** Expression processing */ + +/* Determine whether a function is an operator */ +static bool_t _is_operator(mb_func_t op) { + return ( + (op == _core_dummy_assign) || + (op == _core_add) || + (op == _core_min) || + (op == _core_mul) || + (op == _core_div) || + (op == _core_mod) || + (op == _core_pow) || + (op == _core_open_bracket) || + (op == _core_close_bracket) || + (op == _core_equal) || + (op == _core_less) || + (op == _core_greater) || + (op == _core_less_equal) || + (op == _core_greater_equal) || + (op == _core_not_equal) || + (op == _core_and) || + (op == _core_or) || + (op == _core_is) + ); +} + +/* Determine whether a function is for flow control */ +static bool_t _is_flow(mb_func_t op) { + return ( + (op == _core_if) || + (op == _core_then) || + (op == _core_elseif) || + (op == _core_else) || + (op == _core_endif) || + (op == _core_for) || + (op == _core_to) || + (op == _core_step) || + (op == _core_next) || + (op == _core_while) || + (op == _core_wend) || + (op == _core_do) || + (op == _core_until) || + (op == _core_exit) || + (op == _core_goto) || + (op == _core_gosub) || + (op == _core_return) || + (op == _core_end) + ); +} + +/* Determine whether a function is unary */ +static bool_t _is_unary(mb_func_t op) { + return (op == _core_neg) || (op == _core_not); +} + +/* Determine whether a function is binary */ +static bool_t _is_binary(mb_func_t op) { + return ( + (op == _core_add) || + (op == _core_min) || + (op == _core_mul) || + (op == _core_div) || + (op == _core_mod) || + (op == _core_pow) || + (op == _core_equal) || + (op == _core_less) || + (op == _core_greater) || + (op == _core_less_equal) || + (op == _core_greater_equal) || + (op == _core_not_equal) || + (op == _core_and) || + (op == _core_or) || + (op == _core_is) + ); +} + +/* Get the priority of two operators */ +static char _get_priority(mb_func_t op1, mb_func_t op2) { + char result = _ZERO_CHAR; + int idx1 = 0; + int idx2 = 0; + + mb_assert(op1 && op2); + + idx1 = _get_priority_index(op1); + idx2 = _get_priority_index(op2); + mb_assert(idx1 < countof(_PRECEDE_TABLE) && idx2 < countof(_PRECEDE_TABLE[0])); + result = _PRECEDE_TABLE[idx1][idx2]; + + return result; +} + +/* Get the index of an operator in the priority table */ +static int _get_priority_index(mb_func_t op) { + int i = 0; + + MBCONST mb_func_t funcs[] = { + _core_add, + _core_min, + _core_mul, + _core_div, + _core_mod, + _core_pow, + _core_open_bracket, + _core_close_bracket, + _core_dummy_assign, + _core_greater, + _core_less, + _core_greater_equal, + _core_less_equal, + _core_equal, + _core_not_equal, + _core_and, + _core_or, + _core_not, + _core_neg, + _core_is + }; + + mb_assert(op); + + for(i = 0; i < countof(funcs); i++) { + if(op == funcs[i]) + return i; + } + + mb_assert(0 && "Unknown operator."); + + return -1; +} + +/* Operate two operands */ +static _object_t* _operate_operand(mb_interpreter_t* s, _object_t* optr, _object_t* opnd1, _object_t* opnd2, int* status) { + _object_t* result = 0; + _tuple3_t tp; + _tuple3_t* tpptr = 0; + int ret = 0; + + mb_assert(s && optr); + mb_assert(optr->type == _DT_FUNC); + + if(!opnd1) + return result; + + result = _create_object(); + + memset(&tp, 0, sizeof(_tuple3_t)); + tp.e1 = opnd1; + tp.e2 = opnd2; + tp.e3 = result; + tpptr = &tp; + + ret = (optr->data.func->pointer)(s, (void**)&tpptr); + if(status) + *status = ret; + if(ret != MB_FUNC_OK) { + if(ret != MB_FUNC_WARNING) { + safe_free(result); + } + if(_set_current_error(s, SE_RN_FAILED_TO_OPERATE, 0)) { +#ifdef MB_ENABLE_SOURCE_TRACE + _set_error_pos(s, optr->source_pos, optr->source_row, optr->source_col); +#else /* MB_ENABLE_SOURCE_TRACE */ + _set_error_pos(s, 0, 0, 0); +#endif /* MB_ENABLE_SOURCE_TRACE */ + } + } + + return result; +} + +/* Determine whether an object is an expression termination */ +static bool_t _is_expression_terminal(mb_interpreter_t* s, _object_t* obj) { + bool_t result = false; + + mb_assert(s && obj); + + result = ( + (obj->type == _DT_EOS) || + (obj->type == _DT_SEP) || + (obj->type == _DT_FUNC && + (obj->data.func->pointer == _core_then || + obj->data.func->pointer == _core_elseif || + obj->data.func->pointer == _core_else || + obj->data.func->pointer == _core_endif || + obj->data.func->pointer == _core_to || + obj->data.func->pointer == _core_step)) + ); + + return result; +} + +/* Determine whether an object is an unexpected calculation result */ +static bool_t _is_unexpected_calc_type(mb_interpreter_t* s, _object_t* obj) { + mb_assert(s); + + return !obj || ( + (obj->type == _DT_FUNC) || + (obj->type == _DT_LABEL) || +#ifdef MB_ENABLE_LAMBDA + (obj->type == _DT_OUTER_SCOPE) || +#endif /* MB_ENABLE_LAMBDA */ + (obj->type == _DT_SEP) || +#ifdef MB_ENABLE_SOURCE_TRACE + (obj->type == _DT_PREV_IMPORT) || + (obj->type == _DT_POST_IMPORT) || +#endif /* MB_ENABLE_SOURCE_TRACE */ + (obj->type == _DT_EOS) + ); +} + +/* Determine whether an object is a referenced calculation result */ +static bool_t _is_referenced_calc_type(mb_interpreter_t* s, _object_t* obj) { + mb_assert(s && obj); + + return ( +#ifdef MB_ENABLE_USERTYPE_REF + (obj->type == _DT_USERTYPE_REF) || +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_COLLECTION_LIB + (obj->type == _DT_LIST) || (obj->type == _DT_DICT) || (obj->type == _DT_LIST_IT) || (obj->type == _DT_DICT_IT) || +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + (obj->type == _DT_CLASS) || +#endif /* MB_ENABLE_CLASS */ + (obj->type == _DT_ARRAY) || + (obj->type == _DT_ROUTINE) + ); +} + +/* Calculate an expression */ +static int _calc_expression(mb_interpreter_t* s, _ls_node_t** l, _object_t** val) { + int result = 0; + _ls_node_t* ast = 0; + _running_context_t* running = 0; + _ls_node_t* garbage = 0; + _ls_node_t* optr = 0; + _ls_node_t* opnd = 0; + _object_t* c = 0; + _object_t* x = 0; + _object_t* a = 0; + _object_t* b = 0; + _object_t* r = 0; + _object_t* theta = 0; + char pri = _ZERO_CHAR; + int* inep = 0; + int f = 0; + + _object_t* guard_val = 0; + int bracket_count = 0; + bool_t hack = false; + _ls_node_t* errn = 0; + bool_t gce = true; + + mb_assert(s && l); + + gce = mb_get_gc_enabled(s); + mb_set_gc_enabled(s, false); + + running = s->running_context; + ast = *l; + + if(!ast) { + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + c = (_object_t*)ast->data; +#ifdef MB_PREFER_SPEED + if(c->is_const) { + ast = ast->next; + + goto _fast; + } +#endif /* MB_PREFER_SPEED */ + + optr = _ls_create(); + opnd = _ls_create(); + +#define _LAZY_INIT_GLIST do { if(!garbage) garbage = _ls_create(); } while(0) + + inep = (int*)mb_malloc(sizeof(int)); + *inep = 0; + _ls_pushback(s->in_neg_expr, inep); + + do { + if(c->type == _DT_STRING) { + if(ast->next) { + _object_t* _fsn = (_object_t*)ast->next->data; + if(_IS_FUNC(_fsn, _core_add) || _IS_FUNC(_fsn, _core_equal) || _IS_FUNC(_fsn, _core_not_equal) || _IS_FUNC(_fsn, _core_and) || _IS_FUNC(_fsn, _core_or) || _IS_FUNC(_fsn, _core_is)) + break; + } + + (*val)->type = _DT_STRING; + (*val)->data.string = c->data.string; + (*val)->is_ref = true; + ast = ast->next; + + goto _exit; + } + } while(0); + guard_val = c; + ast = ast->next; + _ls_pushback(optr, _exp_assign); + while( + c && + (!(c->type == _DT_FUNC && strcmp(c->data.func->name, _DUMMY_ASSIGN_CHAR) == 0) || + !(((_object_t*)(_ls_back(optr)->data))->type == _DT_FUNC && strcmp(((_object_t*)(_ls_back(optr)->data))->data.func->name, _DUMMY_ASSIGN_CHAR) == 0)) + ) { + if(!hack) { + if(_IS_FUNC(c, _core_open_bracket)) { + ++bracket_count; + } else if(_IS_FUNC(c, _core_close_bracket)) { + --bracket_count; + if(bracket_count < 0) { + c = _exp_assign; + ast = ast->prev; + + continue; + } + } + } + hack = false; + if(!(c->type == _DT_FUNC && _is_operator(c->data.func->pointer))) { + if(_is_expression_terminal(s, c)) { + c = _exp_assign; + if(ast) + ast = ast->prev; + if(bracket_count) { + _object_t* cb = _create_object(); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, cb); + _MAKE_NIL(cb); + cb->type = _DT_FUNC; + cb->data.func = (_func_t*)mb_malloc(sizeof(_func_t)); + memset(cb->data.func, 0, sizeof(_func_t)); + cb->data.func->name = mb_strdup(")", 2); + cb->data.func->pointer = _core_close_bracket; + while(bracket_count) { + _ls_pushback(optr, cb); + bracket_count--; + f = 0; + } + errn = ast; + } + } else { + if(c->type == _DT_ARRAY) { +#ifdef MB_ENABLE_CLASS + if(s->last_instance) { + _ls_node_t* cs = _search_identifier_in_scope_chain(s, 0, c->data.array->name, _PATHING_NORMAL, 0, 0); + if(cs) + c = (_object_t*)cs->data; + } +#endif /* MB_ENABLE_CLASS */ +_array: + if(ast && !_IS_FUNC(((_object_t*)ast->data), _core_open_bracket)) { + _ls_pushback(opnd, c); + f++; + } else { + unsigned arr_idx = 0; + mb_value_u arr_val; + _data_e arr_type; + _object_t* arr_elem = 0; + ast = ast->prev; + result = _get_array_index(s, &ast, c, &arr_idx, 0); + if(result != MB_FUNC_OK) { + _handle_error_on_obj(s, SE_RN_CALCULATION_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + ast = ast->next; + _get_array_elem(s, c->data.array, arr_idx, &arr_val, &arr_type); + arr_elem = _create_object(); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, arr_elem); + arr_elem->type = arr_type; + arr_elem->is_ref = true; + _copy_bytes(arr_elem->data.bytes, arr_val.bytes); + if(f) { + _handle_error_on_obj(s, SE_RN_OPERATOR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + _ls_pushback(opnd, arr_elem); + f++; + } + } else if(c->type == _DT_FUNC) { + if(ast) ast = ast->prev; + if(_IS_UNARY_FUNC(c)) { +#ifdef MB_ENABLE_STACK_TRACE + _ls_pushback(s->stack_frames, c->data.func->name); +#endif /* MB_ENABLE_STACK_TRACE */ + result = (c->data.func->pointer)(s, (void**)&ast); +#ifdef MB_ENABLE_STACK_TRACE + _ls_popback(s->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ + } else { + int calc_depth = running->calc_depth; + running->calc_depth = _INFINITY_CALC_DEPTH; +#ifdef MB_ENABLE_STACK_TRACE + _ls_pushback(s->stack_frames, c->data.func->name); +#endif /* MB_ENABLE_STACK_TRACE */ + result = (c->data.func->pointer)(s, (void**)&ast); +#ifdef MB_ENABLE_STACK_TRACE + _ls_popback(s->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ + running->calc_depth = calc_depth; + } + if(result != MB_FUNC_OK) { + _handle_error_on_obj(s, SE_RN_CALCULATION_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + c = _create_object(); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, c); + result = _public_value_to_internal_object(&running->intermediate_value, c); + switch(c->type) { + case _DT_ROUTINE: + if(c->data.routine->type != MB_RT_SCRIPT) + break; + mb_make_nil(running->intermediate_value); + /* Fall through */ + case _DT_STRING: + if(running->donot_ref_intermediate_value) + c->is_ref = false; + else + c->is_ref = true; + default: /* Do nothing */ + break; + } + if(result != MB_FUNC_OK) + goto _error; + if(f) { + _handle_error_on_obj(s, SE_RN_OPERATOR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + if(_is_array(c)) { + goto _array; + } else { + if(ast && _IS_FUNC(ast->data, _core_open_bracket)) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + } + _ls_pushback(opnd, c); + f++; + } else if(c->type == _DT_ROUTINE) { +_routine: + do { +#ifdef MB_ENABLE_CLASS + bool_t calling = false; + _object_t* obj = 0; + _ls_node_t* fn = 0; +#endif /* MB_ENABLE_CLASS */ + if(!ast) + break; + ast = ast->prev; +#ifdef MB_ENABLE_CLASS + calling = s->calling; + s->calling = false; +#endif /* MB_ENABLE_CLASS */ + result = _eval_routine(s, &ast, 0, 0, c->data.routine, _has_routine_lex_arg, _pop_routine_lex_arg); +#ifdef MB_ENABLE_CLASS + s->calling = calling; +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_CLASS + obj = ast ? (_object_t*)ast->data : 0; + if(_IS_VAR(obj) && _is_valid_class_accessor_following_routine(s, obj->data.variable, ast, &fn)) { + if(fn) { + if(ast) ast = ast->next; + obj = (_object_t*)fn->data; + if(_IS_VAR(obj)) { + c = obj; + + goto _var; + } + } + } +#endif /* MB_ENABLE_CLASS */ + } while(0); + if(ast) + ast = ast->prev; + if(result == MB_FUNC_END) + goto _error; + if(result != MB_FUNC_OK) { + _handle_error_on_obj(s, SE_RN_CALCULATION_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + c = _create_object(); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, c); + result = _public_value_to_internal_object(&running->intermediate_value, c); + if(result != MB_FUNC_OK) + goto _error; + if(f) { + _handle_error_on_obj(s, SE_RN_OPERATOR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + _ls_pushback(opnd, c); + f++; + } else if(c->type == _DT_VAR && c->data.variable->data->type == _DT_ARRAY) { + unsigned arr_idx = 0; + mb_value_u arr_val; + _data_e arr_type; + _object_t* arr_elem = 0; + + if(ast && !_IS_FUNC(((_object_t*)ast->data), _core_open_bracket)) { + c = c->data.variable->data; + _ls_pushback(opnd, c); + f++; + } else { + ast = ast->prev; + result = _get_array_index(s, &ast, 0, &arr_idx, 0); + if(result != MB_FUNC_OK) { + _handle_error_on_obj(s, SE_RN_CALCULATION_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + ast = ast->next; + _get_array_elem(s, c->data.variable->data->data.array, arr_idx, &arr_val, &arr_type); + arr_elem = _create_object(); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, arr_elem); + arr_elem->type = arr_type; + arr_elem->is_ref = true; + if(arr_type == _DT_INT) { + arr_elem->data.integer = arr_val.integer; + } else if(arr_type == _DT_REAL) { + arr_elem->data.float_point = arr_val.float_point; + } else if(arr_type == _DT_STRING) { + arr_elem->data.string = arr_val.string; + } else if(arr_type == _DT_USERTYPE) { + arr_elem->data.usertype = arr_val.usertype; + } else { +#ifdef MB_SIMPLE_ARRAY + mb_assert(0 && "Unsupported."); +#else /* MB_SIMPLE_ARRAY */ + _copy_bytes(arr_elem->data.bytes, arr_val.bytes); +#endif /* MB_SIMPLE_ARRAY */ + } + if(f) { + _handle_error_on_obj(s, SE_RN_OPERATOR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + _ls_pushback(opnd, arr_elem); + f++; + } + } else { + if(c->type == _DT_VAR) { + do { + _ls_node_t* cs = _search_identifier_in_scope_chain(s, 0, c->data.variable->name, +#ifdef MB_ENABLE_CLASS + _PU(c->data.variable->pathing), +#else /* MB_ENABLE_CLASS */ + 0, +#endif /* MB_ENABLE_CLASS */ + 0, + 0 + ); + if(cs) { +#ifdef MB_ENABLE_USERTYPE_REF + _ls_node_t* fn = ast; + if(fn) fn = fn->prev; + if(_try_call_func_on_usertype_ref(s, &fn, c, cs, 0)) { + ast = fn; + c = _create_object(); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, c); + _public_value_to_internal_object(&running->intermediate_value, c); + _REF(c) + } else { +#else /* MB_ENABLE_USERTYPE_REF */ + { +#endif /* MB_ENABLE_USERTYPE_REF */ + c = (_object_t*)cs->data; + if(c && c->type == _DT_VAR && c->data.variable->data->type == _DT_ROUTINE) { + c = c->data.variable->data; + } + if(ast && ast && _IS_FUNC(ast->data, _core_open_bracket)) { + if(c && c->type == _DT_ROUTINE) + goto _routine; + } + } + } + } while(0); +#ifdef MB_ENABLE_CLASS +_var: +#endif /* MB_ENABLE_CLASS */ + if(ast) { + _object_t* _err_or_bracket = (_object_t*)ast->data; + do { +#ifdef MB_ENABLE_COLLECTION_LIB + if(_IS_VAR(c) && _IS_COLL(c->data.variable->data)) { + if(_IS_FUNC(_err_or_bracket, _core_open_bracket)) { + int_t idx = 0; + mb_value_t key; + mb_value_t ret; + _object_t* ocoll = c->data.variable->data; + + mb_make_nil(ret); + + *l = ast->prev; + + _mb_check_exit(mb_attempt_open_bracket(s, (void**)l), _error); + + switch(ocoll->type) { + case _DT_LIST: + _mb_check_exit(mb_pop_int(s, (void**)l, &idx), _error); + if(!_at_list(ocoll->data.list, idx, &ret)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, TON(l), MB_FUNC_ERR, _error, result); + } + + break; + case _DT_DICT: + mb_make_nil(key); + _mb_check_exit(mb_pop_value(s, (void**)l, &key), _error); + if(!_find_dict(ocoll->data.dict, &key, &ret)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, TON(l), MB_FUNC_ERR, _error, result); + } + + break; + default: /* Do nothing */ + break; + } + + _mb_check_exit(mb_attempt_close_bracket(s, (void**)l), _error); + + c = _create_object(); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, c); + _public_value_to_internal_object(&ret, c); + + ast = *l; + } + + break; + } +#endif /* MB_ENABLE_COLLECTION_LIB */ + if(_IS_FUNC(_err_or_bracket, _core_open_bracket)) { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + } while(0); + } + } + if(f) { + _handle_error_on_obj(s, SE_RN_OPERATOR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + _ls_pushback(opnd, c); + f++; + } + if(running->calc_depth != _INFINITY_CALC_DEPTH) + running->calc_depth--; + if(ast && (running->calc_depth == _INFINITY_CALC_DEPTH || running->calc_depth)) { + c = ast ? (_object_t*)ast->data : 0; + if(c->type == _DT_FUNC && !_is_operator(c->data.func->pointer) && !_is_flow(c->data.func->pointer)) { + _ls_foreach(opnd, _remove_source_object); + + _handle_error_on_obj(s, SE_RN_COLON_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } + ast = ast->next; + } else { + c = _exp_assign; + } + } + } else { + pri = _get_priority(((_object_t*)(_ls_back(optr)->data))->data.func->pointer, c->data.func->pointer); + switch(pri) { + case '<': + _ls_pushback(optr, c); + c = ast ? (_object_t*)ast->data : 0; + if(ast) ast = ast->next; + f = 0; + + break; + case '=': + x = (_object_t*)_ls_popback(optr); + c = ast ? (_object_t*)ast->data : 0; + if(ast) ast = ast->next; + + break; + case '>': + theta = (_object_t*)_ls_popback(optr); + b = (_object_t*)_ls_popback(opnd); + a = (_object_t*)_ls_popback(opnd); + r = _operate_operand(s, theta, a, b, &result); + if(!r) { + _ls_clear(optr); + _handle_error_on_obj(s, SE_RN_FAILED_TO_OPERATE, s->source_file, errn ? DON(errn) : DON(ast), MB_FUNC_ERR, _error, result); + } + _ls_pushback(opnd, r); + _LAZY_INIT_GLIST; + _ls_pushback(garbage, r); + if(_IS_FUNC(c, _core_close_bracket)) + hack = true; + + break; + case ' ': + _handle_error_on_obj(s, SE_RN_FAILED_TO_OPERATE, s->source_file, errn ? DON(errn) : DON(ast), MB_FUNC_ERR, _error, result); + + break; + } + } + } + + if(errn) { + _handle_error_on_obj(s, SE_RN_CLOSE_BRACKET_EXPECTED, s->source_file, DON(errn), MB_FUNC_ERR, _error, result); + } + + c = (_object_t*)(_ls_popback(opnd)); + if(_is_unexpected_calc_type(s, c)) { + _handle_error_on_obj(s, SE_RN_UNEXPECTED_TYPE, s->source_file, DON(ast), MB_FUNC_ERR, _error, result); + } +#ifdef MB_PREFER_SPEED + if(ast && ast->prev == *l) { + _object_t* obj = (_object_t*)(*l)->data; + switch(obj->type) { + case _DT_NIL: /* Fall through */ + case _DT_INT: /* Fall through */ + case _DT_REAL: + obj->is_const = true; + + break; + default: /* Do nothing */ + break; + } + } +_fast: +#endif /* MB_PREFER_SPEED */ + if(c->type == _DT_VAR) { + (*val)->type = c->data.variable->data->type; + (*val)->data = c->data.variable->data->data; + if(_is_string(c)) + (*val)->is_ref = true; + } else { + (*val)->type = c->type; + if(_is_string(c)) { + char* _str = _extract_string(c); + (*val)->data.string = mb_strdup(_str, mb_strlen(_str) + 1); + (*val)->is_ref = false; + } else { + (*val)->data = c->data; + } + } + if(guard_val != c && garbage && _ls_try_remove(garbage, c, _ls_cmp_data, 0)) { + _try_clear_intermediate_value(c, 0, s); + + if(_is_referenced_calc_type(s, c)) + _destroy_object_capsule_only(c, 0); + else + _destroy_object(c, 0); + } + + while(0) { +_error: + if(garbage) { + _LS_FOREACH(garbage, _do_nothing_on_object, _remove_if_exists, opnd); + } + } + +_exit: + if(garbage) { + _LS_FOREACH(garbage, _destroy_object, _try_clear_intermediate_value, s); + _ls_destroy(garbage); + } + if(optr) { + _ls_foreach(optr, _destroy_object_not_compile_time); + _ls_destroy(optr); + } + if(opnd) { + _ls_foreach(opnd, _destroy_object_not_compile_time); + _ls_destroy(opnd); + } + if(inep) { + mb_free(_ls_popback(s->in_neg_expr)); + } + *l = ast; + mb_set_gc_enabled(s, gce); + + return result; +#undef _LAZY_INIT_GLIST +} + +/* Push current variable argument list */ +static _ls_node_t* _push_var_args(mb_interpreter_t* s) { + _ls_node_t* result = s->var_args; + + s->var_args = 0; + + return result; +} + +/* Pop current variable argument list */ +static void _pop_var_args(mb_interpreter_t* s, _ls_node_t* last_var_args) { + _ls_node_t* var_args = s->var_args; + + s->var_args = last_var_args; + if(var_args) { + _LS_FOREACH(var_args, _do_nothing_on_object, _destroy_var_arg, &s->gc); + _ls_destroy(var_args); + } +} + +/* Pop an argument from the caller or a variable argument list */ +static int _pop_arg(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, unsigned* ia, _routine_t* r, mb_pop_routine_arg_func_t pop_arg, _ls_node_t* args, mb_value_t* arg) { + int result = MB_FUNC_OK; + _ls_node_t* ast = *l; + + mb_make_nil(*arg); +#if _MULTILINE_STATEMENT + if(_multiline_statement(s)) { + _object_t* obj = 0; + obj = (_object_t*)ast->data; + while(_IS_EOS(obj)) { + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; + } + } +#endif /* _MULTILINE_STATEMENT */ + if(ast && ast->data && _IS_FUNC(ast->data, _core_args)) { + if(args) { + _object_t* obj = (_object_t*)_ls_popfront(args); + if(obj) { + _internal_object_to_public_value(obj, arg); + _destroy_object_capsule_only(obj, 0); + } + } else { + arg->type = MB_DT_UNKNOWN; + } + } else { + result = pop_arg(s, (void**)l, va, ca, ia, r, arg); + } + + return result; +} + +/* Process arguments of a routine */ +static int _proc_args(mb_interpreter_t* s, _ls_node_t** l, _running_context_t* running, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg, bool_t proc_ref, _ls_node_t* args) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _ls_node_t* parameters = 0; + mb_value_t arg; + _ls_node_t* pars = 0; + _var_t* var = 0; + _ls_node_t* rnode = 0; + unsigned ia = 0; + _ls_node_t* var_args = 0; + + parameters = r->func.basic.parameters; +#ifdef MB_ENABLE_LAMBDA + if(r->type == MB_RT_LAMBDA) + parameters = r->func.lambda.parameters; +#endif /* MB_ENABLE_LAMBDA */ + + if(parameters) { + mb_make_nil(arg); + pars = parameters; + pars = pars->next; + while(pars && (!has_arg || (has_arg && has_arg(s, (void**)l, va, ca, &ia, r)))) { + var = (_var_t*)pars->data; + pars = pars->next; + if(_IS_VAR_ARGS(var)) + break; + + if(pop_arg) { + mb_check(_pop_arg(s, l, va, ca, &ia, r, pop_arg, args, &arg)); +#ifdef MB_ENABLE_COLLECTION_LIB + if(_try_purge_it(s, &arg, 0)) { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } +#endif /* MB_ENABLE_COLLECTION_LIB */ + } + + if(running->meta == _SCOPE_META_REF) { + _object_t* obj = (_object_t*)(_ht_find(running->var_dict, var->name)->data); + var = obj->data.variable; + + if(proc_ref) + var->data->is_ref = false; + } else { + rnode = _search_identifier_in_scope_chain(s, running, var->name, _PATHING_NONE, 0, 0); + if(rnode) + var = ((_object_t*)rnode->data)->data.variable; + + if(proc_ref) + var->data->is_ref = true; + } + + if(!pop_arg && var->data->type == _DT_STRING && !var->data->is_ref) + _mark_lazy_destroy_string(s, var->data->data.string); + result = _public_value_to_internal_object(&arg, var->data); + + if(result != MB_FUNC_OK) + break; + + if(args && _ls_empty(args)) + break; + } + + if(_IS_VAR_ARGS(var)) { + if(has_arg && !var_args && _IS_VAR_ARGS(var)) + var_args = s->var_args = _ls_create(); + + while(has_arg && has_arg(s, (void**)l, va, ca, &ia, r)) { + if(pop_arg) { + mb_check(_pop_arg(s, l, va, ca, &ia, r, pop_arg, args, &arg)); + } + + if(var_args) { + _object_t* obj = _create_object(); + result = _public_value_to_internal_object(&arg, obj); + if(obj->type == _DT_ROUTINE && obj->data.routine->type == MB_RT_SCRIPT) + obj->is_ref = true; + _ls_pushback(var_args, obj); + } + + if(args && _ls_empty(args)) + break; + } + } + + ast = *l; + if(ast) { + _object_t* obj = (_object_t*)ast->data; + if(obj && _IS_FUNC(obj, _core_args)) { + if(ast) ast = ast->next; + *l = ast; + } + } + } + +#ifdef MB_ENABLE_COLLECTION_LIB +_exit : +#endif /* MB_ENABLE_COLLECTION_LIB */ + return result; +} + +/* Evaluate a routine */ +static int _eval_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg) { + int result = MB_FUNC_OK; +#ifdef MB_ENABLE_SOURCE_TRACE + char* src = 0; +#endif /* MB_ENABLE_SOURCE_TRACE */ +#if defined MB_ENABLE_STACK_TRACE && defined MB_ENABLE_LAMBDA + char ln[_LAMBDA_NAME_MAX_LENGTH]; +#endif /* MB_ENABLE_STACK_TRACE && MB_ENABLE_LAMBDA */ + + _PREVCALL(s, l, r); + +#ifdef MB_ENABLE_STACK_TRACE + _ls_pushback(s->stack_frames, r->name); +#endif /* MB_ENABLE_STACK_TRACE */ + +#ifdef MB_ENABLE_SOURCE_TRACE + src = s->source_file; + s->source_file = r->source_file; +#endif /* MB_ENABLE_SOURCE_TRACE */ + + if(r->type == MB_RT_SCRIPT && r->func.basic.entry) { + result = _eval_script_routine(s, l, va, ca, r, has_arg, pop_arg); +#ifdef MB_ENABLE_LAMBDA + } else if(r->type == MB_RT_LAMBDA && r->func.lambda.entry) { +#ifdef MB_ENABLE_STACK_TRACE + _ls_node_t* top = _ls_back(s->stack_frames); + if(top) { + sprintf(ln, "LAMBDA_0x%p", &r->func.lambda.ref); + top->data = ln; + } +#endif /* MB_ENABLE_STACK_TRACE */ + result = _eval_lambda_routine(s, l, va, ca, r, has_arg, pop_arg); +#endif /* MB_ENABLE_LAMBDA */ + } else if(r->type == MB_RT_NATIVE && r->func.native.entry) { + result = _eval_native_routine(s, l, va, ca, r, has_arg, pop_arg); + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ROUTINE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + +_exit: +#ifdef MB_ENABLE_SOURCE_TRACE + s->source_file = src; +#endif /* MB_ENABLE_SOURCE_TRACE */ + +#ifdef MB_ENABLE_STACK_TRACE + _ls_popback(s->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ + + _POSTCALL(s, l, r); + + return result; +} + +/* Evaluate a script routine */ +static int _eval_script_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _running_context_t* running = 0; + _routine_t* lastr = 0; + mb_value_t inte; + _ls_node_t* lastv = 0; + bool_t succ = false; +#ifdef MB_ENABLE_CLASS + bool_t pushed_inst = false; + _class_t* last_inst = 0; + bool_t same_inst = s->last_routine ? s->last_routine->instance == r->instance : false; +#else /* MB_ENABLE_CLASS */ + bool_t same_inst = true; +#endif /* MB_ENABLE_CLASS */ + + mb_assert(s && l && r); + + if(!va && s->last_routine && !s->last_routine->func.basic.parameters && same_inst && (s->last_routine->name == r->name || !strcmp(s->last_routine->name, r->name))) { + ast = *l; + _skip_to(s, &ast, 0, _DT_EOS); + if(ast) + obj = (_object_t*)ast->data; + if(_IS_EOS(obj)) + ast = ast->next; + if(ast && _IS_FUNC((_object_t*)ast->data, _core_enddef)) { /* Tail recursion optimization */ + *l = r->func.basic.entry; + if(*l) + *l = (*l)->next; + + goto _tail; + } + } + + lastr = s->last_routine; + s->last_routine = r; + + lastv = _push_var_args(s); + + if(!va) { + mb_check(mb_attempt_open_bracket(s, (void**)l)); + } + + running = _push_weak_scope_by_routine(s, r->func.basic.scope, r); + result = _proc_args(s, l, running, va, ca, r, has_arg, pop_arg, true, lastv); + if(result != MB_FUNC_OK) { + if(running->meta == _SCOPE_META_REF) + _destroy_scope(s, running); + else + _pop_weak_scope(s, running); + + goto _error; + } + running = _pop_weak_scope(s, running); + + if(!va) { + _mb_check_mark_exit(mb_attempt_close_bracket(s, (void**)l), result, _error); + } + + ast = *l; + _ls_pushback(s->sub_stack, ast); + +#ifdef MB_ENABLE_CLASS + if(r->instance && s->last_instance != r->instance) { + pushed_inst = true; + last_inst = s->last_instance; + s->last_instance = r->instance; + if(r->instance) + _push_scope_by_class(s, r->instance->scope); + } +#endif /* MB_ENABLE_CLASS */ + + running = _push_scope_by_routine(s, running); + + *l = r->func.basic.entry; + if(!(*l)) { + _handle_error_on_obj(s, SE_RN_INVALID_ROUTINE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + do { + result = _execute_statement(s, l, true); + if(ast == *l) { + _handle_error_now(s, SE_RN_INVALID_EXPRESSION, s->last_error_file, result); + + goto _exit; + } + ast = *l; + if(result == MB_SUB_RETURN) { + result = MB_FUNC_OK; + + break; + } + if(result == MB_FUNC_SUSPEND) { + _handle_error_now(s, SE_RN_CANNOT_SUSPEND_HERE, s->last_error_file, result); + + goto _exit; + } + if(result != MB_FUNC_OK) { + if(result >= MB_EXTENDED_ABORT) + s->last_error = SE_EA_EXTENDED_ABORT; + _handle_error_now(s, s->last_error, s->last_error_file, result); + + goto _exit; + } + } while(ast); + +#ifdef MB_ENABLE_CLASS + _out_of_scope(s, running, r->instance, r, true); +#else /* MB_ENABLE_CLASS */ + _out_of_scope(s, running, 0, r, true); +#endif /* MB_ENABLE_CLASS */ + + result = _proc_args(s, l, running, 0, 0, r, 0, 0, false, 0); + if(result != MB_FUNC_OK) + goto _exit; + + succ = true; + + mb_make_nil(inte); + _swap_public_value(&inte, &running->intermediate_value); + + _pop_scope(s, true); + +_exit: + if(!succ) + _pop_scope(s, true); + +#ifdef MB_ENABLE_CLASS + if(pushed_inst) { + if(r->instance) + _pop_scope(s, false); + s->last_instance = last_inst; + } +#endif /* MB_ENABLE_CLASS */ + + if(succ) + _assign_public_value(s, &s->running_context->intermediate_value, &inte, false); + +_error: + s->last_routine = lastr; + + _pop_var_args(s, lastv); + +_tail: + return result; +} + +#ifdef MB_ENABLE_LAMBDA +/* Evaluate a lambda routine */ +static int _eval_lambda_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _running_context_t* running = 0; + _routine_t* lastr = 0; + mb_value_t inte; + _ls_node_t* lastv = 0; + + mb_assert(s && l && r); + + lastr = s->last_routine; + s->last_routine = r; + + lastv = _push_var_args(s); + + if(!va) { + mb_check(mb_attempt_open_bracket(s, (void**)l)); + } + + running = _link_lambda_scope_chain(s, &r->func.lambda, true); + if(!running) { + _handle_error_on_obj(s, SE_RN_INVALID_ROUTINE, s->source_file, DON2(l), MB_FUNC_ERR, _error, result); + } + result = _proc_args(s, l, running, va, ca, r, has_arg, pop_arg, true, lastv); + ast = *l; + if(result != MB_FUNC_OK) { + _unlink_lambda_scope_chain(s, &r->func.lambda, true); + + goto _error; + } + running = _unlink_lambda_scope_chain(s, &r->func.lambda, true); + + if(!va) { + _mb_check_mark_exit(mb_attempt_close_bracket(s, (void**)l), result, _error); + } + + ast = *l; + _ls_pushback(s->sub_stack, ast); + + running = _link_lambda_scope_chain(s, &r->func.lambda, false); + + *l = r->func.lambda.entry; + if(!(*l)) { + _handle_error_on_obj(s, SE_RN_INVALID_ROUTINE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + do { + result = _execute_statement(s, l, true); + ast = *l; + if(result == MB_SUB_RETURN) { + result = MB_FUNC_OK; + + break; + } + if(result == MB_FUNC_SUSPEND) { + _handle_error_now(s, SE_RN_CANNOT_SUSPEND_HERE, s->last_error_file, result); + + goto _exit; + } + if(result != MB_FUNC_OK) { + if(result >= MB_EXTENDED_ABORT) + s->last_error = SE_EA_EXTENDED_ABORT; + _handle_error_now(s, s->last_error, s->last_error_file, result); + + goto _exit; + } + } while(ast); + + _out_of_scope(s, running, 0, r, true); + + result = _proc_args(s, l, running, 0, 0, r, 0, 0, false, 0); + if(result != MB_FUNC_OK) + goto _exit; + + mb_make_nil(inte); + _swap_public_value(&inte, &running->intermediate_value); + + running = _unlink_lambda_scope_chain(s, &r->func.lambda, false); + + _assign_public_value(s, &s->running_context->intermediate_value, &inte, false); + +_exit: + if(result != MB_FUNC_OK) + _unlink_lambda_scope_chain(s, &r->func.lambda, false); + +_error: + s->last_routine = lastr; + + _pop_var_args(s, lastv); + + *l = ast; + + return result; +} +#endif /* MB_ENABLE_LAMBDA */ + +/* Evaluate a native routine */ +static int _eval_native_routine(mb_interpreter_t* s, _ls_node_t** l, mb_value_t* va, unsigned ca, _routine_t* r, mb_has_routine_arg_func_t has_arg, mb_pop_routine_arg_func_t pop_arg) { + int result = MB_FUNC_OK; + _routine_t* lastr = 0; + mb_routine_func_t entry = 0; + _ls_node_t* lastv = 0; + + mb_assert(s && l && r); + + lastr = s->last_routine; + s->last_routine = r; + + lastv = _push_var_args(s); + + entry = r->func.native.entry; + if(!entry) { + _handle_error_on_obj(s, SE_RN_INVALID_ROUTINE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + result = entry(s, (void**)l, va, ca, r, has_arg, pop_arg); + +_exit: + s->last_routine = lastr; + + _pop_var_args(s, lastv); + + return result; +} + +/* Detect if there is any more lexical argument */ +static int _has_routine_lex_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r) { + mb_unrefvar(va); + mb_unrefvar(ca); + mb_unrefvar(ia); + mb_unrefvar(r); + + return mb_has_arg(s, l); +} + +/* Pop a lexical argument */ +static int _pop_routine_lex_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r, mb_value_t* val) { + mb_unrefvar(va); + mb_unrefvar(ca); + mb_unrefvar(ia); + mb_unrefvar(r); + + return mb_pop_value(s, l, val); +} + +/* Detect if there is any more argument in the argument list */ +static int _has_routine_fun_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r) { + mb_unrefvar(s); + mb_unrefvar(l); + mb_unrefvar(va); + mb_unrefvar(r); + + return *ia < ca; +} + +/* Pop an argument from the argument list */ +static int _pop_routine_fun_arg(mb_interpreter_t* s, void** l, mb_value_t* va, unsigned ca, unsigned* ia, void* r, mb_value_t* val) { + mb_unrefvar(s); + mb_unrefvar(l); + mb_unrefvar(ca); + mb_unrefvar(r); + + memcpy(val, &(va[*ia]), sizeof(mb_value_t)); + (*ia)++; + + return MB_FUNC_OK; +} + +/* Determine whether an object is a PRINT termination */ +static bool_t _is_print_terminal(mb_interpreter_t* s, _object_t* obj) { + bool_t result = false; + + mb_assert(s && obj); + + result = ( + _IS_EOS(obj) || + _IS_SEP(obj, ':') || + _IS_FUNC(obj, _core_elseif) || + _IS_FUNC(obj, _core_else) || + _IS_FUNC(obj, _core_endif) + ); + + return result; +} + +/* Try to call overridden function */ +static mb_meta_status_e _try_overridden(mb_interpreter_t* s, void** l, mb_value_t* d, const char* f, mb_meta_func_e t) { + mb_assert(s && l && d && f); + +#ifdef MB_ENABLE_USERTYPE_REF + if(d->type == MB_DT_USERTYPE_REF) { + _object_t obj; + _MAKE_NIL(&obj); + _public_value_to_internal_object(d, &obj); + if(t == MB_MF_COLL && obj.data.usertype_ref->coll_func) + return obj.data.usertype_ref->coll_func(s, l, d, f); + else if(t == MB_MF_FUNC && obj.data.usertype_ref->generic_func) + return obj.data.usertype_ref->generic_func(s, l, d, f); + } +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_CLASS + if(d->type == MB_DT_CLASS) { + char buf[_TEMP_FORMAT_MAX_LENGTH]; + _ls_node_t* ofn = 0; + _object_t obj; + _MAKE_NIL(&obj); + _public_value_to_internal_object(d, &obj); + sprintf(buf, _CLASS_OVERRIDE_FMT, f); + ofn = _search_identifier_in_class(s, obj.data.instance, buf, 0, 0); + if(ofn) { + _object_t* ofo = (_object_t*)ofn->data; + _ls_node_t* ast = (_ls_node_t*)*l; + mb_value_t va[1]; + mb_make_nil(va[0]); + if(_eval_routine(s, &ast, va, 0, ofo->data.routine, _has_routine_lex_arg, _pop_routine_lex_arg) == MB_FUNC_OK) { + if(ast) + *l = ast->prev; + + return (mb_meta_status_e)(MB_MS_DONE | MB_MS_RETURNED); + } + } + } +#endif /* MB_ENABLE_CLASS */ +#if !defined MB_ENABLE_USERTYPE_REF || !defined MB_ENABLE_CLASS + mb_unrefvar(t); +#endif /* !MB_ENABLE_USERTYPE_REF && !MB_ENABLE_CLASS */ + + return MB_MS_NONE; +} + +/** Handlers */ + +/* Set current error information */ +static bool_t _set_current_error(mb_interpreter_t* s, mb_error_e err, char* f) { + mb_assert(s && err >= 0); + + if(s->last_error == SE_NO_ERR) { + s->last_error = err; + s->last_error_file = f; + + return true; + } + + return false; +} + +/* Get a print functor of an interpreter */ +static mb_print_func_t _get_printer(mb_interpreter_t* s) { + mb_assert(s); + + if(s->printer) + return s->printer; + + return _standard_printer; +} + +/* Get an input functor of an interpreter */ +static mb_input_func_t _get_inputer(mb_interpreter_t* s) { + mb_assert(s); + + if(s->inputer) + return s->inputer; + + return mb_gets; +} + +/* Standard printer adapter */ +static int _standard_printer(mb_interpreter_t* s, const char* fmt, ...) { + int result; + va_list args; + mb_unrefvar(s); + + va_start(args, fmt); + result = vprintf(fmt, args); + va_end(args); + + return result; +} + +/* Print a string */ +static void _print_string(mb_interpreter_t* s, _object_t* obj) { +#if defined MB_CP_VC && defined MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING + char* str = 0; + _dynamic_buffer_t buf; + size_t lbuf = 0; + + mb_assert(s && obj); + + str = obj->data.string ? obj->data.string : MB_NULL_STRING; + _INIT_BUF(buf); + while((lbuf = (size_t)mb_bytes_to_wchar(str, &_WCHAR_BUF_PTR(buf), _WCHARS_OF_BUF(buf))) > _WCHARS_OF_BUF(buf)) { + _RESIZE_WCHAR_BUF(buf, lbuf); + } + _get_printer(s)(s, "%ls", _WCHAR_BUF_PTR(buf)); + _DISPOSE_BUF(buf); +#else /* MB_CP_VC && MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING */ + mb_assert(s && obj); + + _get_printer(s)(s, "%s", obj->data.string ? obj->data.string : MB_NULL_STRING); +#endif /* MB_CP_VC && MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING */ +} + +/** Parsing helpers */ + +/* Read all content of a file into a buffer */ +static char* _load_file(mb_interpreter_t* s, const char* f, const char* prefix, bool_t importing) { +#ifndef MB_DISABLE_LOAD_FILE + FILE* fp = 0; + char* buf = 0; + long curpos = 0; + long l = 0; + long i = 0; + _parsing_context_t* context = 0; + + mb_assert(s); + + context = (_parsing_context_t*)s->parsing_context; + + if(_ls_find(context->imported, (void*)f, (_ls_compare_t)_ht_cmp_string, 0)) { + buf = (char*)f; + } else { + fp = fopen(f, "rb"); + if(fp) { + if(importing) { + buf = mb_strdup(f, strlen(f) + 1); + _ls_pushback(context->imported, buf); + buf = 0; + } + + curpos = ftell(fp); + fseek(fp, 0L, SEEK_END); + l = ftell(fp); + fseek(fp, curpos, SEEK_SET); + if(prefix) { + i = (long)strlen(prefix); + l += i; + } + buf = (char*)mb_malloc((size_t)(l + 1)); + mb_assert(buf); + if(prefix) + memcpy(buf, prefix, i); + fread(buf + i, 1, l, fp); + do { + char* off = buf + i; + int b = mb_uu_getbom((const char**)&off); + if(b) { + memmove(buf + i, buf + i + b, l - b - i); + buf[l - b] = _ZERO_CHAR; + } + } while(0); + fclose(fp); + buf[l] = _ZERO_CHAR; + } + } + + return buf; +#else /* MB_DISABLE_LOAD_FILE */ + return 0; +#endif /* MB_DISABLE_LOAD_FILE */ +} + +/* Finish loading a file */ +static void _end_of_file(_parsing_context_t* context) { + if(context) + context->parsing_state = _PS_NORMAL; +} + +/* Determine whether a character is blank */ +static bool_t _is_blank_char(char c) { + return (c == ' ') || (c == '\t'); +} + +/* Determine whether a character is end of file */ +static bool_t _is_eof_char(char c) { +#ifdef __cplusplus + union { signed char s; char c; } u; + u.c = c; + + return u.s == EOF; +#else /* __cplusplus */ + return (c == EOF); +#endif /* __cplusplus */ +} + +/* Determine whether a character is newline */ +static bool_t _is_newline_char(char c) { + return (c == _RETURN_CHAR) || (c == _NEWLINE_CHAR) || _is_eof_char(c); +} + +/* Determine whether a character is separator */ +static bool_t _is_separator_char(char c) { + return (c == ',') || (c == ';') || (c == ':'); +} + +/* Determine whether a character is bracket */ +static bool_t _is_bracket_char(char c) { + return (c == '(') || (c == ')'); +} + +/* Determine whether a character is quotation mark */ +static bool_t _is_quotation_char(char c) { + return (c == '"'); +} + +/* Determine whether a character is comment mark */ +static bool_t _is_comment_char(char c) { + return (c == '\''); +} + +/* Determine whether a character is accessor char */ +static bool_t _is_accessor_char(char c) { + return (c == '.'); +} + +/* Determine whether a character is numeric char */ +static bool_t _is_numeric_char(char c) { + return (c >= '0' && c <= '9') || _is_accessor_char(c); +} + +/* Determine whether a character is identifier char */ +static bool_t _is_identifier_char(char c) { +#if defined MB_ENABLE_LAMBDA && defined MB_LAMBDA_ALIAS + char* p = MB_LAMBDA_ALIAS; + while(*p) { + if(c == *p) return true; + ++p; + } +#endif /* MB_ENABLE_LAMBDA && MB_LAMBDA_ALIAS */ + + return ( + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_') || + _is_numeric_char(c) || + (c == _STRING_POSTFIX_CHAR) + ); +} + +/* Determine whether a character is operator char */ +static bool_t _is_operator_char(char c) { + return ( + (c == '+') || (c == '-') || (c == '*') || (c == '/') || + (c == '^') || + (c == '(') || (c == ')') || + (c == '=') || + (c == '>') || (c == '<') + ); +} + +/* Determine whether a character is exponential char */ +static bool_t _is_exponential_char(char c) { + return (c == 'e') || (c == 'E'); +} + +/* Determine whether a character is module using char */ +static bool_t _is_using_at_char(char c) { + return (c == '@'); +} + +/* Determine whether current symbol is exponent prefix */ +static bool_t _is_exponent_prefix(char* s, int begin, int end) { + int i = 0; + + mb_assert(s); + + if(end < 0) + return false; + + for(i = begin; i <= end; i++) { + if(!_is_numeric_char(s[i])) + return false; + } + + return true; +} + +/* Parse a character and append it to current parsing symbol */ +static int _append_char_to_symbol(mb_interpreter_t* s, char c) { + int result = MB_FUNC_OK; + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + + if(_is_accessor_char(c)) + context->current_symbol_contains_accessor++; + + if(context->current_symbol_nonius + 1 >= _SINGLE_SYMBOL_MAX_LENGTH) { + _set_current_error(s, SE_PS_SYMBOL_TOO_LONG, 0); + + result = MB_FUNC_ERR; + } else { + context->current_symbol[context->current_symbol_nonius] = c; + ++context->current_symbol_nonius; + } + + return result; +} + +#ifdef MB_ENABLE_UNICODE_ID +/* Parse a UTF8 character and append it to current parsing symbol */ +static int _append_uu_char_to_symbol(mb_interpreter_t* s, const char* str, int n) { + int result = MB_FUNC_OK; + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + + if(context->current_symbol_nonius + n >= _SINGLE_SYMBOL_MAX_LENGTH) { + _set_current_error(s, SE_PS_SYMBOL_TOO_LONG, 0); + + result = MB_FUNC_ERR; + } else { + memcpy(&context->current_symbol[context->current_symbol_nonius], str, n); + context->current_symbol_nonius += n; + } + + return result; +} +#endif /* MB_ENABLE_UNICODE_ID */ + +/* Cut current symbol when current one parsing is finished */ +static int _cut_symbol(mb_interpreter_t* s, int pos, unsigned short row, unsigned short col) { + int result = MB_FUNC_OK; + _parsing_context_t* context = 0; + char* sym = 0; + bool_t delsym = false; + + mb_assert(s); + + context = s->parsing_context; + if(context->current_symbol_nonius && context->current_symbol[0] != _ZERO_CHAR) { + sym = (char*)mb_malloc(context->current_symbol_nonius + 1); + memcpy(sym, context->current_symbol, context->current_symbol_nonius + 1); + + result = _append_symbol(s, sym, &delsym, pos, row, col); + if(result != MB_FUNC_OK || delsym) { + safe_free(sym); + } + } + memset(context->current_symbol, 0, sizeof(context->current_symbol)); + context->current_symbol_nonius = 0; + context->current_symbol_contains_accessor = 0; + + return result; +} + +/* Append cut symbol to the AST list */ +static int _append_symbol(mb_interpreter_t* s, char* sym, bool_t* delsym, int pos, unsigned short row, unsigned short col) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _ls_node_t** assign = 0; + _ls_node_t* node = 0; + _parsing_context_t* context = 0; + + mb_assert(s && sym); + + ast = s->ast; + result = _create_symbol(s, ast, sym, &obj, &assign, delsym); + if(obj) { +#ifdef MB_ENABLE_SOURCE_TRACE + obj->source_pos = pos; + obj->source_row = row; + obj->source_col = col; +#else /* MB_ENABLE_SOURCE_TRACE */ + mb_unrefvar(row); + mb_unrefvar(col); + + obj->source_pos = (char)!!pos; +#endif /* MB_ENABLE_SOURCE_TRACE */ + + node = _ls_pushback(ast, obj); + if(assign) + *assign = node; + + context = s->parsing_context; + context->last_symbol = obj; + } + + return result; +} + +/* Create a syntax symbol */ +static int _create_symbol(mb_interpreter_t* s, _ls_node_t* l, char* sym, _object_t** obj, _ls_node_t*** asgn, bool_t* delsym) { + int result = MB_FUNC_OK; + _data_e type; + union { + _func_t* func; _array_t* array; +#ifdef MB_ENABLE_CLASS + _class_t* instance; +#endif /* MB_ENABLE_CLASS */ + _routine_t* routine; _var_t* var; _label_t* label; real_t float_point; int_t integer; _raw_t any; + } tmp; + _raw_t value; + unsigned ul = 0; + _parsing_context_t* context = 0; + _running_context_t* running = 0; + _ls_node_t* glbsyminscope = 0; + bool_t is_field = false; + mb_unrefvar(l); + + mb_assert(s && sym && obj); + + memset(value, 0, sizeof(_raw_t)); + + context = s->parsing_context; + running = s->running_context; + + *obj = _create_object(); +#ifdef MB_ENABLE_SOURCE_TRACE + (*obj)->source_pos = -1; + (*obj)->source_row = (*obj)->source_col = 0xFFFF; +#else /* MB_ENABLE_SOURCE_TRACE */ + (*obj)->source_pos = -1; +#endif /* MB_ENABLE_SOURCE_TRACE */ + + type = _get_symbol_type(s, sym, &value); + if(s->last_error != SE_NO_ERR) { + result = MB_FUNC_ERR; + + goto _exit; + } + (*obj)->type = type; + switch(type) { + case _DT_NIL: + memcpy(tmp.any, value, sizeof(_raw_t)); + if(tmp.integer) { /* Nil type */ + (*obj)->type = _DT_NIL; + } else { /* End of line character */ + safe_free(*obj); + } + safe_free(sym); + + break; + case _DT_INT: + memcpy(tmp.any, value, sizeof(_raw_t)); + (*obj)->data.integer = tmp.integer; + safe_free(sym); + + break; + case _DT_REAL: + memcpy(tmp.any, value, sizeof(_raw_t)); + (*obj)->data.float_point = tmp.float_point; + safe_free(sym); + + break; + case _DT_STRING: { + size_t _sl = strlen(sym); + (*obj)->data.string = (char*)mb_malloc(_sl - 2 + 1); + memcpy((*obj)->data.string, sym + sizeof(char), _sl - 2); + (*obj)->data.string[_sl - 2] = _ZERO_CHAR; + *delsym = true; + } + + break; + case _DT_FUNC: + tmp.func = (_func_t*)mb_malloc(sizeof(_func_t)); + memset(tmp.func, 0, sizeof(_func_t)); + tmp.func->name = sym; + memcpy(&tmp.func->pointer, value, sizeof(tmp.func->pointer)); + (*obj)->data.func = tmp.func; + + break; + case _DT_ARRAY: + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_ARRAY) { + (*obj)->data.array = ((_object_t*)glbsyminscope->data)->data.array; + (*obj)->is_ref = true; + *delsym = true; + } else { + tmp.array = _create_array(s, sym, _DT_UNKNOWN); + memcpy(&tmp.array->type, value, sizeof(tmp.array->type)); + (*obj)->data.array = tmp.array; + + ul = _ht_set_or_insert(running->var_dict, sym, *obj); + mb_assert(ul); + + *obj = _create_object(); + (*obj)->type = type; + (*obj)->data.array = tmp.array; + (*obj)->is_ref = true; + } + + break; +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + if(!_is_identifier_char(*sym)) + *sym = _INVALID_CLASS_CHAR; + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_CLASS) { + (*obj)->data.instance = ((_object_t*)glbsyminscope->data)->data.instance; + (*obj)->is_ref = true; + *delsym = true; + if(running != (*obj)->data.instance->scope && + (context->class_state != _CLASS_STATE_NONE) && + _IS_FUNC(context->last_symbol, _core_class)) { + _push_scope_by_class(s, (*obj)->data.instance->scope); + } + } else { + tmp.instance = (_class_t*)mb_malloc(sizeof(_class_t)); + _init_class(s, tmp.instance, sym); + _push_scope_by_class(s, tmp.instance->scope); + s->last_instance = tmp.instance; + + (*obj)->data.instance = tmp.instance; + + ul = _ht_set_or_insert(running->var_dict, sym, *obj); + mb_assert(ul); + + *obj = _create_object(); + (*obj)->type = type; + (*obj)->data.instance = tmp.instance; + (*obj)->is_ref = true; + } + + break; +#endif /* MB_ENABLE_CLASS */ + case _DT_ROUTINE: + if(!_is_identifier_char(*sym)) + *sym = _INVALID_ROUTINE_CHAR; + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_ROUTINE) { + (*obj)->data.routine = ((_object_t*)glbsyminscope->data)->data.routine; + (*obj)->is_ref = true; + *delsym = true; + if(running != (*obj)->data.routine->func.basic.scope && + context->routine_state && + _IS_FUNC(context->last_symbol, _core_def)) { + _push_scope_by_routine(s, (*obj)->data.routine->func.basic.scope); + } + } else { + _running_context_t* tba = 0; + tmp.routine = (_routine_t*)mb_malloc(sizeof(_routine_t)); + _init_routine(s, tmp.routine, sym, 0); + _push_scope_by_routine(s, tmp.routine->func.basic.scope); + (*obj)->data.routine = tmp.routine; + + tba = _get_scope_to_add_routine(s); + ul = _ht_set_or_insert(tba->var_dict, sym, *obj); + mb_assert(ul); + if(tba != _OUTTER_SCOPE(running) && tba != running) + _pop_scope(s, false); + + *obj = _create_object(); + (*obj)->type = type; + (*obj)->data.routine = tmp.routine; + (*obj)->is_ref = true; + +#ifdef MB_ENABLE_CLASS + tmp.routine->instance = s->last_instance; +#endif /* MB_ENABLE_CLASS */ + } + + break; + case _DT_VAR: + if(context->routine_params_state == _ROUTINE_STATE_PARAMS) + glbsyminscope = _ht_find(running->var_dict, sym); + else + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); +#ifdef MB_ENABLE_CLASS + is_field = context->last_symbol && _IS_FUNC(context->last_symbol, _core_var); +#endif /* MB_ENABLE_CLASS */ + if(!is_field && glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_VAR) { + (*obj)->data.variable = ((_object_t*)glbsyminscope->data)->data.variable; + (*obj)->is_ref = true; + *delsym = true; + } else { +#ifdef MB_ENABLE_CLASS + if(strcmp(sym, _CLASS_ME) == 0) { + _handle_error_now(s, SE_RN_INVALID_ID_USAGE, s->source_file, MB_FUNC_ERR); + (*obj)->is_ref = true; + *delsym = true; + + goto _exit; + } +#endif /* MB_ENABLE_CLASS */ + tmp.var = (_var_t*)mb_malloc(sizeof(_var_t)); + memset(tmp.var, 0, sizeof(_var_t)); + tmp.var->name = sym; + tmp.var->data = _create_object(); + tmp.var->data->type = (sym[strlen(sym) - 1] == _STRING_POSTFIX_CHAR) ? _DT_STRING : _DT_INT; + tmp.var->data->data.integer = 0; +#ifdef MB_ENABLE_CLASS + if(context->class_state != _CLASS_STATE_NONE) + tmp.var->pathing = _PATHING_NORMAL; + else if(!is_field) + tmp.var->pathing = context->current_symbol_contains_accessor ? _PATHING_NORMAL : _PATHING_NONE; +#endif /* MB_ENABLE_CLASS */ + (*obj)->data.variable = tmp.var; + + ul = _ht_set_or_insert(running->var_dict, sym, *obj); + mb_assert(ul); + + *obj = _create_object(); + (*obj)->type = type; + (*obj)->data.variable = tmp.var; + (*obj)->is_ref = true; + } + + break; + case _DT_LABEL: + if(context->current_char == ':') { + if(mb_memtest(value, sizeof(_raw_t))) { + memcpy(&((*obj)->data.label), value, sizeof((*obj)->data.label)); + (*obj)->is_ref = true; + *delsym = true; + } else { + tmp.label = (_label_t*)mb_malloc(sizeof(_label_t)); + memset(tmp.label, 0, sizeof(_label_t)); + tmp.label->name = sym; + *asgn = &(tmp.label->node); + (*obj)->data.label = tmp.label; + + ul = _ht_set_or_insert(running->var_dict, sym, *obj); + mb_assert(ul); + + *obj = _create_object(); + (*obj)->type = type; + (*obj)->data.label = tmp.label; + (*obj)->is_ref = true; + } + } else { + (*obj)->data.label = (_label_t*)mb_malloc(sizeof(_label_t)); + memset((*obj)->data.label, 0, sizeof(_label_t)); + (*obj)->data.label->name = sym; + } + + break; + case _DT_SEP: + (*obj)->data.separator = sym[0]; + safe_free(sym); + + break; + case _DT_EOS: + safe_free(sym); + + break; + default: /* Do nothing */ + break; + } + +_exit: + return result; +} + +/* Get the type of a syntax symbol */ +static _data_e _get_symbol_type(mb_interpreter_t* s, char* sym, _raw_t* value) { + _data_e result = _DT_NIL; + union { real_t float_point; int_t integer; _object_t* obj; _raw_t any; } tmp; + char* conv_suc = 0; + _parsing_context_t* context = 0; + _running_context_t* running = 0; + _ls_node_t* glbsyminscope = 0; + size_t _sl = 0; + _data_e en = _DT_UNKNOWN; + intptr_t ptr = 0; + bool_t mod = false; + + mb_assert(s && sym); + _sl = strlen(sym); + mb_assert(_sl > 0); + + context = s->parsing_context; + running = s->running_context; + + /* int_t */ + tmp.integer = (int_t)mb_strtol(sym, &conv_suc, 0); + if(*conv_suc == _ZERO_CHAR) { + memcpy(*value, tmp.any, sizeof(_raw_t)); + + result = _DT_INT; + + goto _exit; + } + /* real_t */ + tmp.float_point = (real_t)mb_strtod(sym, &conv_suc); + if(*conv_suc == _ZERO_CHAR) { + memcpy(*value, tmp.any, sizeof(_raw_t)); + + result = _DT_REAL; + + goto _exit; + } + /* String */ + if(_is_quotation_char(sym[0]) && _is_quotation_char(sym[_sl - 1]) && _sl >= 2) { + result = _DT_STRING; + + if(context->last_symbol && _IS_FUNC(context->last_symbol, _core_import)) { + /* IMPORT statement */ + int n = context->current_symbol_nonius; + char current_symbol[_SINGLE_SYMBOL_MAX_LENGTH + 1]; + char* buf = 0; + memcpy(current_symbol, context->current_symbol, sizeof(current_symbol)); + memset(context->current_symbol, 0, sizeof(current_symbol)); + context->current_symbol_nonius = 0; + context->last_symbol = 0; + sym[_sl - 1] = _ZERO_CHAR; + context->parsing_state = _PS_NORMAL; + /* Using a module */ + if(_is_using_at_char(*(sym + 1))) { +#ifdef MB_ENABLE_MODULE + char* ns = mb_strdup(sym + 2, strlen(sym + 2) + 1); + mb_strupr(ns); + if(_ls_find(s->using_modules, ns, (_ls_compare_t)_ht_cmp_string, 0)) { + safe_free(ns); + } else { + _ls_pushback(s->using_modules, ns); + } + + goto _end_import; +#else /* MB_ENABLE_MODULE */ + _handle_error_now(s, SE_CM_NOT_SUPPORTED, s->source_file, MB_FUNC_ERR); + + goto _end_import; +#endif /* MB_ENABLE_MODULE */ + } + /* Import another file */ + buf = _load_file(s, sym + 1, ":", true); + if(buf) { + if(buf == sym + 1) { + _handle_error_now(s, SE_PS_DUPLICATE_IMPORT, s->source_file, MB_FUNC_WARNING); + } else { + char* lf = (char*)(_ls_back(context->imported)->data); + int pos = 0; unsigned short row = 0, col = 0; + lf = _prev_import(s, lf, &pos, &row, &col); + mb_load_string(s, buf, true); + safe_free(buf); + _post_import(s, lf, &pos, &row, &col); + } + } else { + if(!_ls_find(context->imported, (void*)(sym + 1), (_ls_compare_t)_ht_cmp_string, 0)) { + if(s->import_handler) { + _object_t* sep = 0; + char* lf = 0; + int pos = 0; unsigned short row = 0, col = 0; + sep = _create_object(); + sep->type = _DT_SEP; + sep->data.separator = ':'; + _ls_pushback(s->ast, sep); + _ls_pushback(context->imported, mb_strdup(sym + 1, strlen(sym + 1) + 1)); + lf = (char*)(_ls_back(context->imported)->data); + lf = _prev_import(s, lf, &pos, &row, &col); + if(s->import_handler(s, sym + 1) != MB_FUNC_OK) { + _ls_node_t* last = _ls_back(context->imported); + if(s->last_error == SE_NO_ERR) { + context->parsing_pos = pos; + context->parsing_row = row; + context->parsing_col = col; + _handle_error_now(s, SE_PS_FAILED_TO_OPEN_FILE, lf, MB_FUNC_ERR); + } + _destroy_memory(last->data, last->extra); + _ls_popback(context->imported); + } + _post_import(s, lf, &pos, &row, &col); + } else { + _handle_error_now(s, SE_PS_FAILED_TO_OPEN_FILE, s->source_file, MB_FUNC_ERR); + } + } + } + +_end_import: + context->parsing_state = _PS_STRING; + sym[_sl - 1] = '"'; + context->current_symbol_nonius = n; + memcpy(context->current_symbol, current_symbol, sizeof(current_symbol)); + result = _DT_NIL; + } + + goto _exit; + } + /* Nil */ + if(!strcmp(sym, MB_NIL)) { + tmp.integer = ~0; + memcpy(*value, tmp.any, sizeof(_raw_t)); + + result = _DT_NIL; + + goto _exit; + } + /* REM */ + if(!strcmp(sym, _REMARK_STR)) { + context->parsing_state = _PS_COMMENT; + + result = _DT_EOS; + + goto _exit; + } + /* _array_t */ + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_ARRAY) { + tmp.obj = (_object_t*)glbsyminscope->data; + memcpy(*value, &(tmp.obj->data.array->type), sizeof(tmp.obj->data.array->type)); + + result = _DT_ARRAY; + + goto _exit; + } + if(context->last_symbol && _IS_FUNC(context->last_symbol, _core_dim)) { +#ifdef MB_SIMPLE_ARRAY + en = (sym[_sl - 1] == _STRING_POSTFIX_CHAR ? _DT_STRING : _DT_REAL); +#else /* MB_SIMPLE_ARRAY */ + en = _DT_REAL; +#endif /* MB_SIMPLE_ARRAY */ + memcpy(*value, &en, sizeof(en)); + + result = _DT_ARRAY; + + goto _exit; + } + /* _class_t */ +#ifdef MB_ENABLE_CLASS + if(context->last_symbol) { + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_ROUTINE) + goto _routine; + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_CLASS) { + if(_IS_FUNC(context->last_symbol, _core_class)) { + _handle_error_now(s, SE_RN_DUPLICATE_CLASS, s->source_file, MB_FUNC_ERR); + } + result = _DT_CLASS; + + goto _exit; + } + if(_IS_FUNC(context->last_symbol, _core_class)) { + if(s->last_instance) { + _handle_error_now(s, SE_RN_DUPLICATE_CLASS, s->source_file, MB_FUNC_ERR); + + goto _exit; + } + _begin_class(s); +#ifdef MB_ENABLE_UNICODE_ID + if(!_is_identifier_char(sym[0]) && !mb_uu_ischar(sym)) { +#else /* MB_ENABLE_UNICODE_ID */ + if(!_is_identifier_char(sym[0])) { +#endif /* MB_ENABLE_UNICODE_ID */ + result = _DT_NIL; + + goto _exit; + } + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_VAR) { + _handle_error_now(s, SE_RN_INVALID_CLASS, s->source_file, MB_FUNC_ERR); + + goto _exit; + } + + if(context->routine_state > 1) { + _handle_error_now(s, SE_RN_INVALID_CLASS, s->source_file, MB_FUNC_ERR); + + goto _exit; + } + + result = _DT_CLASS; + + goto _exit; + } else if(_IS_FUNC(context->last_symbol, _core_endclass)) { + if(_end_class(s)) + _pop_scope(s, false); + } + } +_routine: +#endif /* MB_ENABLE_CLASS */ + /* _routine_t */ + if(context->last_symbol && !_is_bracket_char(sym[0])) { + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_ROUTINE) { + if(_IS_FUNC(context->last_symbol, _core_def)) { + if(_begin_routine(s) != MB_FUNC_OK) + goto _exit; + } + result = _DT_ROUTINE; + + goto _exit; + } + if(_IS_FUNC(context->last_symbol, _core_def) || _IS_FUNC(context->last_symbol, _core_call)) { + if(_IS_FUNC(context->last_symbol, _core_def)) { + if(_begin_routine(s) != MB_FUNC_OK) + goto _exit; + } +#ifdef MB_ENABLE_UNICODE_ID + if(!_is_identifier_char(sym[0]) && !mb_uu_ischar(sym)) { +#else /* MB_ENABLE_UNICODE_ID */ + if(!_is_identifier_char(sym[0])) { +#endif /* MB_ENABLE_UNICODE_ID */ + result = _DT_NIL; + + goto _exit; + } + if(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_VAR) { + _handle_error_now(s, SE_RN_INVALID_ROUTINE, s->source_file, MB_FUNC_ERR); + + goto _exit; + } + + if(_IS_FUNC(context->last_symbol, _core_def)) { + if(context->routine_state > 1) { + _handle_error_now(s, SE_RN_INVALID_ROUTINE, s->source_file, MB_FUNC_ERR); + + goto _exit; + } + } + + result = _DT_ROUTINE; + + goto _exit; + } else if(_IS_FUNC(context->last_symbol, _core_enddef)) { + if(_end_routine(s)) + _pop_scope(s, false); + } + } + /* _func_t */ + if(!context->last_symbol || + (context->last_symbol && ((context->last_symbol->type == _DT_FUNC && context->last_symbol->data.func->pointer != _core_close_bracket && context->last_symbol->data.func->pointer != _core_mem) || + context->last_symbol->type == _DT_SEP || context->last_symbol->type == _DT_EOS))) { + if(strcmp("-", sym) == 0) { + ptr = (intptr_t)_core_neg; + memcpy(*value, &ptr, sizeof(intptr_t)); + + result = _DT_FUNC; + + goto _exit; + } + } + glbsyminscope = _find_func(s, sym, &mod); + if(glbsyminscope) { + if(context->last_symbol && context->last_symbol->type == _DT_ROUTINE) { + if(_sl == 1 && sym[0] == '(') { + if(context->routine_params_state == _ROUTINE_STATE_DEF) + _begin_routine_parameter_list(s); + } + } else if(context->routine_params_state == _ROUTINE_STATE_PARAMS) { + if(_sl == 1 && sym[0] == ')') + _end_routine_parameter_list(s); + } + +#ifdef MB_ENABLE_MODULE + if(mod) { + _module_func_t* mp = (_module_func_t*)glbsyminscope->data; + ptr = (intptr_t)mp->func; + memcpy(*value, &ptr, sizeof(intptr_t)); + } else { + ptr = (intptr_t)glbsyminscope->data; + memcpy(*value, &ptr, sizeof(intptr_t)); + if(ptr == (intptr_t)_core_def) + _begin_routine_definition(s); + } +#else /* MB_ENABLE_MODULE */ + ptr = (intptr_t)glbsyminscope->data; + memcpy(*value, &ptr, sizeof(intptr_t)); + if(ptr == (intptr_t)_core_def) + _begin_routine_definition(s); +#endif /* MB_ENABLE_MODULE */ + + result = _DT_FUNC; + + goto _exit; + } + /* MB_EOS */ + if(_sl == 1 && sym[0] == MB_EOS) { + if(_IS_EOS(context->last_symbol)) + result = _DT_NIL; + else + result = _DT_EOS; + + goto _exit; + } + /* Separator */ + if(_sl == 1 && _is_separator_char(sym[0])) { + result = _DT_SEP; + + goto _exit; + } + /* _var_t */ + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope) { + if(((_object_t*)glbsyminscope->data)->type != _DT_LABEL) { + memcpy(*value, &glbsyminscope->data, sizeof(glbsyminscope->data)); + + result = _DT_VAR; + + goto _exit; + } + } + /* _label_t */ + if(context->current_char == ':') { + if(!context->last_symbol || _IS_EOS(context->last_symbol)) { + glbsyminscope = _search_identifier_in_scope_chain(s, 0, sym, _PATHING_NONE, 0, 0); + if(glbsyminscope) + memcpy(*value, &glbsyminscope->data, sizeof(glbsyminscope->data)); + + result = _DT_LABEL; + + goto _exit; + } + } + if(context->last_symbol && (_IS_FUNC(context->last_symbol, _core_goto) || _IS_FUNC(context->last_symbol, _core_gosub))) { + result = _DT_LABEL; + + goto _exit; + } + /* Otherwise */ + result = _DT_VAR; + +_exit: + return result; +} + +/* Parse a character */ +static int _parse_char(mb_interpreter_t* s, const char* str, int n, int pos, unsigned short row, unsigned short col) { + int result = MB_FUNC_OK; + _parsing_context_t* context = 0; + char last_char = _ZERO_CHAR; + char c = _ZERO_CHAR; +#ifdef MB_ENABLE_UNICODE_ID + unsigned uc = 0; +#else /* MB_ENABLE_UNICODE_ID */ + mb_unrefvar(n); +#endif /* MB_ENABLE_UNICODE_ID */ + + mb_assert(s && s->parsing_context); + + context = s->parsing_context; + + if(str) { +#ifdef MB_ENABLE_UNICODE_ID + if(n == 0) + result = MB_FUNC_ERR; + else if(n == 1) + c = *str; + else + memcpy(&uc, str, n); +#else /* MB_ENABLE_UNICODE_ID */ + c = *str; +#endif /* MB_ENABLE_UNICODE_ID */ + } else { + c = MB_EOS; + } + + last_char = context->current_char; + context->current_char = c; + + switch(context->parsing_state) { + case _PS_NORMAL: +#ifdef MB_ENABLE_UNICODE_ID + if(uc) { + if(n == countof(_UNICODE_OPEN_QUOTE)) { + if(memcmp(str, _UNICODE_OPEN_QUOTE, n) == 0) { + _handle_error_at_pos(s, SE_PS_INVALID_CHAR, s->source_file, pos, row, col, MB_FUNC_ERR, _exit, result); + } + } + if(n == countof(_UNICODE_CLOSE_QUOTE)) { + if(memcmp(str, _UNICODE_CLOSE_QUOTE, n) == 0) { + _handle_error_at_pos(s, SE_PS_INVALID_CHAR, s->source_file, pos, row, col, MB_FUNC_ERR, _exit, result); + } + } + if(context->symbol_state == _SS_IDENTIFIER) { + _mb_check_exit(result = _append_uu_char_to_symbol(s, str, n), _exit); + } else if(context->symbol_state == _SS_OPERATOR) { + context->symbol_state = _SS_IDENTIFIER; + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_uu_char_to_symbol(s, str, n), _exit); + } + + break; + } +#endif /* MB_ENABLE_UNICODE_ID */ + + c = toupper(c); + if(_is_blank_char(c)) { /* \t space */ + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + } else if(_is_newline_char(c)) { /* \r \n EOF */ + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_char_to_symbol(s, MB_EOS), _exit); + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + } else if(_is_separator_char(c) || _is_bracket_char(c)) { /* , ; : ( ) */ + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + } else if(_is_quotation_char(c)) { /* " */ + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + context->parsing_state = _PS_STRING; + } else if(_is_comment_char(c)) { /* ' */ + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_char_to_symbol(s, MB_EOS), _exit); + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + context->parsing_state = _PS_COMMENT; + context->multi_line_comment_count = 1; + } else { + if(context->symbol_state == _SS_IDENTIFIER) { + if(_is_identifier_char(c)) { + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + } else if(_is_operator_char(c)) { + if(_is_exponent_prefix(context->current_symbol, 0, context->current_symbol_nonius - 2) && _is_exponential_char(last_char) && (c == '+' || c == '-')) { + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + } else { + context->symbol_state = _SS_OPERATOR; + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + } + } else { + _handle_error_at_pos(s, SE_PS_INVALID_CHAR, s->source_file, pos, row, col, MB_FUNC_ERR, _exit, result); + } + } else if(context->symbol_state == _SS_OPERATOR) { + if(_is_identifier_char(c)) { + context->symbol_state = _SS_IDENTIFIER; + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + } else if(_is_operator_char(c)) { + if(c == '-') + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + } else { + _handle_error_at_pos(s, SE_PS_INVALID_CHAR, s->source_file, pos, row, col, MB_FUNC_ERR, _exit, result); + } + } else { + mb_assert(0 && "Impossible."); + } + } + + break; + case _PS_STRING: + if(_is_quotation_char(c)) { /* " */ + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + _mb_check_exit(result = _cut_symbol(s, pos, row, col), _exit); + context->parsing_state = _PS_NORMAL; + } else { + _mb_check_exit(result = _append_char_to_symbol(s, c), _exit); + } + + break; + case _PS_COMMENT: + if(_is_eof_char(c)) { /* EOF */ + context->parsing_state = _PS_NORMAL; + + break; + } + if(context->multi_line_comment_count != 0 && c == _MULTI_LINE_COMMENT_PREFIX[context->multi_line_comment_count++]) { + if(context->multi_line_comment_count >= countof(_MULTI_LINE_COMMENT_PREFIX) - 1) { + context->parsing_state = _PS_MULTI_LINE_COMMENT; + context->multi_line_comment_count = 0; + + break; + } + + break; + } else { + context->multi_line_comment_count = 0; + } + if(_is_newline_char(c)) /* \r \n EOF */ + context->parsing_state = _PS_NORMAL; + + break; + case _PS_MULTI_LINE_COMMENT: + if(_is_eof_char(c)) { /* EOF */ + context->parsing_state = _PS_NORMAL; + + break; + } + if(_is_comment_char(c) && context->multi_line_comment_count == 0) { + context->multi_line_comment_count = 1; + } else if(context->multi_line_comment_count != 0 && c == _MULTI_LINE_COMMENT_POSTFIX[context->multi_line_comment_count++]) { + if(context->multi_line_comment_count >= countof(_MULTI_LINE_COMMENT_POSTFIX) - 1) { + context->parsing_state = _PS_NORMAL; + context->multi_line_comment_count = 0; + } + } else { + context->multi_line_comment_count = 0; + } + + break; + default: + mb_assert(0 && "Unknown parsing state."); + + break; + } + +_exit: + return result; +} + +/* Set the position of an error */ +static void _set_error_pos(mb_interpreter_t* s, int pos, unsigned short row, unsigned short col) { + mb_assert(s); + + s->last_error_pos = pos; + s->last_error_row = row; + s->last_error_col = col; +} + +/* Do something before importing another file */ +static char* _prev_import(mb_interpreter_t* s, char* lf, int* pos, unsigned short* row, unsigned short* col) { +#ifdef MB_ENABLE_SOURCE_TRACE + char* result = 0; + _parsing_context_t* context = 0; + _import_info_t* info = 0; + _object_t* obj = 0; + + mb_assert(s); + + context = s->parsing_context; + if(context) { + if(pos) *pos = context->parsing_pos; + if(row) *row = context->parsing_row; + if(col) *col = context->parsing_col; + context->parsing_pos = 0; + context->parsing_row = 1; + context->parsing_col = 0; + } + + result = s->source_file; + s->source_file = lf; + + obj = _create_object(); + obj->type = _DT_EOS; + obj->is_ref = false; + _ls_pushback(s->ast, obj); + + info = (_import_info_t*)mb_malloc(sizeof(_import_info_t)); + info->source_file = lf ? mb_strdup(lf, strlen(lf) + 1) : 0; + obj = _create_object(); + obj->type = _DT_PREV_IMPORT; + obj->is_ref = false; + obj->data.import_info = info; + _ls_pushback(s->ast, obj); + + return result; +#else /* MB_ENABLE_SOURCE_TRACE */ + mb_unrefvar(s); + mb_unrefvar(lf); + mb_unrefvar(pos); + mb_unrefvar(row); + mb_unrefvar(col); + + return 0; +#endif /* MB_ENABLE_SOURCE_TRACE */ +} + +/* Do something after importing another file */ +static char* _post_import(mb_interpreter_t* s, char* lf, int* pos, unsigned short* row, unsigned short* col) { +#ifdef MB_ENABLE_SOURCE_TRACE + char* result = 0; + _parsing_context_t* context = 0; + _import_info_t* info = 0; + _object_t* obj = 0; + + mb_assert(s); + + context = s->parsing_context; + if(context) { + if(pos) context->parsing_pos = *pos; + if(row) context->parsing_row = *row; + if(col) context->parsing_col = *col; + } + + s->source_file = lf; + result = s->source_file; + + info = (_import_info_t*)mb_malloc(sizeof(_import_info_t)); + info->source_file = lf ? mb_strdup(lf, strlen(lf) + 1) : 0; + obj = _create_object(); + obj->type = _DT_POST_IMPORT; + obj->is_ref = false; + obj->data.import_info = info; + _ls_pushback(s->ast, obj); + + obj = _create_object(); + obj->type = _DT_EOS; + obj->is_ref = false; + _ls_pushback(s->ast, obj); + + return result; +#else /* MB_ENABLE_SOURCE_TRACE */ + mb_unrefvar(s); + mb_unrefvar(lf); + mb_unrefvar(pos); + mb_unrefvar(row); + mb_unrefvar(col); + + return 0; +#endif /* MB_ENABLE_SOURCE_TRACE */ +} + +/** Object processors */ + +/* Get the size of a data type */ +static int_t _get_size_of(_data_e type) { + int_t result = 0; + +#ifdef MB_SIMPLE_ARRAY + if(type == _DT_INT) { + result = sizeof(int_t); + } else if(type == _DT_REAL) { + result = sizeof(real_t); + } else if(type == _DT_STRING) { + result = sizeof(char*); + } else { + mb_assert(0 && "Unsupported."); + } +#else /* MB_SIMPLE_ARRAY */ + mb_unrefvar(type); + + result = sizeof(_raw_u); +#endif /* MB_SIMPLE_ARRAY */ + + return result; +} + +/* Try to get a value (typed as int_t, real_t or char*) */ +static bool_t _try_get_value(_object_t* obj, mb_value_u* val, _data_e expected) { + bool_t result = false; + + mb_assert(obj && val); + + if(obj->type == _DT_INT && (expected == _DT_UNKNOWN || expected == _DT_INT)) { + val->integer = obj->data.integer; + result = true; + } else if(obj->type == _DT_REAL && (expected == _DT_UNKNOWN || expected == _DT_REAL)) { + val->float_point = obj->data.float_point; + result = true; + } else if(obj->type == _DT_VAR) { + result = _try_get_value(obj->data.variable->data, val, expected); + } + + return result; +} + +/* Determine if an object is a nil */ +static bool_t _is_nil(void* obj) { + bool_t result = false; + _object_t* o = 0; + + mb_assert(obj); + + o = (_object_t*)obj; + if(o->type == _DT_NIL) + result = true; + else if(o->type == _DT_VAR) + result = o->data.variable->data->type == _DT_NIL; + + return result; +} + +/* Determine if an object is a number */ +static bool_t _is_number(void* obj) { + bool_t result = false; + _object_t* o = 0; + + mb_assert(obj); + + o = (_object_t*)obj; + if(o->type == _DT_INT || o->type == _DT_REAL) + result = true; + else if(o->type == _DT_VAR) + result = o->data.variable->data->type == _DT_INT || o->data.variable->data->type == _DT_REAL; + + return result; +} + +/* Determine if an object is a string value or a string variable */ +static bool_t _is_string(void* obj) { + bool_t result = false; + _object_t* o = 0; + + mb_assert(obj); + + o = (_object_t*)obj; + if(o->type == _DT_STRING) + result = true; + else if(o->type == _DT_VAR) + result = o->data.variable->data->type == _DT_STRING; + + return result; +} + +/* Extract a string from an object */ +static char* _extract_string(_object_t* obj) { + char* result = 0; + + mb_assert(obj); + + if(obj->type == _DT_STRING) + result = obj->data.string; + else if(obj->type == _DT_VAR && obj->data.variable->data->type == _DT_STRING) + result = obj->data.variable->data->data.string; + + if(!result) + result = MB_NULL_STRING; + + return result; +} + +#ifdef MB_MANUAL_REAL_FORMATTING +/* Convert a real number to string */ +static void _real_to_str(real_t r, char* str, size_t size, size_t afterpoint) { + size_t pos = 0; + size_t len = 0; + char curr[4]; + int val = (int)r; + char dot = 0; + + itoa(val, str, 10); + if(r < 0) { + r *= -1; + val *= -1; + } + pos = len = strlen(str); + while(pos < size - 1) { + r = r - (real_t)val; + if(r == 0.0) + break; + if(!dot) { + dot = 1; + str[pos++] = '.'; + } + r *= 10; + val = (int)r; + itoa(val, curr, 10); + str[pos++] = *curr; + if(--afterpoint == 0) + break; + } + str[pos] = _ZERO_CHAR; +} +#endif /* MB_MANUAL_REAL_FORMATTING */ + +/* Convert a real number to string the standard way */ +static void _real_to_str_std(real_t r, char* str, size_t size) { + size_t i = 0; + + if((size_t)sprintf(str, MB_REAL_FMT, r) >= size) { + mb_assert(0 && "Buffer overflow."); + } + for(i = 0; i < size; ++i) { + if(str[i] == ',') { + str[i] = '.'; + + break; + } else if(str[i] == _ZERO_CHAR) { + break; + } + } +} + +#ifdef _HAS_REF_OBJ_LOCK +/* Lock a referenced object */ +static bool_t _lock_ref_object(_lock_t* lk, _ref_t* ref, void* obj) { + mb_assert(lk); + + _ref(ref, obj); + if(*lk >= 0) + ++(*lk); + else + --(*lk); + + return true; +} + +/* Unlock a referenced object */ +static bool_t _unlock_ref_object(_lock_t* lk, _ref_t* ref, void* obj) { + bool_t result = true; + + mb_assert(lk); + + if(*lk > 0) + --(*lk); + else if(*lk < 0) + ++(*lk); + else + result = false; + _unref(ref, obj); + + return result; +} + +/* Write operation on a referenced object */ +static bool_t _write_on_ref_object(_lock_t* lk, _ref_t* ref, void* obj) { + bool_t result = true; + mb_unrefvar(ref); + mb_unrefvar(obj); + + mb_assert(lk); + + if(*lk > 0) + *lk = -(*lk); + else + result = false; + + return result; +} +#endif /* _HAS_REF_OBJ_LOCK */ + +/* Tell whether an object is referenced */ +static bool_t _is_ref(_object_t* obj) { + switch(obj->type) { +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + return true; +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_ARRAY_REF + case _DT_ARRAY: + return true; +#endif /* MB_ENABLE_ARRAY_REF */ +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST: + return true; + case _DT_DICT: + return true; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + return true; +#endif /* MB_ENABLE_CLASS */ + case _DT_ROUTINE: +#ifdef MB_ENABLE_LAMBDA + return obj->data.routine->type == MB_RT_LAMBDA; +#else /* MB_ENABLE_LAMBDA */ + return false; +#endif /* MB_ENABLE_LAMBDA */ + default: + return false; + } +} + +/* Increase the reference of a stub by 1 */ +static _ref_count_t _ref(_ref_t* ref, void* data) { + _ref_count_t before = *ref->count; + + mb_assert((intptr_t)ref == (intptr_t)data); + + ++(*ref->count); + if(before > *ref->count) { + mb_assert(0 && "Too many referencing, count overflow, please redefine _ref_count_t."); + + _handle_error_now(ref->s, SE_RN_OVERFLOW, ref->s->last_error_file, MB_FUNC_ERR); + } + + return *ref->count; +} + +/* Decrease the reference of a stub by 1 */ +static bool_t _unref(_ref_t* ref, void* data) { + bool_t result = true; + _gc_t* gc = 0; + bool_t cld = false; + + mb_assert((intptr_t)ref == (intptr_t)data); + + cld = *ref->count == _NONE_REF + 1; + do { + gc = &ref->s->gc; + result = --(*ref->count) == _NONE_REF; + mb_assert(*ref->count >= _NONE_REF); + _gc_add(ref, data, &ref->s->gc); + if(ref->count && *ref->count == _NONE_REF) + _collect_intermediate_value(ref, data); + ref->on_unref(ref, data); + if(result) + _gc_remove(ref, data, gc); + } while(0); + if(cld) { + _ht_set_or_insert(gc->collected_table, ref, data); + _ht_set_or_insert(gc->collected_table, data, ref); + } + + return result; +} + +/* Increase the weak reference of a stub by 1 */ +static _ref_count_t _weak_ref(_ref_t* ref, void* data, _ref_t* weak) { + _ref_count_t before = *ref->weak_count; + mb_unrefvar(data); + + ++(*ref->weak_count); + if(before > *ref->weak_count) { + mb_assert(0 && "Too many referencing, weak count overflow, please redefine _ref_count_t."); + + _handle_error_now(ref->s, SE_RN_OVERFLOW, ref->s->last_error_file, MB_FUNC_ERR); + } + memcpy(weak, ref, sizeof(_ref_t)); + + return *ref->weak_count; +} + +/* Decrease the weak reference of a stub by 1 */ +static bool_t _weak_unref(_ref_t* weak) { + bool_t result = true; + + --(*weak->weak_count); + mb_assert(*weak->weak_count >= _NONE_REF); + if(weak->count && *weak->count == _NONE_REF) + result = false; + if(weak->count && *weak->count == _NONE_REF && weak->weak_count && *weak->weak_count == _NONE_REF) { + safe_free(weak->weak_count); + safe_free(weak->count); + } + + return result; +} + +/* Create a reference stub, initialize the reference count with zero */ +static void _create_ref(_ref_t* ref, _unref_func_t dtor, _data_e t, mb_interpreter_t* s) { +#ifdef MB_ENABLE_FORK + mb_interpreter_t* src = 0; +#endif /* MB_ENABLE_FORK */ + + if(ref->count) + return; + +#ifdef MB_ENABLE_FORK + while(mb_get_forked_from(s, &src) == MB_FUNC_OK) + s = src; +#endif /* MB_ENABLE_FORK */ + + ref->count = (_ref_count_t*)mb_malloc(sizeof(_ref_count_t)); + *ref->count = _NONE_REF; + ref->weak_count = (_ref_count_t*)mb_malloc(sizeof(_ref_count_t)); + *ref->weak_count = _NONE_REF; + ref->on_unref = dtor; + ref->type = t; + ref->s = s; +} + +/* Destroy a reference stub */ +static void _destroy_ref(_ref_t* ref) { + if(!ref->count || !ref->weak_count) + return; + + if(*ref->weak_count == _NONE_REF) { + safe_free(ref->weak_count); + safe_free(ref->count); + } + ref->on_unref = 0; +} + +/* Add a referenced object to GC table for later garbage detection */ +static void _gc_add(_ref_t* ref, void* data, _gc_t* gc) { + _ht_node_t* table = 0; + + mb_assert(ref && data); + + if(!ref->count) + return; + + if(gc && _ht_find(gc->collected_table, ref)) + _ht_remove(gc->collected_table, ref, 0); + + if(!gc->table) + return; + + if(gc->collecting) + table = gc->recursive_table; + else + table = gc->table; + + if(gc && gc->valid_table && _ht_find(gc->valid_table, ref)) + _ht_remove(table, ref, 0); + else if(ref->count && *ref->count > _NONE_REF) + _ht_set_or_insert(table, ref, data); + else + _ht_remove(table, ref, 0); +} + +/* Remove a referenced object from GC */ +static unsigned _gc_remove(_ref_t* ref, void* data, _gc_t* gc) { + _ht_node_t* table = 0; + + mb_assert(ref && data && gc); + + if(gc->collecting) + table = gc->recursive_table; + else + table = gc->table; + + if(table) + return _ht_remove(table, ref, 0); + + return 0; +} + +/* Get reachable objects */ +static int _gc_add_reachable(void* data, void* extra, void* h) { + int result = _OP_RESULT_NORMAL; + _object_t* obj = 0; + _var_t* var = 0; + _ht_node_t* ht = 0; + + mb_assert(data && h); + + ht = (_ht_node_t*)h; + obj = (_object_t*)data; + if(_is_internal_object(obj)) + goto _exit; + switch(obj->type) { + case _DT_VAR: + var = (_var_t*)obj->data.variable; + _gc_add_reachable(var->data, extra, ht); + + break; +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + if(!_ht_find(ht, &obj->data.usertype_ref->ref)) + _ht_set_or_insert(ht, &obj->data.usertype_ref->ref, obj->data.usertype_ref); +#ifdef MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF + if(obj->data.usertype_ref->alive_checker) { + mb_value_t val; + mb_make_nil(val); + _internal_object_to_public_value(obj, &val); + obj->data.usertype_ref->alive_checker(obj->data.usertype_ref->ref.s, h, val, _gc_alive_marker); + } +#endif /* MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF */ + + break; +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_ARRAY_REF + case _DT_ARRAY: + if(!_ht_find(ht, &obj->data.array->ref)) + _ht_set_or_insert(ht, &obj->data.array->ref, obj->data.array); + + break; +#endif /* MB_ENABLE_ARRAY_REF */ +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST: + if(!_ht_find(ht, &obj->data.list->ref)) { + _ht_set_or_insert(ht, &obj->data.list->ref, obj->data.list); + _LS_FOREACH(obj->data.list->list, _do_nothing_on_object, _gc_add_reachable, ht); + } + + break; + case _DT_DICT: + if(!_ht_find(ht, &obj->data.dict->ref)) { + _ht_set_or_insert(ht, &obj->data.dict->ref, obj->data.dict); + _HT_FOREACH(obj->data.dict->dict, _do_nothing_on_object, _gc_add_reachable_both, ht); + } + + break; + case _DT_LIST_IT: + if(!_ht_find(ht, &obj->data.list_it->list->ref)) { + _ht_set_or_insert(ht, &obj->data.list_it->list->ref, obj->data.list_it->list); + _LS_FOREACH(obj->data.list_it->list->list, _do_nothing_on_object, _gc_add_reachable, ht); + } + + break; + case _DT_DICT_IT: + if(!_ht_find(ht, &obj->data.dict_it->dict->ref)) { + _ht_set_or_insert(ht, &obj->data.dict_it->dict->ref, obj->data.dict_it->dict); + _HT_FOREACH(obj->data.dict_it->dict->dict, _do_nothing_on_object, _gc_add_reachable_both, ht); + } + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + if(!_ht_find(ht, &obj->data.instance->ref)) { + _ht_set_or_insert(ht, &obj->data.instance->ref, obj->data.instance); + _traverse_class(obj->data.instance, _gc_add_reachable, _add_class_meta_reachable, _META_LIST_MAX_DEPTH, false, ht, 0); + } + + break; +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_LAMBDA + case _DT_ROUTINE: + if(obj->data.routine->type == MB_RT_LAMBDA) { + if(!_ht_find(ht, &obj->data.routine->func.lambda.ref)) { + _running_context_ref_t* outs = obj->data.routine->func.lambda.outer_scope; + _ht_set_or_insert(ht, &obj->data.routine->func.lambda.ref, obj->data.routine); + _HT_FOREACH(obj->data.routine->func.lambda.scope->var_dict, _do_nothing_on_object, _gc_add_reachable, ht); + while(outs) { + _ht_set_or_insert(ht, &outs->ref, outs); + _HT_FOREACH(outs->scope->var_dict, _do_nothing_on_object, _gc_add_reachable, ht); + outs = outs->prev; + } + } + } + + break; +#endif /* MB_ENABLE_LAMBDA */ + default: /* Do nothing */ + break; + } + +_exit: + return result; +} + +/* Get reachable objects from both key and value */ +static int _gc_add_reachable_both(void* data, void* extra, void* h) { + int result = _OP_RESULT_NORMAL; + + mb_assert(data && extra && h); + + _gc_add_reachable(extra, 0, h); + _gc_add_reachable(data, extra, h); + + return result; +} + +#ifdef MB_ENABLE_FORK +/* Get reachable objects in a forked environment */ +static int _gc_get_reachable_in_forked(void* data, void* extra, _ht_node_t* valid) { + int result = _OP_RESULT_NORMAL; + mb_interpreter_t* s = 0; + _running_context_t* root = 0; + mb_unrefvar(extra); + + mb_assert(data); + + s = (mb_interpreter_t*)data; + root = _get_root_scope(s->running_context); + _gc_get_reachable(s, valid, root); + + return result; +} +#endif /* MB_ENABLE_FORK */ + +/* Get all reachable referenced objects */ +static void _gc_get_reachable(mb_interpreter_t* s, _ht_node_t* ht, _running_context_t* end) { + _running_context_t* running = 0; + _ht_node_t* scope = 0; + + mb_assert(s && ht); + + running = s->running_context; + while(running && running != end) { + scope = running->var_dict; + if(scope) { + _HT_FOREACH(scope, _do_nothing_on_object, _gc_add_reachable, ht); + } + running = running->prev; + } +} + +/* Alive marker functor of a value */ +static void _gc_alive_marker(mb_interpreter_t* s, void* h, mb_value_t val) { + _ht_node_t* ht = 0; + _object_t obj; + mb_unrefvar(s); + + ht = (_ht_node_t*)h; + _MAKE_NIL(&obj); + _public_value_to_internal_object(&val, &obj); + _gc_add_reachable(&obj, 0, h); +} + +/* Destroy only the capsule (wrapper) of an object, leave the data behind, and add it to GC if possible */ +static int _gc_destroy_garbage_in_list(void* data, void* extra, _gc_t* gc) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(data); + + obj = (_object_t*)data; + _ADDGC(obj, gc, false) + safe_free(obj); + + return result; +} + +/* Destroy only the capsule (wrapper) of an object, leave the data behind, deal with extra as well, and add it to GC if possible */ +static int _gc_destroy_garbage_in_dict(void* data, void* extra, _gc_t* gc) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + + mb_assert(data); + + obj = (_object_t*)data; + _ADDGC(obj, gc, false) + safe_free(obj); + + obj = (_object_t*)extra; + _ADDGC(obj, gc, false) + safe_free(obj); + + return result; +} + +#ifdef MB_ENABLE_CLASS +/* Destroy only the capsule (wrapper) of an object, leave the data behind, deal with extra as well, and add it to GC if possible */ +static int _gc_destroy_garbage_in_class(void* data, void* extra, _gc_t* gc) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(data); + + obj = (_object_t*)data; + if(_IS_VAR(obj)) { + _gc_destroy_garbage_in_class(obj->data.variable->data, 0, gc); + safe_free(obj->data.variable->name); + safe_free(obj->data.variable); + } else { + _ADDGC(obj, gc, true) + } + safe_free(obj); + + return result; +} +#endif /* MB_ENABLE_CLASS */ + +#ifdef MB_ENABLE_LAMBDA +/* Destroy only the capsule (wrapper) of an object, leave the data behind, deal with extra as well, and add it to GC if possible */ +static int _gc_destroy_garbage_in_lambda(void* data, void* extra, _gc_t* gc) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(data); + + obj = (_object_t*)data; + if(_IS_VAR(obj)) { +#ifdef MB_ENABLE_CLASS + if(_is_string(obj) && obj->data.variable->pathing) { + safe_free(obj->data.variable->data); + } else { + _gc_destroy_garbage_in_lambda(obj->data.variable->data, 0, gc); + } +#else /* MB_ENABLE_CLASS */ + _gc_destroy_garbage_in_lambda(obj->data.variable->data, 0, gc); +#endif /* MB_ENABLE_CLASS */ + safe_free(obj->data.variable->name); + safe_free(obj->data.variable); + } else { + _ADDGC(obj, gc, false) + } + safe_free(obj); + + return result; +} + +/* Collect garbage of outer scopes */ +static void _gc_destroy_garbage_in_outer_scope(_running_context_ref_t* p, _gc_t* gc) { + while(p) { + _running_context_ref_t* scope = p; + p = p->prev; + _HT_FOREACH(scope->scope->var_dict, _do_nothing_on_object, _gc_destroy_garbage_in_lambda, gc); + _ht_clear(scope->scope->var_dict); + } +} +#endif /* MB_ENABLE_LAMBDA */ + +/* Collect a garbage */ +static int _gc_destroy_garbage(void* data, void* extra, _gc_t* gc) { + int result = _OP_RESULT_NORMAL; + _ref_t* ref = 0; + bool_t proc = true; +#ifdef MB_ENABLE_COLLECTION_LIB + _list_t* lst = 0; + _dict_t* dct = 0; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_LAMBDA + _routine_t* routine = 0; + _running_context_ref_t* outs = 0; +#endif /* MB_ENABLE_LAMBDA */ + + mb_assert(data && extra); + + ref = (_ref_t*)extra; + if(_ht_find(gc->collected_table, ref)) { + proc = true; + + goto _exit; + } + switch(ref->type) { +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST: + lst = (_list_t*)data; + _LS_FOREACH(lst->list, _do_nothing_on_object, _gc_destroy_garbage_in_list, gc); + _ls_clear(lst->list); + lst->count = 0; + + break; + case _DT_DICT: + dct = (_dict_t*)data; + _HT_FOREACH(dct->dict, _do_nothing_on_object, _gc_destroy_garbage_in_dict, gc); + _ht_clear(dct->dict); + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_LAMBDA + case _DT_ROUTINE: + routine = (_routine_t*)data; + if(routine->type == MB_RT_LAMBDA) { + _HT_FOREACH(routine->func.lambda.scope->var_dict, _do_nothing_on_object, _gc_destroy_garbage_in_lambda, gc); + _ht_clear(routine->func.lambda.scope->var_dict); + outs = routine->func.lambda.outer_scope; + if(outs) { + if(!_ht_find(gc->collected_table, &outs->ref)) + _gc_add(&outs->ref, outs, gc); + routine->func.lambda.outer_scope = 0; + } + while(outs) { + if(_ht_find(gc->collected_table, &outs->ref)) + break; + _HT_FOREACH(outs->scope->var_dict, _do_nothing_on_object, _remove_this_lambda_from_upvalue, routine); + outs = outs->prev; + } + } + + break; +#endif /* MB_ENABLE_LAMBDA */ +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: /* Fall through */ +#endif /* MB_ENABLE_USERTYPE_REF */ + case _DT_ARRAY: /* Do nothing */ + break; + default: + proc = false; + + break; + } + if(proc && ref->count) + _unref(ref, data); + +_exit: + if(proc) + result = _OP_RESULT_DEL_NODE; + + return result; +} + +#ifdef MB_ENABLE_CLASS +/* Collect a class instance */ +static int _gc_destroy_garbage_class(void* data, void* extra, _gc_t* gc) { + int result = _OP_RESULT_NORMAL; + _ref_t* ref = 0; + bool_t proc = true; + _class_t* instance = 0; + + mb_assert(data && extra); + + ref = (_ref_t*)extra; + if(_ht_find(gc->collected_table, ref)) { + proc = true; + + goto _exit; + } + switch(ref->type) { + case _DT_CLASS: + instance = (_class_t*)data; + _HT_FOREACH(instance->scope->var_dict, _do_nothing_on_object, _gc_destroy_garbage_in_class, gc); + _ht_clear(instance->scope->var_dict); + _ls_clear(instance->meta_list); +#ifdef MB_ENABLE_LAMBDA + if(instance->scope->refered_lambdas) { + _ls_destroy(instance->scope->refered_lambdas); + instance->scope->refered_lambdas = 0; + } +#endif /* MB_ENABLE_LAMBDA */ + + break; + default: + proc = false; + + break; + } + if(proc && ref->count) + _unref(ref, data); + +_exit: + if(proc) + result = _OP_RESULT_DEL_NODE; + + return result; +} +#endif /* MB_ENABLE_CLASS */ + +#ifdef MB_ENABLE_LAMBDA +/* Collect an outer scope */ +static int _gc_destroy_garbage_outer_scope(void* data, void* extra, _gc_t* gc) { + int result = _OP_RESULT_NORMAL; + _ref_t* ref = 0; + bool_t proc = true; + _running_context_ref_t* outs = 0; + + mb_assert(data && extra); + + ref = (_ref_t*)extra; + if(_ht_find(gc->collected_table, ref)) { + proc = true; + + goto _exit; + } + switch(ref->type) { + case _DT_OUTER_SCOPE: + outs = (_running_context_ref_t*)data; + if(!_ht_find(gc->collected_table, &outs->ref)) + _gc_destroy_garbage_in_outer_scope(outs, gc); + + break; + default: + proc = false; + + break; + } + if(proc && ref->count) + _unref(ref, data); + +_exit: + if(proc) + result = _OP_RESULT_DEL_NODE; + + return result; +} +#endif /* MB_ENABLE_LAMBDA */ + +/* Swap active garbage table and recursive table */ +static void _gc_swap_tables(mb_interpreter_t* s) { + _ht_node_t* tmp = 0; + + mb_assert(s); + + tmp = s->gc.table; + s->gc.table = s->gc.recursive_table; + s->gc.recursive_table = tmp; +} + +/* Try trigger garbage collection */ +static void _gc_try_trigger(mb_interpreter_t* s) { + if(!_GCNOW(s)) + return; + + if(_ht_count(s->gc.table) >= MB_GC_GARBAGE_THRESHOLD) + _gc_collect_garbage(s, 1); +} + +/* Collect all garbage */ +static void _gc_collect_garbage(mb_interpreter_t* s, int depth) { +#ifdef MB_ENABLE_FORK + mb_interpreter_t* src = 0; +#endif /* MB_ENABLE_FORK */ + _ht_node_t* valid = 0; + _gc_t* gc = 0; + + mb_assert(s); + +#ifdef MB_ENABLE_FORK + while(mb_get_forked_from(s, &src) == MB_FUNC_OK) + s = src; +#endif /* MB_ENABLE_FORK */ + + gc = &s->gc; + + /* Check whether it's paused */ + if(gc->disabled) + return; + + /* Prepare for GC */ + if(gc->collecting) + return; + gc->collecting++; + _PREPAREGC(s, gc); + + /* Get reachable information */ + valid = _ht_create(0, _ht_cmp_ref, _ht_hash_ref, _do_nothing_on_object); + if(depth != -1) + gc->valid_table = valid; + _gc_get_reachable(s, valid, 0); +#ifdef MB_ENABLE_FORK + if(s->all_forked) { + _LS_FOREACH(s->all_forked, _do_nothing_on_object, _gc_get_reachable_in_forked, valid); + } +#endif /* MB_ENABLE_FORK */ + if(s->alive_check_handler) + s->alive_check_handler(s, valid, _gc_alive_marker); + + /* Get unreachable information */ + _HT_FOREACH(valid, _do_nothing_on_object, _ht_remove_existing, gc->table); + _HT_FOREACH(valid, _do_nothing_on_object, _ht_remove_existing, gc->recursive_table); + + /* Collect garbage */ + _PREVGC(s, gc); + do { +#ifdef MB_ENABLE_CLASS + _HT_FOREACH(gc->table, _do_nothing_on_object, _gc_destroy_garbage_class, &s->gc); +#endif /* MB_ENABLE_CLASS */ + _HT_FOREACH(gc->table, _do_nothing_on_object, _gc_destroy_garbage, &s->gc); +#ifdef MB_ENABLE_LAMBDA + _HT_FOREACH(gc->table, _do_nothing_on_object, _gc_destroy_garbage_outer_scope, &s->gc); +#endif /* MB_ENABLE_LAMBDA */ + _ht_clear(gc->table); + if(gc->collecting > 1) + gc->collecting--; + + if(!depth || !_ht_count(gc->recursive_table)) + break; + + _gc_swap_tables(s); + gc->collecting++; + } while(1); + _POSTGC(s, gc); + + /* Tidy */ + _ht_clear(gc->collected_table); + gc->valid_table = 0; + _ht_clear(valid); + _ht_destroy(valid); + gc->collecting--; + mb_assert(!gc->collecting); +} + +#ifdef MB_ENABLE_USERTYPE_REF +/* Create a referenced usertype */ +static _usertype_ref_t* _create_usertype_ref(mb_interpreter_t* s, void* val, mb_dtor_func_t un, mb_clone_func_t cl, mb_hash_func_t hs, mb_cmp_func_t cp, mb_fmt_func_t ft) { + _usertype_ref_t* result = 0; + + mb_assert(s); + + result = (_usertype_ref_t*)mb_malloc(sizeof(_usertype_ref_t)); + memset(result, 0, sizeof(_usertype_ref_t)); + result->usertype = val; + result->dtor = un; + result->clone = cl; + result->hash = hs; + result->cmp = cp; + result->fmt = ft; + _create_ref(&result->ref, _unref_usertype_ref, _DT_USERTYPE_REF, s); + + return result; +} + +/* Destroy a referenced usertype */ +static void _destroy_usertype_ref(_usertype_ref_t* c) { + mb_assert(c); + + if(c->dtor) + c->dtor(c->ref.s, c->usertype); + if(c->calc_operators) { + safe_free(c->calc_operators); + } + _destroy_ref(&c->ref); + safe_free(c); +} + +/* Unreference a referenced usertype */ +static void _unref_usertype_ref(_ref_t* ref, void* data) { + mb_assert(ref); + + if(*ref->count == _NONE_REF) + _destroy_usertype_ref((_usertype_ref_t*)data); +} + +/* Clone a referenced usertype to a target object */ +static void _clone_usertype_ref(_usertype_ref_t* src, _object_t* tgt) { + void* cpy = 0; + + mb_assert(src && tgt); + + _MAKE_NIL(tgt); + + if(!src->clone) + return; + cpy = src->clone(src->ref.s, src->usertype); + if(!cpy) + return; + tgt->type = _DT_USERTYPE_REF; + tgt->data.usertype_ref = _create_usertype_ref( + src->ref.s, cpy, + src->dtor, src->clone, src->hash, src->cmp, src->fmt + ); +#ifdef MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF + tgt->data.usertype_ref->alive_checker = src->alive_checker; +#endif /* MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF */ + if(src->calc_operators) { + tgt->data.usertype_ref->calc_operators = (_calculation_operator_info_t*)mb_malloc(sizeof(_calculation_operator_info_t)); + memcpy(tgt->data.usertype_ref->calc_operators, src->calc_operators, sizeof(_calculation_operator_info_t)); + } + tgt->data.usertype_ref->coll_func = src->coll_func; + tgt->data.usertype_ref->generic_func = src->generic_func; + _ref(&tgt->data.usertype_ref->ref, tgt->data.usertype_ref); +} + +/* Try to call a registered function on a referenced usertype */ +static bool_t _try_call_func_on_usertype_ref(mb_interpreter_t* s, _ls_node_t** ast, _object_t* obj, _ls_node_t* pathed, int* ret) { + _object_t* tmp = (_object_t*)pathed->data; + if(_IS_VAR(tmp) && tmp->data.variable->data->type == _DT_USERTYPE_REF) { + bool_t mod = false; + _ls_node_t* fn = 0; + mb_func_t func = 0; + char* r = strrchr(obj->data.variable->name, '.'); + if(!r) return false; + ++r; + fn = _find_func(s, r, &mod); + if(fn && fn->data) { +#ifdef MB_ENABLE_MODULE + if(mod) { + _module_func_t* mp = (_module_func_t*)fn->data; + func = (mb_func_t)(intptr_t)mp->func; + } else { + func = (mb_func_t)(intptr_t)fn->data; + } +#else /* MB_ENABLE_MODULE */ + func = (mb_func_t)(intptr_t)fn->data; +#endif /* MB_ENABLE_MODULE */ + s->usertype_ref_ahead = (_object_t*)tmp->data.variable->data; +#ifdef MB_ENABLE_STACK_TRACE + _ls_pushback(s->stack_frames, r); +#endif /* MB_ENABLE_STACK_TRACE */ + if(ret) + *ret = (func)(s, (void**)ast); + else + (func)(s, (void**)ast); +#ifdef MB_ENABLE_STACK_TRACE + _ls_popback(s->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ + + return true; + } + } + + return false; +} +#endif /* MB_ENABLE_USERTYPE_REF */ + +/* Create an array */ +static _array_t* _create_array(mb_interpreter_t* s, const char* n, _data_e t) { + _array_t* result = 0; + + mb_assert(s); + + result = (_array_t*)mb_malloc(sizeof(_array_t)); + memset(result, 0, sizeof(_array_t)); + result->type = t; + result->name = (char*)n; +#ifdef MB_ENABLE_ARRAY_REF + _create_ref(&result->ref, _unref_array, _DT_ARRAY, s); + _ref(&result->ref, result); +#else /* MB_ENABLE_ARRAY_REF */ + mb_unrefvar(s); +#endif /* MB_ENABLE_ARRAY_REF */ + + return result; +} + +/* Destroy an array */ +static void _destroy_array(_array_t* arr) { + mb_assert(arr); + + _clear_array(arr); + if(arr->name) { + safe_free(arr->name); + } +#ifndef MB_SIMPLE_ARRAY + if(arr->types) { + safe_free(arr->types); + } +#endif /* MB_SIMPLE_ARRAY */ +#ifdef MB_ENABLE_ARRAY_REF + _destroy_ref(&arr->ref); +#endif /* MB_ENABLE_ARRAY_REF */ + safe_free(arr); +} + +/* Initialize an array */ +static void _init_array(_array_t* arr) { + int elemsize = 0; + + mb_assert(arr); + +#ifdef MB_SIMPLE_ARRAY + elemsize = (int)_get_size_of(arr->type); +#else /* MB_SIMPLE_ARRAY */ + elemsize = (int)_get_size_of(_DT_UNKNOWN); +#endif /* MB_SIMPLE_ARRAY */ + mb_assert(arr->count > 0); + mb_assert(!arr->raw); + arr->raw = (void*)mb_malloc(elemsize * arr->count); + if(arr->raw) + memset(arr->raw, 0, elemsize * arr->count); +#ifndef MB_SIMPLE_ARRAY + arr->types = (_data_e*)mb_malloc(sizeof(_data_e) * arr->count); + if(arr->types) { + unsigned ul = 0; + for(ul = 0; ul < arr->count; ++ul) + arr->types[ul] = _DT_INT; + } +#endif /* MB_SIMPLE_ARRAY */ +} + +/* Clone an array */ +static _array_t* _clone_array(mb_interpreter_t* s, _array_t* arr) { + _array_t* result = 0; + unsigned index = 0; + mb_value_u val; + _data_e type = _DT_NIL; + + mb_assert(s && arr); + + result = _create_array(s, mb_strdup(arr->name, 0), arr->type); + result->count = arr->count; + result->dimension_count = arr->dimension_count; + memcpy(result->dimensions, arr->dimensions, sizeof(result->dimensions)); + _init_array(result); + for(index = 0; index < arr->count; index++) { + _get_array_elem(s, arr, index, &val, &type); + _set_array_elem(s, 0, result, index, &val, &type); + } + + return result; +} + +/* Calculate the true index of an array */ +static int _get_array_pos(mb_interpreter_t* s, _array_t* arr, int* d, int c) { + int result = 0; + int i = 0; + unsigned n = 0; + int f = 1; + + mb_assert(s && arr && d); + + if(c < 0 || c > arr->dimension_count) { + result = -1; + + goto _exit; + } + for(i = 0; i < c; i++) { + n = d[i]; + if(n >= arr->dimensions[i]) { + result = -1; + + goto _exit; + } + result += n * f; + f *= arr->dimensions[i]; + } + +_exit: + return result; +} + +/* Calculate the true index of an array, used when walking through an AST */ +static int _get_array_index(mb_interpreter_t* s, _ls_node_t** l, _object_t* c, unsigned* index, bool_t* literally) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* arr = 0; + _object_t* len = 0; + _object_t subscript; + _object_t* subscript_ptr = 0; + mb_value_u val; + int dcount = 0; + int f = 1; + unsigned idx = 0; + + mb_assert(s && l && index); + + subscript_ptr = &subscript; + _MAKE_NIL(subscript_ptr); + + if(literally) *literally = false; + + /* Array name */ + ast = *l; + if(!c && ast && _is_array(ast->data)) + c = (_object_t*)ast->data; + if(!_is_array(c)) { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(((_object_t*)c)->type == _DT_ARRAY) + arr = (_object_t*)c; + else + arr = ((_object_t*)c)->data.variable->data; + /* = */ + if(literally && ast->next && _IS_FUNC((_object_t*)ast->next->data, _core_equal)) { + *literally = true; + + goto _exit; + } + /* ( */ + if(!ast->next || ((_object_t*)ast->next->data)->type != _DT_FUNC || ((_object_t*)ast->next->data)->data.func->pointer != _core_open_bracket) { + _handle_error_on_obj( + s, SE_RN_OPEN_BRACKET_EXPECTED, s->source_file, + (ast && ast->next) ? ((_object_t*)ast->next->data) : 0, + MB_FUNC_ERR, _exit, result + ); + } + ast = ast->next; + /* Array subscript */ + if(!ast->next) { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + while(((_object_t*)ast->data)->type != _DT_FUNC || ((_object_t*)ast->data)->data.func->pointer != _core_close_bracket) { + /* Calculate an integer value */ + result = _calc_expression(s, &ast, &subscript_ptr); + if(result != MB_FUNC_OK) + goto _exit; + len = subscript_ptr; + if(!_try_get_value(len, &val, _DT_INT)) { + _handle_error_on_obj(s, SE_RN_UNEXPECTED_TYPE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(val.integer < 0) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(dcount + 1 > arr->data.array->dimension_count) { + _handle_error_on_obj(s, SE_RN_TOO_MANY_DIMENSIONS, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if((unsigned)val.integer >= arr->data.array->dimensions[dcount]) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + idx += (unsigned)val.integer * f; + /* Comma? */ + if(_IS_SEP(ast->data, ',')) + ast = ast->next; + + f *= arr->data.array->dimensions[dcount]; + ++dcount; + } + *index = idx; + if(!arr->data.array->raw) { + _handle_error_on_obj(s, SE_RN_RANK_OUT_OF_BOUND, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + +_exit: + *l = ast; + + return result; +} + +/* Get the value of an element in an array */ +static bool_t _get_array_elem(mb_interpreter_t* s, _array_t* arr, unsigned index, mb_value_u* val, _data_e* type) { + bool_t result = true; + int_t elemsize = 0; + unsigned pos = 0; + void* rawptr = 0; + + mb_assert(s && arr && val && type); + mb_assert(index < arr->count); + + elemsize = _get_size_of(arr->type); + pos = (unsigned)(elemsize * index); + rawptr = (void*)((intptr_t)arr->raw + pos); + if(arr->type == _DT_REAL) { +#ifdef MB_SIMPLE_ARRAY + val->float_point = *((real_t*)rawptr); + *type = _DT_REAL; +#else /* MB_SIMPLE_ARRAY */ + _copy_bytes(val->bytes, *((mb_val_bytes_t*)rawptr)); + *type = arr->types[index]; +#endif /* MB_SIMPLE_ARRAY */ + } else if(arr->type == _DT_STRING) { + val->string = *((char**)rawptr); + *type = _DT_STRING; + } else { + mb_assert(0 && "Unsupported."); + } + + return result; +} + +/* Set the value of an element in an array */ +static int _set_array_elem(mb_interpreter_t* s, _ls_node_t* ast, _array_t* arr, unsigned index, mb_value_u* val, _data_e* type) { + int result = MB_FUNC_OK; + int_t elemsize = 0; + unsigned pos = 0; + void* rawptr = 0; + mb_unrefvar(ast); + + mb_assert(s && arr && val); + mb_assert(index < arr->count); + + elemsize = _get_size_of(arr->type); + pos = (unsigned)(elemsize * index); + rawptr = (void*)((intptr_t)arr->raw + pos); +#ifdef MB_SIMPLE_ARRAY + switch(*type) { + case _DT_INT: + *((real_t*)rawptr) = (real_t)val->integer; + + break; + case _DT_REAL: + *((real_t*)rawptr) = val->float_point; + + break; + case _DT_STRING: { + size_t _sl = 0; + if(arr->type != _DT_STRING) { + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + _sl = mb_strlen(val->string); + if(*((char**)rawptr)) + mb_free(*((char**)rawptr)); + *((char**)rawptr) = (char*)mb_malloc(_sl + 1); + memcpy(*((char**)rawptr), val->string, _sl + 1); + } + + break; + default: + result = MB_FUNC_ERR; + + break; + } +#else /* MB_SIMPLE_ARRAY */ + if(arr->types[index] == _DT_STRING && *((char**)rawptr)) + mb_free(*((char**)rawptr)); + switch(*type) { + case _DT_STRING: { + size_t _sl = 0; + _sl = mb_strlen(val->string); + *((char**)rawptr) = (char*)mb_malloc(_sl + 1); + memcpy(*((char**)rawptr), val->string, _sl + 1); + arr->types[index] = _DT_STRING; + } + + break; + default: + _copy_bytes(*((mb_val_bytes_t*)rawptr), val->bytes); + arr->types[index] = *type; + + break; + } +#endif /* MB_SIMPLE_ARRAY */ + + goto _exit; /* Avoid an unreferenced label warning */ + +_exit: + return result; +} + +/* Clear an array */ +static void _clear_array(_array_t* arr) { + char* str = 0; + int_t elemsize = 0; + unsigned pos = 0; + void* rawptr = 0; + unsigned ul = 0; + + mb_assert(arr); + + if(arr->raw) { +#ifndef MB_SIMPLE_ARRAY + if(arr->type == _DT_REAL) { + for(ul = 0; ul < arr->count; ++ul) { + elemsize = _get_size_of(arr->type); + pos = (unsigned)(elemsize * ul); + rawptr = (void*)((intptr_t)arr->raw + pos); + if(arr->types[ul] == _DT_STRING) { + str = *((char**)rawptr); + if(str) { + safe_free(str); + } + } else { + _object_t obj; + obj.type = arr->types[ul]; + _copy_bytes(obj.data.bytes, rawptr); + _dispose_object(&obj); + } + } + } +#endif /* MB_SIMPLE_ARRAY */ + if(arr->type == _DT_STRING) { + for(ul = 0; ul < arr->count; ++ul) { + elemsize = _get_size_of(arr->type); + pos = (unsigned)(elemsize * ul); + rawptr = (void*)((intptr_t)arr->raw + pos); + str = *((char**)rawptr); + if(str) { + safe_free(str); + } + } + } + safe_free(arr->raw); + } +} + +/* Determine if an object is an array value or an array variable */ +static bool_t _is_array(void* obj) { + bool_t result = false; + _object_t* o = 0; + + o = (_object_t*)obj; + if(o && o->type == _DT_ARRAY) + result = true; + else if(o && o->type == _DT_VAR) + result = o->data.variable->data->type == _DT_ARRAY; + + return result; +} + +#ifdef MB_ENABLE_ARRAY_REF +/* Unreference an array */ +static void _unref_array(_ref_t* ref, void* data) { + mb_assert(ref); + + if(*ref->count == _NONE_REF) + _destroy_array((_array_t*)data); +} +#endif /* MB_ENABLE_ARRAY_REF */ + +#ifdef MB_ENABLE_COLLECTION_LIB +/* Create a list */ +static _list_t* _create_list(mb_interpreter_t* s) { + _list_t* result = 0; + + mb_assert(s); + + result = (_list_t*)mb_malloc(sizeof(_list_t)); + memset(result, 0, sizeof(_list_t)); + result->list = _ls_create(); + _create_ref(&result->ref, _unref_list, _DT_LIST, s); + + return result; +} + +/* Destroy a list */ +static void _destroy_list(_list_t* c) { + mb_assert(c); + + if(c->range_begin) { safe_free(c->range_begin); } + _ls_foreach(c->list, _destroy_object); + _ls_destroy(c->list); + _destroy_ref(&c->ref); + safe_free(c); +} + +/* Create a dictionary */ +static _dict_t* _create_dict(mb_interpreter_t* s) { + _dict_t* result = 0; + + mb_assert(s); + + result = (_dict_t*)mb_malloc(sizeof(_dict_t)); + memset(result, 0, sizeof(_dict_t)); + result->dict = _ht_create(0, _ht_cmp_object, _ht_hash_object, _destroy_object_with_extra); + _create_ref(&result->ref, _unref_dict, _DT_DICT, s); + + return result; +} + +/* Destroy a dictionary */ +static void _destroy_dict(_dict_t* c) { + mb_assert(c); + + _ht_foreach(c->dict, _destroy_object_with_extra); + _ht_destroy(c->dict); + _destroy_ref(&c->ref); + safe_free(c); +} + +/* Create an iterator of a list */ +static _list_it_t* _create_list_it(_list_t* coll, bool_t lock) { + _list_it_t* result = 0; + + mb_assert(coll); + + result = (_list_it_t*)mb_malloc(sizeof(_list_it_t)); + memset(result, 0, sizeof(_list_it_t)); + result->list = coll; + result->locking = lock; + if(coll->range_begin) + result->curr.ranging = *coll->range_begin - sgn(coll->count); + else + result->curr.node = coll->list; + if(lock) + _lock_ref_object(&coll->lock, &coll->ref, coll); + _weak_ref(&coll->ref, coll, &result->weak_ref); + + return result; +} + +/* Destroy an iterator of a list */ +static bool_t _destroy_list_it(_list_it_t* it) { + bool_t result = true; + + mb_assert(it); + + if(_weak_unref(&it->weak_ref)) + _unlock_ref_object(&it->list->lock, &it->list->ref, it->list); + safe_free(it); + + return result; +} + +/* Move an iterator of a list to next step */ +static _list_it_t* _move_list_it_next(_list_it_t* it) { + _list_it_t* result = 0; + + if(!it || !it->list || !it->list->list) + goto _exit; + + if(it->list->lock < 0) { /* The iterator goes invalid if collection has been changed after obtaining iterator */ + result = it; + + goto _exit; + } + + if(!it->curr.node && !it->list->range_begin) + goto _exit; + + if(it->list->range_begin) { + if(it->list->lock) + it->curr.ranging += sgn(it->list->count); + + if(it->list->count > 0 && it->curr.ranging < *it->list->range_begin + it->list->count) + result = it; + else if(it->list->count < 0 && it->curr.ranging > *it->list->range_begin + it->list->count) + result = it; + } else { + if(it->list->lock) + it->curr.node = it->curr.node->next; + + if(it->curr.node) + result = it; + } + +_exit: + return result; +} + +/* Create an iterator of a dictionary */ +static _dict_it_t* _create_dict_it(_dict_t* coll, bool_t lock) { + _dict_it_t* result = 0; + + mb_assert(coll); + + result = (_dict_it_t*)mb_malloc(sizeof(_dict_it_t)); + memset(result, 0, sizeof(_dict_it_t)); + result->dict = coll; + result->locking = lock; + result->curr_bucket = 0; + result->curr_node = _INVALID_DICT_IT; + if(lock) + _lock_ref_object(&coll->lock, &coll->ref, coll); + _weak_ref(&coll->ref, coll, &result->weak_ref); + + return result; +} + +/* Destroy an iterator of a dictionary */ +static bool_t _destroy_dict_it(_dict_it_t* it) { + bool_t result = true; + + mb_assert(it); + + if(_weak_unref(&it->weak_ref)) + _unlock_ref_object(&it->dict->lock, &it->dict->ref, it->dict); + safe_free(it); + + return result; +} + +/* Move an iterator of a dictionary to next step */ +static _dict_it_t* _move_dict_it_next(_dict_it_t* it) { + _dict_it_t* result = 0; + + if(!it || !it->dict || !it->dict->dict || !it->curr_node) + goto _exit; + + if(!it->dict->lock) + goto _exit; + + if(it->curr_node && it->curr_node != _INVALID_DICT_IT) { + it->curr_node = it->curr_node->next; + if(!it->curr_node) + ++it->curr_bucket; + } + if(!it->curr_node || it->curr_node == _INVALID_DICT_IT) { + if(!it->dict->dict->array) + goto _exit; + for( ; it->curr_bucket < it->dict->dict->array_size; ++it->curr_bucket) { + it->curr_node = it->dict->dict->array[it->curr_bucket]; + if(it->curr_node && it->curr_node->next) { + it->curr_node = it->curr_node->next; + + break; + } + } + } + + if(it->curr_node && it->curr_node->extra) + result = it; + +_exit: + return result; +} + +/* Unreference a list */ +static void _unref_list(_ref_t* ref, void* data) { + mb_assert(ref); + + if(*ref->count == _NONE_REF) + _destroy_list((_list_t*)data); +} + +/* Unreference a dictionary */ +static void _unref_dict(_ref_t* ref, void* data) { + mb_assert(ref); + + if(*ref->count == _NONE_REF) + _destroy_dict((_dict_t*)data); +} + +/* Push a value to a list */ +static bool_t _push_list(_list_t* coll, mb_value_t* val, _object_t* oarg) { + mb_assert(coll && (val || oarg)); + + if(_try_purge_it(coll->ref.s, val, oarg)) + return false; + + _fill_ranged(coll); + + if(val && !oarg) + _create_internal_object_from_public_value(val, &oarg); + _COLL_ROUTINE(oarg); + _ls_pushback(coll->list, oarg); + coll->count++; + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); + + return true; +} + +/* Pop a value from a list */ +static bool_t _pop_list(_list_t* coll, mb_value_t* val, mb_interpreter_t* s) { + _object_t* oval = 0; + + mb_assert(coll && val && s); + + _fill_ranged(coll); + + oval = (_object_t*)_ls_popback(coll->list); + if(oval) { + _internal_object_to_public_value(oval, val); + _destroy_object_capsule_only(oval, 0); + coll->count--; + + if(val->type == MB_DT_STRING) + _mark_lazy_destroy_string(s, val->value.string); + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); + + return true; + } else { + mb_make_nil(*val); + + return false; + } +} + +/* Insert a value into a list */ +static bool_t _insert_list(_list_t* coll, int_t idx, mb_value_t* val, _object_t** oval) { + _object_t* oarg = 0; + + mb_assert(coll && val); + + if(_try_purge_it(coll->ref.s, val, oval ? *oval : 0)) + return false; + + _fill_ranged(coll); + + _create_internal_object_from_public_value(val, &oarg); + _COLL_ROUTINE(oarg); + if(oval) + *oval = oarg; + + if(_ls_insert_at(coll->list, (int)idx, oarg)) { + coll->count++; + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); + + return true; + } + + return false; +} + +/* Set an element in a list with a specific index with a given value */ +static bool_t _set_list(_list_t* coll, int_t idx, mb_value_t* val, _object_t** oval) { + _ls_node_t* result = 0; + _object_t* oarg = 0; + + mb_assert(coll && (val || (oval && *oval))); + + if(_try_purge_it(coll->ref.s, val, oval ? *oval : 0)) + return false; + + _fill_ranged(coll); + + result = _node_at_list(coll, (int)idx); + if(result) { + if(result->data) + _destroy_object(result->data, 0); + if(val) { + _create_internal_object_from_public_value(val, &oarg); + _COLL_ROUTINE(oarg); + if(oval) + *oval = oarg; + } else { + oarg = *oval; + } + result->data = oarg; + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); + } + + return !!(result && result->data); +} + +/* Remove an element in a list with a specific index */ +static bool_t _remove_at_list(_list_t* coll, int_t idx) { + bool_t result = false; + _ls_node_t* node = 0; + + mb_assert(coll); + + _fill_ranged(coll); + + node = _node_at_list(coll, (int)idx); + if(node) { + if(node->data) { + _ls_remove(coll->list, node, _destroy_object); + coll->count--; + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); + + result = true; + } + } + + return result; +} + +/* Get a node in a list with a specific index */ +static _ls_node_t* _node_at_list(_list_t* coll, int index) { + _ls_node_t* result = 0; + _ls_node_t* tmp = 0; + int n = 0; + + mb_assert(coll); + + _fill_ranged(coll); + + if(index >= 0 && index < (int)coll->count) { + /* Position: HEAD ... LEFT ... PIVOT ... RIGHT ... TAIL + PIVOT is a cached node and, + LEN(HEAD to LEFT) == LEN(LEFT to PIVOT) && LEN(PIVOT to RIGHT) == LEN(RIGHT to TAIL) + */ + int head = 0, + left = coll->cached_index / 2, + right = coll->cached_index + (coll->count - coll->cached_index) / 2, + tail = coll->count - 1; + if(coll->cached_node) { + if(index >= head && index < left) { /* [HEAD, LEFT) */ + n = index; + tmp = coll->list->next; + while(tmp && n) { + tmp = tmp->next; + --n; + } + if(tmp) { + result = tmp; + coll->cached_node = tmp; + coll->cached_index = index; + } + } else if(index >= left && index <= right) { /* [LEFT, RIGHT] */ + while(index != coll->cached_index) { + if(index > coll->cached_index) { + coll->cached_node = coll->cached_node->next; + coll->cached_index++; + } else if(index < coll->cached_index) { + coll->cached_node = coll->cached_node->prev; + coll->cached_index--; + } + } + result = coll->cached_node; + } else if(index > right && index <= tail) { /* (RIGHT, TAIL] */ + n = tail - index; + tmp = coll->list->prev; + while(tmp && n) { + tmp = tmp->prev; + --n; + } + if(tmp) { + result = tmp; + coll->cached_node = tmp; + coll->cached_index = index; + } + } else { + mb_assert(0 && "Impossible."); + } + } else { + n = index; + tmp = coll->list->next; + while(tmp && n) { + tmp = tmp->next; + --n; + } + if(tmp) { + result = tmp; + coll->cached_node = tmp; + coll->cached_index = index; + } + } + } + + return result; +} + +/* Get the value in a list with a specific index */ +static bool_t _at_list(_list_t* coll, int_t idx, mb_value_t* oval) { + _ls_node_t* result = 0; + + mb_assert(coll && oval); + + _fill_ranged(coll); + + result = _node_at_list(coll, (int)idx); + if(oval && result && result->data) + _internal_object_to_public_value((_object_t*)result->data, oval); + + return !!(result && result->data); +} + +/* Find a value in a list */ +static bool_t _find_list(_list_t* coll, mb_value_t* val, int* idx) { + bool_t result = false; + _object_t* oarg = 0; + + mb_assert(coll && val); + + _fill_ranged(coll); + + _create_internal_object_from_public_value(val, &oarg); + result = !!_ls_find(coll->list, oarg, (_ls_compare_t)_ht_cmp_object, idx); + _destroy_object(oarg, 0); + + return result; +} + +/* Clear a list */ +static void _clear_list(_list_t* coll) { + mb_assert(coll); + + if(coll->range_begin) { safe_free(coll->range_begin); } + + _ls_foreach(coll->list, _destroy_object); + _ls_clear(coll->list); + coll->count = 0; + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); +} + +/* Sort a list */ +static void _sort_list(_list_t* coll) { + mb_assert(coll); + + _ls_sort(&coll->list, (_ls_compare_t)_ht_cmp_object); + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); +} + +/* Invalidate cached list index */ +static void _invalidate_list_cache(_list_t* coll) { + mb_assert(coll); + + coll->cached_node = 0; + coll->cached_index = 0; +} + +/* Fill a ranged list with numbers */ +static void _fill_ranged(_list_t* coll) { + _object_t* obj = 0; + + mb_assert(coll); + + if(coll->range_begin) { + mb_value_t arg; + int_t begin = *coll->range_begin; + int_t end = *coll->range_begin + coll->count; + int_t step = sgn(coll->count); + + do { + mb_make_int(arg, begin); + _create_internal_object_from_public_value(&arg, &obj); + _ls_pushback(coll->list, obj); + + begin += step; + } while(begin != end); + safe_free(coll->range_begin); + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + _invalidate_list_cache(coll); + } +} + +/* Set an element to a dictionary with a key-value pair */ +static bool_t _set_dict(_dict_t* coll, mb_value_t* key, mb_value_t* val, _object_t* okey, _object_t* oval) { + _ls_node_t* exists = 0; + + mb_assert(coll && (key || okey) && (val || oval)); + + if(_try_purge_it(coll->ref.s, key, okey)) + return false; + if(_try_purge_it(coll->ref.s, val, oval)) + return false; + + if(key && !okey) + _create_internal_object_from_public_value(key, &okey); + if(val && !oval) + _create_internal_object_from_public_value(val, &oval); + exists = _ht_find(coll->dict, okey); + if(exists) + _ht_remove(coll->dict, okey, _ls_cmp_extra_object); + _COLL_ROUTINE(okey); + _COLL_ROUTINE(oval); + _ht_set_or_insert(coll->dict, okey, oval); + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + + return true; +} + +/* Remove an element to a dictionary with a specific key */ +static bool_t _remove_dict(_dict_t* coll, mb_value_t* key) { + _ls_node_t* result = 0; + _object_t* okey = 0; + + mb_assert(coll && key); + + _create_internal_object_from_public_value(key, &okey); + + if(_try_purge_it(coll->ref.s, key, okey)) { + _destroy_object_capsule_only(okey, 0); + + return false; + } + + result = _ht_find(coll->dict, okey); + if(result && result->data) { + _ht_remove(coll->dict, okey, _ls_cmp_extra_object); + _destroy_object(okey, 0); + + _write_on_ref_object(&coll->lock, &coll->ref, coll); + + return true; + } + + return false; +} + +/* Find a key in a dictionary */ +static bool_t _find_dict(_dict_t* coll, mb_value_t* val, mb_value_t* oval) { + _ls_node_t* result = 0; + _object_t* oarg = 0; + + mb_assert(coll && val); + + if(val->type == MB_DT_LIST_IT || val->type == MB_DT_DICT_IT) + return false; + _create_internal_object_from_public_value(val, &oarg); + result = _ht_find(coll->dict, oarg); + _destroy_object(oarg, 0); + if(oval) { + if(result && result->data) { + _internal_object_to_public_value((_object_t*)result->data, oval); + } else { + oval->type = MB_DT_UNKNOWN; + oval->value.integer = 0; + + return true; + } + } + + return !!(result && result->data); +} + +/* Clear a dictionary */ +static void _clear_dict(_dict_t* coll) { + mb_assert(coll); + + _ht_foreach(coll->dict, _destroy_object_with_extra); + _ht_clear(coll->dict); + + _write_on_ref_object(&coll->lock, &coll->ref, coll); +} + +/* Determin whether a list iterator is invalid */ +static bool_t _invalid_list_it(_list_it_t* it) { + if(!it) + return false; + + return it && it->list && it->list->lock <= 0; +} + +/* Determin whether a dictionary iterator is invalid */ +static bool_t _invalid_dict_it(_dict_it_t* it) { + if(!it) + return false; + + return it && it->dict && it->dict->lock <= 0; +} + +/* Assign an iterator to another object */ +static bool_t _assign_with_it(_object_t* tgt, _object_t* src) { + mb_assert(tgt && src); + + if(src->type != _DT_LIST_IT && src->type != _DT_DICT_IT) + return false; + + switch(src->type) { + case _DT_LIST_IT: + if(src->data.list_it->locking) { + tgt->data.list_it = _create_list_it(src->data.list_it->list, true); + } else { + tgt->data = src->data; + tgt->data.list_it->locking = true; + _lock_ref_object(&tgt->data.list_it->list->lock, &tgt->data.list_it->list->ref, tgt->data.list_it->list); + } + + break; + case _DT_DICT_IT: + if(src->data.dict_it->locking) { + tgt->data.dict_it = _create_dict_it(src->data.dict_it->dict, true); + } else { + tgt->data = src->data; + tgt->data.dict_it->locking = true; + _lock_ref_object(&tgt->data.dict_it->dict->lock, &tgt->data.dict_it->dict->ref, tgt->data.dict_it->dict); + } + + break; + default: + mb_assert(0 && "Impossible."); + + break; + } + + return true; +} + +/* Try to purege an iterator */ +static bool_t _try_purge_it(mb_interpreter_t* s, mb_value_t* val, _object_t* obj) { + bool_t result = false; + _object_t tmp; + + mb_assert(s && (val || obj)); + + _MAKE_NIL(&tmp); +#ifdef MB_ENABLE_COLLECTION_LIB + if(val) { + switch(val->type) { + case MB_DT_LIST_IT: /* Fall through */ + case MB_DT_DICT_IT: + _public_value_to_internal_object(val, &tmp); + obj = &tmp; + + break; + default: /* Do nothing */ + break; + } + } + if(obj) { + if(obj->type == _DT_LIST_IT) { + result = true; + if(obj->data.list_it->locking) + return result; + + _destroy_list_it(obj->data.list_it); /* Process dangling value */ + } else if(obj->type == _DT_DICT_IT) { + result = true; + if(obj->data.dict_it->locking) + return result; + + _destroy_dict_it(obj->data.dict_it); /* Process dangling value */ + } else { + return result; + } + } +#endif /* MB_ENABLE_COLLECTION_LIB */ + + return result; +} + +/* Clone an object to a list */ +static int _clone_to_list(void* data, void* extra, _list_t* coll) { + _object_t* obj = 0; + _object_t* tgt = 0; + mb_unrefvar(extra); + + mb_assert(data && coll); + + _fill_ranged(coll); + + tgt = _create_object(); + obj = (_object_t*)data; + _clone_object(coll->ref.s, obj, tgt, false, false); + _push_list(coll, 0, tgt); + _REF(tgt) + + return 1; +} + +/* Clone a key-value pair to a dictionary */ +static int _clone_to_dict(void* data, void* extra, _dict_t* coll) { + _object_t* kobj = 0; + _object_t* ktgt = 0; + _object_t* vobj = 0; + _object_t* vtgt = 0; + + mb_assert(data && extra && coll); + + ktgt = _create_object(); + kobj = (_object_t*)extra; + _clone_object(coll->ref.s, kobj, ktgt, false, false); + + vtgt = _create_object(); + vobj = (_object_t*)data; + _clone_object(coll->ref.s, vobj, vtgt, false, false); + + _set_dict(coll, 0, 0, ktgt, vtgt); + _REF(ktgt) + _REF(vtgt) + + return 1; +} + +/* Copy an object from a list to an array */ +static int _copy_list_to_array(void* data, void* extra, _array_helper_t* h) { + _object_t* obj = 0; + mb_value_t val; + _data_e type = _DT_NIL; + mb_unrefvar(extra); + + mb_assert(data && h); + + obj = (_object_t*)data; + mb_make_nil(val); + _internal_object_to_public_value(obj, &val); + type = obj->type; + _set_array_elem(h->s, 0, h->array, h->index++, &val.value, &type); + + return 1; +} + +/* Copy all keys of a dictionary to a value array */ +static int _copy_keys_to_value_array(void* data, void* extra, _keys_helper_t* h) { + _object_t* obj = 0; + mb_value_t val; + mb_unrefvar(data); + + mb_assert(extra && h); + + if(h->index >= h->size) + return 1; + + obj = (_object_t*)extra; + mb_make_nil(val); + _internal_object_to_public_value(obj, &val); + h->keys[h->index++] = val; + + return 1; +} +#endif /* MB_ENABLE_COLLECTION_LIB */ + +#ifdef MB_ENABLE_CLASS +/* Initialize a class */ +static void _init_class(mb_interpreter_t* s, _class_t* instance, char* n) { + _running_context_t* running = 0; + _object_t* meobj = 0; + _var_t* me = 0; + + mb_assert(s && instance && n); + + running = s->running_context; + + memset(instance, 0, sizeof(_class_t)); + _create_ref(&instance->ref, _unref_class, _DT_CLASS, s); + _ref(&instance->ref, instance); + instance->name = n; + instance->meta_list = _ls_create(); + instance->scope = _create_running_context(true); + instance->created_from = instance; + + me = _create_var(&meobj, _CLASS_ME, strlen(_CLASS_ME) + 1, true); + me->data->type = _DT_CLASS; + me->data->data.instance = instance; + me->pathing = _PATHING_NORMAL; + me->is_me = true; + _ht_set_or_insert(instance->scope->var_dict, me->name, meobj); +} + +/* Begin parsing a class */ +static void _begin_class(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + context->class_state = _CLASS_STATE_PROC; +} + +/* End parsing a class */ +static bool_t _end_class(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + if(context->routine_state) { + _handle_error_now(s, SE_RN_INVALID_ROUTINE, s->source_file, MB_FUNC_ERR); + } + if(context->class_state == _CLASS_STATE_NONE) { + _handle_error_now(s, SE_RN_INVALID_CLASS, s->source_file, MB_FUNC_ERR); + + return false; + } + context->class_state = _CLASS_STATE_NONE; + s->last_instance = 0; + + return true; +} + +/* Unreference a class instance */ +static void _unref_class(_ref_t* ref, void* data) { + mb_assert(ref); + + if(ref->s->valid) + _out_of_scope(ref->s, ((_class_t*)data)->scope, (_class_t*)data, 0, false); + + if(*ref->count == _NONE_REF) + _destroy_class((_class_t*)data); +} + +/* Destroy a class instance */ +static void _destroy_class(_class_t* c) { + mb_assert(c); + + if(c->meta_list) { + _unlink_meta_class(c->ref.s, c); + _ls_destroy(c->meta_list); + } + if(c->scope->var_dict) { + _ht_foreach(c->scope->var_dict, _destroy_object); + _ht_destroy(c->scope->var_dict); + } +#ifdef MB_ENABLE_LAMBDA + if(c->scope->refered_lambdas) { + _ls_destroy(c->scope->refered_lambdas); + c->scope->refered_lambdas = 0; + } +#endif /* MB_ENABLE_LAMBDA */ + safe_free(c->scope); + _destroy_ref(&c->ref); + safe_free(c->name); + safe_free(c); +} + +/* Traverse all fields of a class instance, and its meta class instances recursively as well */ +static bool_t _traverse_class(_class_t* c, _class_scope_walker_t scope_walker, _class_meta_walker_t meta_walker, unsigned meta_depth, bool_t meta_walk_on_self, void* extra_data, void* ret) { + bool_t result = true; + _ls_node_t* node = 0; + _class_t* meta = 0; + + mb_assert(c); + + if(scope_walker) { + _HT_FOREACH(c->scope->var_dict, _do_nothing_on_object, scope_walker, extra_data); + } + if(meta_walk_on_self) { + if(meta_walker) { + result = meta_walker(c, extra_data, ret); + if(!result) + goto _exit; + } + } + node = c->meta_list ? c->meta_list->next : 0; + while(node) { + meta = (_class_t*)node->data; + if(meta_walker && meta_depth) { + result = meta_walker(meta, extra_data, ret); + if(!result) + break; + } + result = _traverse_class( + meta, + scope_walker, + meta_walker, meta_depth ? meta_depth - 1 : 0, + meta_walk_on_self, + extra_data, ret + ); + if(!result) + break; + node = node->next; + } + +_exit: + return result; +} + +/* Link a class instance to the meta list of another class instance */ +static bool_t _link_meta_class(mb_interpreter_t* s, _class_t* derived, _class_t* base) { + mb_assert(s && derived && base); + + if(_ls_find(derived->meta_list, base, (_ls_compare_t)_ht_cmp_intptr, 0)) { + _handle_error_now(s, SE_RN_INVALID_CLASS, s->source_file, MB_FUNC_ERR); + + return false; + } + + _ls_pushback(derived->meta_list, base); + _ref(&base->ref, base); + + return true; +} + +/* Unlink all meta class instances of a class instance */ +static void _unlink_meta_class(mb_interpreter_t* s, _class_t* derived) { + mb_assert(s && derived); + + _LS_FOREACH(derived->meta_list, _do_nothing_on_object, _unlink_meta_instance, derived); + _ls_clear(derived->meta_list); +} + +/* Unlink a meta class instance */ +static int _unlink_meta_instance(void* data, void* extra, _class_t* derived) { + _class_t* base = 0; + mb_unrefvar(extra); + + mb_assert(data && derived); + + base = (_class_t*)data; + _unref(&base->ref, base); + + return 0; +} + +/* Clone fields of a class instance to another */ +static int _clone_clsss_field(void* data, void* extra, void* n) { + int result = _OP_RESULT_NORMAL; + _object_t* obj = 0; + _array_t* arr = 0; + _var_t* var = 0; + _routine_t* sub = 0; + _class_t* instance = (_class_t*)n; + _object_t* ret = 0; + mb_unrefvar(extra); + + mb_assert(data && n); + + obj = (_object_t*)data; + if(_is_internal_object(obj)) + goto _exit; + switch(obj->type) { + case _DT_VAR: + var = obj->data.variable; + if(!_IS_ME(var)) { + if(_ht_find(instance->scope->var_dict, var->name)) + break; + + ret = _duplicate_parameter(var, 0, instance->scope); + _clone_object(instance->ref.s, obj, ret->data.variable->data, false, var->data->type != _DT_CLASS); +#ifdef MB_ENABLE_SOURCE_TRACE + ret->source_pos = -1; + ret->source_row = ret->source_col = 0xFFFF; +#else /* MB_ENABLE_SOURCE_TRACE */ + ret->source_pos = -1; +#endif /* MB_ENABLE_SOURCE_TRACE */ + } + + break; + case _DT_ARRAY: + arr = obj->data.array; + if(!_ht_find(instance->scope->var_dict, arr->name)) { + ret = _create_object(); + ret->type = _DT_ARRAY; + ret->is_ref = false; + _clone_object(instance->ref.s, obj, ret, false, false); + + _ht_set_or_insert(instance->scope->var_dict, ret->data.array->name, ret); + } + + break; + case _DT_ROUTINE: + sub = obj->data.routine; + if(!_ht_find(instance->scope->var_dict, sub->name)) { + _routine_t* routine = _clone_routine(sub, instance, false); + ret = _create_object(); + ret->type = _DT_ROUTINE; + ret->data.routine = routine; + ret->is_ref = false; + + _ht_set_or_insert(instance->scope->var_dict, obj->data.routine->name, ret); + } + + break; + default: /* Do nothing */ + break; + } + +_exit: + return result; +} + +/* Link meta class to a new instance */ +static bool_t _clone_class_meta_link(_class_t* meta, void* n, void* ret) { + _class_t* instance = (_class_t*)n; + mb_unrefvar(ret); + + mb_assert(meta && n); + + _link_meta_class(instance->ref.s, instance, meta); + + return true; +} + +/* Search for a meta function with a specific name and assign to a member field */ +static int _search_class_meta_function(mb_interpreter_t* s, _class_t* instance, const char* n, _routine_t* _UNALIGNED_ARG * f) { + _ls_node_t* node = 0; + + mb_assert(s); + + node = _search_identifier_in_class(s, instance, n, 0, 0); + if(f) *f = 0; + if(node) { + _object_t* obj = (_object_t*)node->data; + if(obj && _IS_ROUTINE(obj)) { + if(f) *f = obj->data.routine; + + return 1; + } + } + + return 0; +} + +/* Search for the HASH and COMPARE meta function for a class */ +static int _search_class_hash_and_compare_functions(mb_interpreter_t* s, _class_t* instance) { + mb_assert(s && instance); + + _search_class_meta_function(s, instance, _CLASS_HASH_FUNC, &instance->hash); + _search_class_meta_function(s, instance, _CLASS_COMPARE_FUNC, &instance->compare); + + if(!instance->hash && !instance->compare) { + return MB_FUNC_OK; + } else if(instance->hash && instance->compare) { + return MB_FUNC_OK; + } else { + instance->hash = 0; + instance->compare = 0; + + return MB_FUNC_WARNING; + } +} + +/* Detect whether a class instance is inherited from another */ +static bool_t _is_a_class(_class_t* instance, void* m, void* ret) { + _class_t* meta = (_class_t*)m; + bool_t* r = (bool_t*)ret; + bool_t is_a = false; + + mb_assert(instance && meta && ret); + + do { + if(instance == meta) { + is_a = true; + + break; + } + if(instance == instance->created_from) + break; + instance = instance->created_from; + } while(1); + + *r = is_a; + + return !(*r); +} + +/* Add a meta class instance to a GC reachable table */ +static bool_t _add_class_meta_reachable(_class_t* meta, void* ht, void* ret) { + _ht_node_t* htable = (_ht_node_t*)ht; + mb_unrefvar(ret); + + mb_assert(meta && ht); + + if(!_ht_find(htable, &meta->ref)) + _ht_set_or_insert(htable, &meta->ref, meta); + + return true; +} + +#ifdef MB_ENABLE_COLLECTION_LIB +/* Reflect each field of a class instance to a dictionary */ +static int _reflect_class_field(void* data, void* extra, void* d) { + int result = _OP_RESULT_NORMAL; + _object_t* obj = 0; + _var_t* var = 0; + _routine_t* sub = 0; + _dict_t* coll = (_dict_t*)d; + _object_t tmp; + mb_unrefvar(extra); + + mb_assert(data && d); + + _MAKE_NIL(&tmp); + tmp.type = _DT_STRING; + obj = (_object_t*)data; + if(_is_internal_object(obj)) + goto _exit; + switch(obj->type) { + case _DT_VAR: + var = (_var_t*)obj->data.variable; + tmp.data.string = var->name; + if(!_ht_find(coll->dict, &tmp)) { + mb_value_t key, val; + mb_make_string(key, var->name); + _internal_object_to_public_value(obj, &val); + _set_dict(coll, &key, &val, 0, 0); + } + + break; + case _DT_ROUTINE: + sub = (_routine_t*)obj->data.routine; + tmp.data.string = sub->name; + if(!_ht_find(coll->dict, &tmp)) { + mb_value_t key, val; + mb_make_string(key, sub->name); + mb_make_type(val, _internal_type_to_public_type(obj->type)); + _set_dict(coll, &key, &val, 0, 0); + } + + break; + default: /* Do nothing */ + break; + } + +_exit: + return result; +} +#endif /* MB_ENABLE_COLLECTION_LIB */ + +/* Call the TOSTRING function of a class instance to format it */ +static int _format_class_to_string(mb_interpreter_t* s, void** l, _class_t* instance, _object_t* out, bool_t* got_tostr) { + int result = MB_FUNC_OK; + _ls_node_t* tsn = 0; + + mb_assert(s && l && instance && out); + + tsn = _search_identifier_in_class(s, instance, _CLASS_TO_STRING_FUNC, 0, 0); + if(got_tostr) *got_tostr = false; + if(tsn) { + _object_t* tso = (_object_t*)tsn->data; + _ls_node_t* tmp = (_ls_node_t*)*l; + mb_value_t va[1]; + mb_make_nil(va[0]); + if(_eval_routine(s, &tmp, va, 1, tso->data.routine, _has_routine_fun_arg, _pop_routine_fun_arg) == MB_FUNC_OK) { + _MAKE_NIL(out); + _public_value_to_internal_object(&s->running_context->intermediate_value, out); + if(out->type == _DT_STRING) { + out->data.string = mb_strdup(out->data.string, strlen(out->data.string) + 1); + out->is_ref = false; + mb_make_nil(s->running_context->intermediate_value); + } else { + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + if(got_tostr) + *got_tostr = true; + } + } + +_exit: + return result; +} + +/* Reflect a class instance from a string */ +static _class_t* _reflect_string_to_class(mb_interpreter_t* s, const char* n, mb_value_t* arg) { + _ls_node_t* cs = 0; + _object_t* c = 0; + + cs = _search_identifier_in_scope_chain(s, 0, n, _PATHING_NONE, 0, 0); + if(!cs) + return 0; + c = (_object_t*)cs->data; + if(!c) + return 0; + c = _GET_CLASS(c); + if(!c) + return 0; + if(arg) + _internal_object_to_public_value(c, arg); + + return c->data.instance; +} + +/* Detect whether it's accessing a member of a class instance following a sub routine */ +static bool_t _is_valid_class_accessor_following_routine(mb_interpreter_t* s, _var_t* var, _ls_node_t* ast, _ls_node_t** out) { + bool_t result = false; + _running_context_t* running = 0; + + mb_assert(s && var && ast); + + running = s->running_context; + + if(out) *out = 0; + + if(_is_accessor_char(*var->name) && (ast && ast->prev && ast->prev != s->ast && _IS_FUNC(ast->prev->data, _core_close_bracket)) && running->intermediate_value.type == MB_DT_CLASS) { + _class_t* instance = (_class_t*)running->intermediate_value.value.instance; + _ls_node_t* fn = _search_identifier_in_class(s, instance, var->name + 1, 0, 0); + result = true; + if(fn && out) + *out = fn; + } + + return result; +} +#endif /* MB_ENABLE_CLASS */ + +/* Initialize a routine */ +static void _init_routine(mb_interpreter_t* s, _routine_t* routine, char* n, mb_routine_func_t f) { + _running_context_t* running = 0; + + mb_assert(s && routine); + + running = s->running_context; + + memset(routine, 0, sizeof(_routine_t)); + routine->name = n; + + if(n && f) + routine->type = MB_RT_NATIVE; + else if(n && !f) + routine->type = MB_RT_SCRIPT; +#ifdef MB_ENABLE_LAMBDA + else if(!n && !f) + routine->type = MB_RT_LAMBDA; +#endif /* MB_ENABLE_LAMBDA */ + + switch(routine->type) { + case MB_RT_SCRIPT: + routine->func.basic.scope = _create_running_context(true); + + break; +#ifdef MB_ENABLE_LAMBDA + case MB_RT_LAMBDA: + _create_ref(&routine->func.lambda.ref, _unref_routine, _DT_ROUTINE, s); + _ref(&routine->func.lambda.ref, routine); + + break; +#endif /* MB_ENABLE_LAMBDA */ + case MB_RT_NATIVE: + routine->func.native.entry = f; + + break; + default: /* Do nothing */ + break; + } + +#ifdef MB_ENABLE_SOURCE_TRACE + if(s->source_file) + routine->source_file = mb_strdup(s->source_file, 0); +#endif /* MB_ENABLE_SOURCE_TRACE */ +} + +/* Begin parsing a routine */ +static int _begin_routine(mb_interpreter_t* s) { + int result = MB_FUNC_OK; + _parsing_context_t* context = 0; + unsigned short before = 0; + + mb_assert(s); + + context = s->parsing_context; + before = context->routine_state++; + if(before > context->routine_state) { + context->routine_state--; + result = MB_FUNC_ERR; + _handle_error_now(s, SE_RN_INVALID_ROUTINE, s->last_error_file, result); + } + + return result; +} + +/* End parsing a routine */ +static bool_t _end_routine(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + if(!context->routine_state) { + _handle_error_now(s, SE_RN_INVALID_ROUTINE, s->source_file, MB_FUNC_ERR); + + return false; + } + context->routine_state--; + + return true; +} + +/* Begin parsing the definition of a routine */ +static void _begin_routine_definition(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + context->routine_params_state = _ROUTINE_STATE_DEF; +} + +/* Begin parsing the parameter list of a routine */ +static void _begin_routine_parameter_list(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + context->routine_params_state = _ROUTINE_STATE_PARAMS; +} + +/* End parsing the parameter list of a routine */ +static void _end_routine_parameter_list(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + context->routine_params_state = _ROUTINE_STATE_NONE; +} + +/* Duplicate a parameter from a parameter list to variable dictionary */ +static _object_t* _duplicate_parameter(void* data, void* extra, _running_context_t* running) { + _var_t* ref = 0; + _var_t* var = 0; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(running); + + if(data == 0) + return 0; + + ref = (_var_t*)data; + + var = _create_var(&obj, ref->name, 0, true); + + _ht_set_or_insert(running->var_dict, var->name, obj); + + return obj; +} + +/* Clone a routine */ +static _routine_t* _clone_routine(_routine_t* sub, void* c, bool_t toupval) { + _routine_t* result = 0; +#ifdef MB_ENABLE_CLASS + _class_t* instance = (_class_t*)c; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(c); +#endif /* MB_ENABLE_CLASS */ + + mb_assert(sub); + +#ifdef MB_ENABLE_LAMBDA + if(toupval || sub->type == MB_RT_LAMBDA) + result = sub; +#else /* MB_ENABLE_LAMBDA */ + mb_unrefvar(toupval); +#endif /* MB_ENABLE_LAMBDA */ + + if(!result) { + result = (_routine_t*)mb_malloc(sizeof(_routine_t)); + memset(result, 0, sizeof(_routine_t)); + result->name = sub->name; +#ifdef MB_ENABLE_SOURCE_TRACE + result->source_file = sub->source_file; +#endif /* MB_ENABLE_SOURCE_TRACE */ +#ifdef MB_ENABLE_CLASS + result->instance = instance; +#endif /* MB_ENABLE_CLASS */ + result->is_cloned = true; + result->type = sub->type; + result->func = sub->func; + } + + return result; +} + +#ifdef MB_ENABLE_LAMBDA +/* Initialize a lambda */ +static _running_context_t* _init_lambda(mb_interpreter_t* s, _routine_t* routine) { + _running_context_t* result = 0; + _lambda_t* lambda = 0; + + mb_assert(s && routine); + + _init_routine(s, routine, 0, 0); + mb_assert(routine->type == MB_RT_LAMBDA); + lambda = &routine->func.lambda; + lambda->scope = _create_running_context(true); + result = _push_scope_by_routine(s, lambda->scope); + + return result; +} + +/* Unreference a lambda routine */ +static void _unref_routine(_ref_t* ref, void* data) { + mb_assert(ref); + + if(*ref->count == _NONE_REF) + _destroy_routine(ref->s, (_routine_t*)data); +} + +/* Destroy a lambda routine */ +static void _destroy_routine(mb_interpreter_t* s, _routine_t* r) { + _gc_t* gc = 0; + + mb_assert(r); + + if(s) gc = &s->gc; + if(!r->is_cloned) { + if(r->name) { + safe_free(r->name); + } +#ifdef MB_ENABLE_SOURCE_TRACE + if(r->source_file) { + safe_free(r->source_file); + } +#endif /* MB_ENABLE_SOURCE_TRACE */ + switch(r->type) { + case MB_RT_SCRIPT: + if(r->func.basic.scope) { + _destroy_scope(s, r->func.basic.scope); + r->func.basic.scope = 0; + } + if(r->func.basic.parameters) + _ls_destroy(r->func.basic.parameters); + + break; + case MB_RT_LAMBDA: + _destroy_ref(&r->func.lambda.ref); + if(r->func.lambda.scope->var_dict) { + _ht_foreach(r->func.lambda.scope->var_dict, _destroy_object); + _ht_destroy(r->func.lambda.scope->var_dict); + } + safe_free(r->func.lambda.scope); + if(r->func.lambda.parameters) + _ls_destroy(r->func.lambda.parameters); + if(r->func.lambda.outer_scope && !_ht_find(gc->collected_table, &r->func.lambda.outer_scope->ref)) + _unref(&r->func.lambda.outer_scope->ref, r->func.lambda.outer_scope); + if(r->func.lambda.upvalues) + _ht_destroy(r->func.lambda.upvalues); + + break; + case MB_RT_NATIVE: /* Do nothing */ + break; + default: /* Do nothing */ + break; + } + } + safe_free(r); +} + +/* Mark an upvalue of a lambda */ +static void _mark_upvalue(mb_interpreter_t* s, _lambda_t* lambda, _object_t* obj, const char* n) { + _running_context_t* running = 0; + _running_context_t* found_in_scope = 0; + _ls_node_t* scp = 0; + + mb_assert(s && lambda && obj); + + running = s->running_context; + scp = _search_identifier_in_scope_chain(s, running, n, _PATHING_NORMAL, 0, &found_in_scope); + if(scp && found_in_scope) { + _object_t* rot = (_object_t*)scp->data; + rot = _GET_ROUTINE(rot); + if(rot && lambda->scope && lambda->scope->prev != found_in_scope) + return; + if(!found_in_scope->refered_lambdas) + found_in_scope->refered_lambdas = _ls_create(); + if(!_ls_find(found_in_scope->refered_lambdas, lambda, (_ls_compare_t)_ht_cmp_intptr, 0)) + _ls_pushback(found_in_scope->refered_lambdas, lambda); + } + + if(!lambda->upvalues) + lambda->upvalues = _ht_create(0, _ht_cmp_string, _ht_hash_string, 0); + _ht_set_or_insert(lambda->upvalues, obj->data.variable->name, obj); +} + +/* Try to mark upvalues of a lambda */ +static void _try_mark_upvalue(mb_interpreter_t* s, _routine_t* r, _object_t* obj) { + _lambda_t* lambda = 0; + _ls_node_t* node = 0; + + mb_assert(s && r && obj); + mb_assert(r->type == MB_RT_LAMBDA); + + lambda = &r->func.lambda; + + switch(obj->type) { + case _DT_VAR: + node = _ht_find(lambda->scope->var_dict, obj->data.variable->name); + if(!node || !node->data) { + /* Mark upvalues referencing outer scope chain */ + _mark_upvalue(s, lambda, obj, obj->data.variable->name); + } + + break; + default: /* Do nothing */ + break; + } +} + +/* Create an outer scope, which is a referenced type */ +static _running_context_ref_t* _create_outer_scope(mb_interpreter_t* s) { + _running_context_ref_t* result = 0; + + mb_assert(s); + + result = (_running_context_ref_t*)mb_malloc(sizeof(_running_context_ref_t)); + memset(result, 0, sizeof(_running_context_ref_t)); + _create_ref(&result->ref, _unref_outer_scope, _DT_OUTER_SCOPE, s); + result->scope = _create_running_context(true); + + return result; +} + +/* Unreference an outer scope */ +static void _unref_outer_scope(_ref_t* ref, void* data) { + mb_assert(ref); + + if(*ref->count == _NONE_REF) + _destroy_outer_scope((_running_context_ref_t*)data); +} + +/* Destroy an outer scope */ +static void _destroy_outer_scope(_running_context_ref_t* p) { + mb_assert(p); + + if(p) { + _running_context_ref_t* scope = p; + p = p->prev; + _destroy_scope(scope->ref.s, scope->scope); + _destroy_ref(&scope->ref); + safe_free(scope); + } + while(p) { + _running_context_ref_t* scope = p; + p = p->prev; + _unref(&scope->ref, scope); + } +} + +/* Do nothing, this is a helper function for lambda */ +static int _do_nothing_on_ht_for_lambda(void* data, void* extra) { + int result = _OP_RESULT_NORMAL; + mb_unrefvar(data); + mb_unrefvar(extra); + + return result; +} + +/* Fill an outer scope with the original value */ +static int _fill_with_upvalue(void* data, void* extra, _upvalue_scope_tuple_t* tuple) { + _object_t* obj = (_object_t*)data; + const char* n = (const char*)extra; + unsigned ul = 0; + _ls_node_t* ast = 0; + _ls_node_t* nput = 0; + + nput = _ht_find(tuple->outer_scope->scope->var_dict, (void*)n); + if(!nput) { + _ls_node_t* nori = 0; +#ifdef MB_ENABLE_CLASS + if(tuple->instance) + nori = _search_identifier_in_scope_chain(tuple->s, tuple->scope, n, _PATHING_NORMAL, 0, 0); + else + nori = _ht_find(tuple->scope->var_dict, (void*)n); +#else /* MB_ENABLE_CLASS */ + nori = _ht_find(tuple->scope->var_dict, (void*)n); +#endif /* MB_ENABLE_CLASS */ + if(nori) { + _object_t* ovar = 0; + _var_t* var = _create_var(&ovar, n, 0, true); + obj = (_object_t*)nori->data; + _clone_object(tuple->s, obj, var->data, true, true); + _REF(var->data) + if(_IS_ROUTINE(obj) && obj->data.routine->type != MB_RT_LAMBDA) { + ovar->is_ref = true; + var->data->is_ref = true; + } +#ifdef MB_ENABLE_CLASS + if(_IS_VAR(obj)) + var->pathing = obj->data.variable->pathing; + else + var->pathing = _PATHING_NONE; +#endif /* MB_ENABLE_CLASS */ + ul = _ht_set_or_insert(tuple->outer_scope->scope->var_dict, ovar->data.variable->name, ovar); + mb_assert(ul); + _ht_set_or_insert(tuple->filled, extra, data); + + ast = tuple->lambda->entry; + while(ast && ast != tuple->lambda->end->next) { + _object_t* aobj = (_object_t*)ast->data; + if(aobj) { + switch(aobj->type) { + case _DT_VAR: + if(!strcmp(aobj->data.variable->name, ovar->data.variable->name)) { +#ifdef MB_ENABLE_CLASS + aobj->data.variable->pathing = _PATHING_UPVALUE; +#endif /* MB_ENABLE_CLASS */ + } + + break; + default: /* Do nothing */ + break; + } + } + ast = ast->next; + } + } + } + + return 0; +} + +/* Remove filled upvalues */ +static int _remove_filled_upvalue(void* data, void* extra, _ht_node_t* ht) { + _ht_remove_existing(data, extra, ht); + + return _OP_RESULT_NORMAL; +} + +/* Fill an outer scope with the original one */ +static int _fill_outer_scope(void* data, void* extra, _upvalue_scope_tuple_t* tuple) { + _lambda_t* lambda = (_lambda_t*)data; + mb_unrefvar(extra); + + if(lambda->upvalues) { + tuple->filled = _ht_create(0, _ht_cmp_intptr, _ht_hash_intptr, 0); { + tuple->lambda = lambda; + _HT_FOREACH(lambda->upvalues, _do_nothing_on_ht_for_lambda, _fill_with_upvalue, tuple); + tuple->lambda = 0; + } + _HT_FOREACH(tuple->filled, _do_nothing_on_ht_for_lambda, _remove_filled_upvalue, lambda->upvalues); + if(!_ht_count(lambda->upvalues)) { + _ht_destroy(lambda->upvalues); + lambda->upvalues = 0; + } + _ht_destroy(tuple->filled); + } + + if(lambda->outer_scope) { + _running_context_ref_t* root_ref = _get_root_ref_scope(lambda->outer_scope); + root_ref->prev = tuple->outer_scope; + root_ref->scope->prev = tuple->outer_scope->scope; + } else { + lambda->outer_scope = tuple->outer_scope; + } + + _ref(&tuple->outer_scope->ref, tuple->outer_scope); + + return 0; +} + +/* Remove a lambda from outer scope, which collected the lambda itself as an upvalue */ +static int _remove_this_lambda_from_upvalue(void* data, void* extra, _routine_t* routine) { + int result = _OP_RESULT_NORMAL; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(routine->type == MB_RT_LAMBDA); + + obj = (_object_t*)data; + if(_IS_VAR(obj)) + obj = obj->data.variable->data; + if(_IS_ROUTINE(obj)) { + if(obj->data.routine == routine) { + mb_assert(obj->data.routine->type == MB_RT_LAMBDA); + _MAKE_NIL(obj); + } + } + + return result; +} + +/* Link the local scope of a lambda and all upvalue scopes in chain to a given scope */ +static _running_context_t* _link_lambda_scope_chain(mb_interpreter_t* s, _lambda_t* lambda, bool_t weak) { + _running_context_ref_t* root_ref = 0; + _running_context_t* root = 0; + + if(lambda->outer_scope) { + lambda->scope->prev = lambda->outer_scope->scope; + if(_find_scope(s, lambda->scope->prev)) { + lambda->overlapped = s->running_context; + if(!weak) + s->running_context = lambda->scope; + + return lambda->scope; + } + root_ref = _get_root_ref_scope(lambda->outer_scope); + root_ref->scope->prev = 0; + } + root = _get_root_scope(lambda->scope); + + if(weak) { + _running_context_t* ret = _push_weak_scope_by_routine(s, root, 0); + if(ret != root) { + _destroy_scope(s, ret); + + return 0; + } + } else { + root->prev = s->running_context; + s->running_context = lambda->scope; + } + + return lambda->scope; +} + +/* Unlink the local scope of a lambda and all upvalue scopes in chain from a given scope */ +static _running_context_t* _unlink_lambda_scope_chain(mb_interpreter_t* s, _lambda_t* lambda, bool_t weak) { + _running_context_ref_t* root_ref = 0; + _running_context_t* root = 0; + + if(lambda->outer_scope) { + if(lambda->overlapped) { + if(!weak) + s->running_context = lambda->overlapped; + lambda->overlapped = 0; + lambda->scope->prev = 0; + + return lambda->scope; + } + root_ref = _get_root_ref_scope(lambda->outer_scope); + root = root_ref->scope; + } else { + root = lambda->scope; + } + + if(weak) + _pop_weak_scope(s, root); + else + s->running_context = root->prev; + + root->prev = 0; + lambda->scope->prev = 0; + + return lambda->scope; +} + +/* Check whether an object is a valid lambda body node */ +static bool_t _is_valid_lambda_body_node(mb_interpreter_t* s, _lambda_t* lambda, _object_t* obj) { + mb_unrefvar(s); + mb_unrefvar(lambda); + + return ( + !_IS_FUNC(obj, _core_def) && + !_IS_FUNC(obj, _core_enddef) && +#ifdef MB_ENABLE_CLASS + !_IS_FUNC(obj, _core_class) && + !_IS_FUNC(obj, _core_endclass) && +#endif /* MB_ENABLE_CLASS */ + true + ); +} +#endif /* MB_ENABLE_LAMBDA */ + +#ifdef MB_ENABLE_CLASS +/* Create a scope reference to an existing one by a class */ +static _running_context_t* _reference_scope_by_class(mb_interpreter_t* s, _running_context_t* p, _class_t* c) { + _running_context_t* result = 0; + mb_unrefvar(c); + + mb_assert(s && p); + + if(p->meta == _SCOPE_META_REF) + p = p->ref; + + result = _create_running_context(false); + result->meta = _SCOPE_META_REF; + result->ref = p; + + return result; +} + +/* Push a scope by a class */ +static _running_context_t* _push_scope_by_class(mb_interpreter_t* s, _running_context_t* p) { + mb_assert(s); + + if(_find_scope(s, p)) + p = _reference_scope_by_class(s, p, 0); + p->prev = s->running_context; + s->running_context = p; + + return s->running_context; +} + +/* Try to search an identifire from a class */ +static _ls_node_t* _search_identifier_in_class(mb_interpreter_t* s, _class_t* instance, const char* n, _ht_node_t** ht, _running_context_t** sp) { + _ls_node_t* result = 0; + _ls_node_t* node = 0; + _class_t* meta = 0; + + mb_assert(s && instance && n); + + result = _ht_find(instance->scope->var_dict, (void*)n); + if(result) { + if(ht) *ht = instance->scope->var_dict; + if(sp) *sp = instance->scope; + } + + if(!result) { + node = instance->meta_list ? instance->meta_list->next : 0; + while(node) { + meta = (_class_t*)node->data; + result = _search_identifier_in_class(s, meta, n, ht, sp); + if(result) + break; + node = node->next; + } + } + + return result; +} +#endif /* MB_ENABLE_CLASS */ + +/* Create a scope reference to an existing one by a routine */ +static _running_context_t* _reference_scope_by_routine(mb_interpreter_t* s, _running_context_t* p, _routine_t* r) { + _running_context_t* result = 0; + + mb_assert(s && p); + + if(p->meta == _SCOPE_META_REF) + p = p->ref; + + result = _create_running_context(false); + result->meta = _SCOPE_META_REF; + result->ref = p; + if(r && r->func.basic.parameters) { + result->var_dict = _ht_create(0, _ht_cmp_string, _ht_hash_string, 0); + _LS_FOREACH(r->func.basic.parameters, _do_nothing_on_object, _duplicate_parameter, result); + } + + return result; +} + +/* Push a weak scope by a routine */ +static _running_context_t* _push_weak_scope_by_routine(mb_interpreter_t* s, _running_context_t* p, _routine_t* r) { + mb_assert(s); + + if(_find_scope(s, p)) + p = _reference_scope_by_routine(s, p, r); + if(p) + p->prev = s->running_context; + + return p; +} + +/* Push a scope by a routine */ +static _running_context_t* _push_scope_by_routine(mb_interpreter_t* s, _running_context_t* p) { + mb_assert(s); + + if(_find_scope(s, p)) + p = _reference_scope_by_routine(s, p, 0); + if(p) { + p->prev = s->running_context; + s->running_context = p; + } + + return s->running_context; +} + +/* Destroy a scope */ +static void _destroy_scope(mb_interpreter_t* s, _running_context_t* p) { + mb_unrefvar(s); + + if(p->var_dict) { + _ht_foreach(p->var_dict, _destroy_object); + _ht_destroy(p->var_dict); +#ifdef MB_ENABLE_LAMBDA + if(p->refered_lambdas) { + _ls_destroy(p->refered_lambdas); + p->refered_lambdas = 0; + } +#endif /* MB_ENABLE_LAMBDA */ + } + safe_free(p); +} + +/* Pop a weak scope */ +static _running_context_t* _pop_weak_scope(mb_interpreter_t* s, _running_context_t* p) { + mb_assert(s); + + if(p) + p->prev = 0; + + return p; +} + +/* Pop a scope */ +static _running_context_t* _pop_scope(mb_interpreter_t* s, bool_t tidy) { + _running_context_t* running = 0; + + mb_assert(s); + + running = s->running_context; + s->running_context = running->prev; + running->prev = 0; + if(running->meta == _SCOPE_META_REF) + _destroy_scope(s, running); + else if(tidy) + _out_of_scope(s, running, 0, 0, true); + + return s->running_context; +} + +/* Out of current scope */ +static void _out_of_scope(mb_interpreter_t* s, _running_context_t* running, void* instance, _routine_t* routine, bool_t lose) { +#ifdef MB_ENABLE_LAMBDA + _upvalue_scope_tuple_t tuple; +#endif /* MB_ENABLE_LAMBDA */ + mb_unrefvar(routine); + + mb_assert(s); + +#ifdef MB_ENABLE_LAMBDA + if(running->refered_lambdas) { + tuple.s = s; +#ifdef MB_ENABLE_CLASS + tuple.instance = (_class_t*)instance; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(instance); +#endif /* MB_ENABLE_CLASS */ + tuple.scope = running; + tuple.outer_scope = _create_outer_scope(s); + tuple.lambda = 0; + + _LS_FOREACH(running->refered_lambdas, _do_nothing_on_ht_for_lambda, _fill_outer_scope, &tuple); + + _ls_destroy(running->refered_lambdas); + running->refered_lambdas = 0; + } +#else /* MB_ENABLE_LAMBDA */ + mb_unrefvar(instance); +#endif /* MB_ENABLE_LAMBDA */ + + if(lose) { + if(running->var_dict) + _HT_FOREACH(running->var_dict, _do_nothing_on_object, _lose_object, running); + } +} + +/* Find a scope in a scope chain */ +static _running_context_t* _find_scope(mb_interpreter_t* s, _running_context_t* p) { + _running_context_t* running = 0; + + mb_assert(s); + + running = s->running_context; + while(running) { + if(running == p) + return running; + + if(running->ref == p) + return running->ref; + + running = running->prev; + } + + return running; +} + +/* Get the root scope in a scope chain */ +static _running_context_t* _get_root_scope(_running_context_t* scope) { + _running_context_t* result = 0; + + while(scope) { + result = scope; + scope = scope->prev; + } + + return result; +} + +#ifdef MB_ENABLE_LAMBDA +/* Get the root referenced scope in a referenced scope chain */ +static _running_context_ref_t* _get_root_ref_scope(_running_context_ref_t* scope) { + _running_context_ref_t* result = 0; + + while(scope) { + result = scope; + scope = scope->prev; + } + + return result; +} +#endif /* MB_ENABLE_LAMBDA */ + +/* Get a proper scope to define a routine */ +static _running_context_t* _get_scope_to_add_routine(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + _running_context_t* running = 0; + unsigned short class_state = _CLASS_STATE_NONE; + + mb_assert(s); + + context = s->parsing_context; + running = s->running_context; +#ifdef MB_ENABLE_CLASS + class_state = context->class_state; +#endif /* MB_ENABLE_CLASS */ + if(class_state != _CLASS_STATE_NONE) { + if(running) + running = running->prev; + } else { + while(running) { + if(running->meta == _SCOPE_META_ROOT) + break; + + running = running->prev; + } + } + + return running; +} + +/* Try to search an identifier in a scope chain */ +static _ls_node_t* _search_identifier_in_scope_chain(mb_interpreter_t* s, _running_context_t* scope, const char* n, int fp, _ht_node_t** ht, _running_context_t** sp) { + _ls_node_t* result = 0; + _running_context_t* running = 0; + _ht_node_t* fn = 0; + _running_context_t* fs = 0; + + mb_assert(s && n); + +#ifdef MB_ENABLE_CLASS + if(fp) { + result = _search_identifier_accessor(s, scope, n, &fn, &fs, fp == _PATHING_UNKNOWN_FOR_NOT_FOUND); + if(result) + goto _exit; + } + + if(s->last_routine && s->last_routine->instance) { + _class_t* lastinst = s->last_routine->instance; + s->last_routine->instance = 0; + result = _search_identifier_in_class(s, lastinst, n, &fn, &fs); + s->last_routine->instance = lastinst; + if(result) + goto _exit; + } +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(fp); +#endif /* MB_ENABLE_CLASS */ + + if(scope) + running = scope; + else + running = s->running_context; + while(running && !result) { + if(running->var_dict) { + result = _ht_find(running->var_dict, (void*)n); + fn = running->var_dict; + fs = running; + if(!result && running->meta == _SCOPE_META_REF) { + result = _ht_find(running->ref->var_dict, (void*)n); + fn = running->ref->var_dict; + fs = running->ref; + } + if(result) + break; + } + + running = running->prev; + } + +#ifdef MB_ENABLE_CLASS +_exit: +#endif /* MB_ENABLE_CLASS */ + if(ht) *ht = fn; + if(sp) *sp = fs; + + return result; +} + +/* Try to search an array in a scope chain */ +static _array_t* _search_array_in_scope_chain(mb_interpreter_t* s, _array_t* i, _object_t** o) { + _object_t* obj = 0; + _ls_node_t* scp = 0; + _array_t* result = 0; + + mb_assert(s && i); + + result = i; + if(result->name) + scp = _search_identifier_in_scope_chain(s, 0, result->name, _PATHING_NONE, 0, 0); + if(scp) { + obj = (_object_t*)scp->data; + if(obj && obj->type == _DT_ARRAY) { + result = obj->data.array; + if(o) *o = obj; + } + } + + return result; +} + +/* Try to search a variable in a scope chain */ +static _var_t* _search_var_in_scope_chain(mb_interpreter_t* s, _var_t* i, _object_t** o) { + _object_t* obj = 0; + _ls_node_t* scp = 0; + _var_t* result = 0; + + mb_assert(s && i); + + if(o) *o = 0; + result = i; + scp = _search_identifier_in_scope_chain(s, 0, result->name, _PATHING_NORMAL, 0, 0); + if(scp) { + obj = (_object_t*)scp->data; + if(_IS_VAR(obj)) { + if(o) *o = obj; + result = obj->data.variable; + } + } + + return result; +} + +/* Try to search an identifier accessor in a scope */ +static _ls_node_t* _search_identifier_accessor(mb_interpreter_t* s, _running_context_t* scope, const char* n, _ht_node_t** ht, _running_context_t** sp, bool_t unknown_for_not_found) { + _ls_node_t* result = 0; + _object_t* obj = 0; + char acc[_SINGLE_SYMBOL_MAX_LENGTH]; + int i = 0; + int j = 0; + int nc = 0; +#ifdef MB_ENABLE_CLASS + _class_t* instance = 0; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(unknown_for_not_found); +#endif /* MB_ENABLE_CLASS */ + + mb_assert(s && n); + + while((i == 0) || (i > 0 && n[i - 1])) { + acc[j] = n[i]; + if(_is_accessor_char(acc[j]) || acc[j] == _ZERO_CHAR) { + acc[j] = _ZERO_CHAR; + ++nc; + do { +#ifdef MB_ENABLE_CLASS + if(instance) { + result = _search_identifier_in_class(s, instance, acc, ht, sp); + if(!result && unknown_for_not_found) { + result = (_ls_node_t*)&_LS_NODE_UNKNOWN; + + return result; + } + + break; + } else if(nc > 1) { + return 0; + } +#endif /* MB_ENABLE_CLASS */ + + result = _search_identifier_in_scope_chain(s, scope, acc, _PATHING_NONE, ht, sp); + } while(0); + + if(!result) + return 0; + obj = (_object_t*)result->data; + if(!obj) + return 0; + switch(obj->type) { + case _DT_VAR: +#ifdef MB_ENABLE_USERTYPE_REF + if(obj->data.variable->data->type == _DT_USERTYPE_REF) + return result; +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_CLASS + if(obj->data.variable->data->type == _DT_CLASS) + instance = obj->data.variable->data->data.instance; +#endif /* MB_ENABLE_CLASS */ + + break; +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + instance = obj->data.instance; + + break; +#endif /* MB_ENABLE_CLASS */ + case _DT_ARRAY: /* Fall through */ + case _DT_ROUTINE: /* Do nothing */ + break; + default: + mb_assert(0 && "Unsupported."); + + return 0; + } + + j = 0; + i++; + + continue; + } + j++; + i++; + } + + return result; +} + +/* Create a variable object */ +static _var_t* _create_var(_object_t** oobj, const char* n, size_t ns, bool_t dup_name) { + _object_t* obj = 0; + _var_t* var = 0; + + var = (_var_t*)mb_malloc(sizeof(_var_t)); + memset(var, 0, sizeof(_var_t)); + if(dup_name) + var->name = mb_strdup(n, ns); + else + var->name = (char*)n; + var->data = _create_object(); + + if(!oobj || !(*oobj)) + obj = _create_object(); + else + obj = *oobj; + _MAKE_NIL(obj); + obj->type = _DT_VAR; + obj->data.variable = var; + obj->is_ref = false; + + if(oobj) *oobj = obj; + + return var; +} + +/* Retrieve a variable's name and value */ +static int _retrieve_var(void* data, void* extra, void* t) { + int result = _OP_RESULT_NORMAL; + _tuple3_t* tuple = 0; + mb_interpreter_t* s = 0; + mb_var_retrieving_func_t retrieved = 0; + int* count = 0; + _object_t* obj = 0; + _var_t* var = 0; + _array_t* arr = 0; + mb_value_t val; + mb_unrefvar(extra); + + mb_assert(data && t); + + mb_make_nil(val); + + tuple = (_tuple3_t*)t; + s = (mb_interpreter_t*)tuple->e1; + retrieved = (mb_var_retrieving_func_t)(uintptr_t)tuple->e2; + count = (int*)tuple->e3; + + obj = (_object_t*)data; + switch(obj->type) { + case _DT_VAR: + if(retrieved) { + var = (_var_t*)obj->data.variable; + _internal_object_to_public_value(var->data, &val); + retrieved(s, var->name, val); + } + ++*count; + + break; + case _DT_ARRAY: + if(retrieved) { + arr = (_array_t*)obj->data.array; + _internal_object_to_public_value(obj, &val); + retrieved(s, arr->name, val); + } + ++*count; + + break; + default: /* Do nothing */ + break; + } + + return result; +} + +/* Create an _object_t struct */ +static _object_t* _create_object(void) { + _object_t* result = 0; + + result = (_object_t*)mb_malloc(sizeof(_object_t)); + _MAKE_NIL(result); + + return result; +} + +/* Clone the data of an object */ +static int _clone_object(mb_interpreter_t* s, _object_t* obj, _object_t* tgt, bool_t toupval, bool_t deep) { + int result = 0; + + mb_assert(obj && tgt); + + _MAKE_NIL(tgt); + if(_is_internal_object(obj)) + goto _exit; + tgt->type = obj->type; + switch(obj->type) { + case _DT_VAR: + _clone_object(s, obj->data.variable->data, tgt, toupval, deep); + + break; + case _DT_STRING: + tgt->data.string = mb_strdup(obj->data.string, 0); + + break; +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + _clone_usertype_ref(obj->data.usertype_ref, tgt); + + break; +#endif /* MB_ENABLE_USERTYPE_REF */ + case _DT_FUNC: + tgt->data.func->name = mb_strdup(obj->data.func->name, strlen(obj->data.func->name) + 1); + tgt->data.func->pointer = obj->data.func->pointer; + + break; + case _DT_ARRAY: + if(deep) { + tgt->data.array = _clone_array(s, obj->data.array); + } else { + tgt->data.array = obj->data.array; +#ifdef MB_ENABLE_ARRAY_REF + _ref(&obj->data.array->ref, obj->data.array); +#endif /* MB_ENABLE_ARRAY_REF */ + } + + break; +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST: + if(deep) { + tgt->data.list = _create_list(obj->data.list->ref.s); + _ref(&tgt->data.list->ref, tgt->data.list); + _LS_FOREACH(obj->data.list->list, _do_nothing_on_object, _clone_to_list, tgt->data.list); + } else { + tgt->data.list = obj->data.list; + _ref(&obj->data.list->ref, obj->data.list); + } + + break; + case _DT_LIST_IT: + tgt->data.list_it = _create_list_it(obj->data.list_it->list, true); + + break; + case _DT_DICT: + if(deep) { + tgt->data.dict = _create_dict(obj->data.dict->ref.s); + _ref(&tgt->data.dict->ref, tgt->data.dict); + _HT_FOREACH(obj->data.dict->dict, _do_nothing_on_object, _clone_to_dict, tgt->data.dict); + } else { + tgt->data.dict = obj->data.dict; + _ref(&obj->data.dict->ref, obj->data.dict); + } + + break; + case _DT_DICT_IT: + tgt->data.dict_it = _create_dict_it(obj->data.dict_it->dict, true); + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ + case _DT_LABEL: + tgt->data.label->name = mb_strdup(obj->data.label->name, 0); + tgt->data.label->node = obj->data.label->node; + + break; +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + if(deep) { + tgt->data.instance = (_class_t*)mb_malloc(sizeof(_class_t)); + _init_class(s, tgt->data.instance, mb_strdup(obj->data.instance->name, 0)); + tgt->data.instance->created_from = obj->data.instance->created_from; + _push_scope_by_class(s, tgt->data.instance->scope); + _traverse_class(obj->data.instance, _clone_clsss_field, _clone_class_meta_link, _META_LIST_MAX_DEPTH, false, tgt->data.instance, 0); + if(_search_class_hash_and_compare_functions(s, tgt->data.instance) != MB_FUNC_OK) { + mb_assert(0 && "Impossible."); + } + tgt->data.instance->userdata = obj->data.instance->userdata; + _pop_scope(s, false); + } else { + tgt->data.instance = obj->data.instance; + _ref(&obj->data.instance->ref, obj->data.instance); + } + + break; +#endif /* MB_ENABLE_CLASS */ + case _DT_ROUTINE: + tgt->data.routine = _clone_routine( + obj->data.routine, +#ifdef MB_ENABLE_CLASS + obj->data.routine->instance, +#else /* MB_ENABLE_CLASS */ + 0, +#endif /* MB_ENABLE_CLASS */ + toupval + ); + + break; + case _DT_NIL: /* Fall through */ + case _DT_UNKNOWN: /* Fall through */ + case _DT_INT: /* Fall through */ + case _DT_REAL: /* Fall through */ + case _DT_TYPE: /* Fall through */ + case _DT_SEP: /* Fall through */ + case _DT_EOS: /* Fall through */ + case _DT_USERTYPE: + tgt->data = obj->data; + + break; + default: + mb_assert(0 && "Invalid type."); + + break; + } + tgt->is_ref = false; +#ifdef MB_ENABLE_SOURCE_TRACE + tgt->source_pos = 0; + tgt->source_row = 0; + tgt->source_col = 0; +#else /* MB_ENABLE_SOURCE_TRACE */ + tgt->source_pos = 0; +#endif /* MB_ENABLE_SOURCE_TRACE */ + ++result; + +_exit: + return result; +} + +/* Dispose the data of an object */ +static int _dispose_object(_object_t* obj) { + int result = 0; + _var_t* var = 0; + + mb_assert(obj); + + if(_is_internal_object(obj)) + goto _exit; + switch(obj->type) { + case _DT_VAR: + if(!obj->is_ref) { + var = (_var_t*)obj->data.variable; + safe_free(var->name); + mb_assert(var->data->type != _DT_VAR); + if(_IS_ME(var)) + _destroy_object_capsule_only(var->data, 0); + else + _destroy_object(var->data, 0); + safe_free(var); + } + + break; + case _DT_STRING: + if(!obj->is_ref) { + if(obj->data.string) { + safe_free(obj->data.string); + } + } + + break; + case _DT_FUNC: + safe_free(obj->data.func->name); + safe_free(obj->data.func); + + break; + _UNREF_USERTYPE_REF(obj) +#ifdef MB_ENABLE_ARRAY_REF + _UNREF_ARRAY(obj) +#else /* MB_ENABLE_ARRAY_REF */ + _DESTROY_ARRAY(obj) +#endif /* MB_ENABLE_ARRAY_REF */ + _UNREF_COLL(obj) + _UNREF_COLL_IT(obj) + _UNREF_CLASS(obj) + _UNREF_ROUTINE(obj) + case _DT_LABEL: + if(!obj->is_ref) { + safe_free(obj->data.label->name); + safe_free(obj->data.label); + } + + break; +#ifdef MB_ENABLE_SOURCE_TRACE + case _DT_PREV_IMPORT: /* Fall through */ + case _DT_POST_IMPORT: + if(!obj->is_ref) { + if(obj->data.import_info) { + if(obj->data.import_info->source_file) { + safe_free(obj->data.import_info->source_file); + } + safe_free(obj->data.import_info); + } + } + + break; +#endif /* MB_ENABLE_SOURCE_TRACE */ + case _DT_NIL: /* Fall through */ + case _DT_UNKNOWN: /* Fall through */ + case _DT_INT: /* Fall through */ + case _DT_REAL: /* Fall through */ + case _DT_TYPE: /* Fall through */ + case _DT_SEP: /* Fall through */ + case _DT_EOS: /* Fall through */ + case _DT_USERTYPE: /* Do nothing */ + break; + default: + mb_assert(0 && "Invalid type."); + + break; + } + obj->is_ref = false; + obj->type = _DT_NIL; + memset(&obj->data, 0, sizeof(obj->data)); +#ifdef MB_ENABLE_SOURCE_TRACE + obj->source_pos = 0; + obj->source_row = 0; + obj->source_col = 0; +#else /* MB_ENABLE_SOURCE_TRACE */ + obj->source_pos = 0; +#endif /* MB_ENABLE_SOURCE_TRACE */ + ++result; + +_exit: + return result; +} + +/* Destroy an object and its data */ +static int _destroy_object(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(data); + + obj = (_object_t*)data; + if(!_dispose_object(obj)) + goto _exit; + safe_free(obj); + +_exit: + return result; +} + +/* Destroy an object, including its data and extra data */ +static int _destroy_object_with_extra(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + + mb_assert(data); + + obj = (_object_t*)data; + if(!_dispose_object(obj)) + goto _exit; + safe_free(obj); + obj = (_object_t*)extra; + if(!_dispose_object(obj)) + goto _exit; + safe_free(obj); + +_exit: + return result; +} + +/* Destroy an object which is not come from compile time */ +static int _destroy_object_not_compile_time(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(data); + + obj = (_object_t*)data; + if(!obj->source_pos) { + if(!_dispose_object(obj)) + goto _exit; + safe_free(obj); + } + +_exit: + return result; +} + +/* Destroy only the capsule (wrapper) of an object, leave the data behind */ +static int _destroy_object_capsule_only(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + _object_t* obj = 0; + mb_unrefvar(extra); + + mb_assert(data); + + obj = (_object_t*)data; + safe_free(obj); + + return result; +} + +/* Do nothing with an object, this is a helper function */ +static int _do_nothing_on_object(void* data, void* extra) { + int result = _OP_RESULT_NORMAL; + mb_unrefvar(data); + mb_unrefvar(extra); + + return result; +} + +/* Lose an object in a scope */ +static int _lose_object(void* data, void* extra, _running_context_t* running) { + int result = _OP_RESULT_NORMAL; + _object_t* obj = 0; + bool_t make_nil = true; + + mb_assert(data && extra); + + obj = (_object_t*)data; +#ifdef MB_ENABLE_LAMBDA + if(obj->type == _DT_ROUTINE && obj->data.routine->type == MB_RT_LAMBDA) + obj->is_ref = false; +#endif /* MB_ENABLE_LAMBDA */ +#ifdef MB_ENABLE_COLLECTION_LIB + if(obj->type == _DT_LIST_IT) { + if((!obj->is_ref || !obj->data.list_it->locking) && running->intermediate_value.value.list_it != obj->data.list_it) { + _destroy_list_it(obj->data.list_it); /* Process dangling value */ + } + + goto _exit; + } else if(obj->type == _DT_DICT_IT) { + if((!obj->is_ref || !obj->data.dict_it->locking) && running->intermediate_value.value.dict_it != obj->data.dict_it) { + _destroy_dict_it(obj->data.dict_it); /* Process dangling value */ + } + + goto _exit; + } +#endif /* MB_ENABLE_COLLECTION_LIB */ + switch(obj->type) { + case _DT_VAR: + _lose_object(obj->data.variable->data, extra, running); + make_nil = false; + + break; + _UNREF_USERTYPE_REF(obj) + _UNREF_ARRAY(obj) + _UNREF_COLL(obj) + _UNREF_CLASS(obj) + _UNREF_ROUTINE(obj) + default: + make_nil = false; + + break; + } + +#ifdef MB_ENABLE_COLLECTION_LIB +_exit: +#endif /* MB_ENABLE_COLLECTION_LIB */ + if(make_nil) { + _MAKE_NIL(obj); + } + + return result; +} + +/* Remove an object referenced to source code */ +static int _remove_source_object(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + mb_unrefvar(extra); + + mb_assert(data); + + return result; +} + +/* Destroy a chunk of memory */ +static int _destroy_memory(void* data, void* extra) { + int result = _OP_RESULT_NORMAL; + mb_unrefvar(extra); + + mb_assert(data); + + safe_free(data); + + return result; +} + +/* Compare two numbers from two objects */ +static int _compare_numbers(const _object_t* first, const _object_t* second) { + int result = 0; + + mb_assert(first && second); + mb_assert((first->type == _DT_INT || first->type == _DT_REAL) && (second->type == _DT_INT || second->type == _DT_REAL)); + + if(first->type == _DT_INT && second->type == _DT_INT) { + if(first->data.integer > second->data.integer) + result = 1; + else if(first->data.integer < second->data.integer) + result = -1; + } else if(first->type == _DT_REAL && second->type == _DT_REAL) { + if(first->data.float_point > second->data.float_point) + result = 1; + else if(first->data.float_point < second->data.float_point) + result = -1; + } else { + if((first->type == _DT_INT ? (real_t)first->data.integer : first->data.float_point) > (second->type == _DT_INT ? (real_t)second->data.integer : second->data.float_point)) + result = 1; + else if((first->type == _DT_INT ? (real_t)first->data.integer : first->data.float_point) < (second->type == _DT_INT ? (real_t)second->data.integer : second->data.float_point)) + result = -1; + } + + return result; +} + +/* Determine whether an object is internal */ +static bool_t _is_internal_object(_object_t* obj) { + bool_t result = false; + + mb_assert(obj); + + result = ( + (_exp_assign == obj) || + (_OBJ_BOOL_TRUE == obj) || (_OBJ_BOOL_FALSE == obj) + ); + + return result; +} + +/* Convert a public mb_data_e type to an internal _data_e */ +static _data_e _public_type_to_internal_type(mb_data_e t) { + switch(t) { + case MB_DT_NIL: + return _DT_NIL; + case MB_DT_INT: + return _DT_INT; + case MB_DT_REAL: + return _DT_REAL; + case MB_DT_STRING: + return _DT_STRING; + case MB_DT_TYPE: + return _DT_TYPE; + case MB_DT_USERTYPE: + return _DT_USERTYPE; +#ifdef MB_ENABLE_USERTYPE_REF + case MB_DT_USERTYPE_REF: + return _DT_USERTYPE_REF; +#endif /* MB_ENABLE_USERTYPE_REF */ + case MB_DT_ARRAY: + return _DT_ARRAY; +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: + return _DT_LIST; + case MB_DT_LIST_IT: + return _DT_LIST_IT; + case MB_DT_DICT: + return _DT_DICT; + case MB_DT_DICT_IT: + return _DT_DICT_IT; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case MB_DT_CLASS: + return _DT_CLASS; +#endif /* MB_ENABLE_CLASS */ + case MB_DT_ROUTINE: + return _DT_ROUTINE; + default: + return _DT_UNKNOWN; + } +} + +/* Convert an internal mb_data_e type to a public _data_e */ +static mb_data_e _internal_type_to_public_type(_data_e t) { + switch(t) { + case _DT_NIL: + return MB_DT_NIL; + case _DT_INT: + return MB_DT_INT; + case _DT_REAL: + return MB_DT_REAL; + case _DT_STRING: + return MB_DT_STRING; + case _DT_TYPE: + return MB_DT_TYPE; + case _DT_USERTYPE: + return MB_DT_USERTYPE; +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + return MB_DT_USERTYPE_REF; +#endif /* MB_ENABLE_USERTYPE_REF */ + case _DT_ARRAY: + return MB_DT_ARRAY; +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST: + return MB_DT_LIST; + case _DT_LIST_IT: + return MB_DT_LIST_IT; + case _DT_DICT: + return MB_DT_DICT; + case _DT_DICT_IT: + return MB_DT_DICT_IT; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + return MB_DT_CLASS; +#endif /* MB_ENABLE_CLASS */ + case _DT_ROUTINE: + return MB_DT_ROUTINE; + default: + return MB_DT_UNKNOWN; + } +} + +/* Assign a public mb_value_t to an internal _object_t */ +static int _public_value_to_internal_object(mb_value_t* pbl, _object_t* itn) { + int result = MB_FUNC_OK; + + mb_assert(pbl && itn); + + _UNREF(itn) + + switch(pbl->type) { + case MB_DT_NIL: + itn->type = _DT_NIL; + itn->data.integer = false; + + break; + case MB_DT_UNKNOWN: + itn->type = _DT_UNKNOWN; + itn->data.integer = false; + + break; + case MB_DT_INT: + itn->type = _DT_INT; + itn->data.integer = pbl->value.integer; + + break; + case MB_DT_REAL: + itn->type = _DT_REAL; + itn->data.float_point = pbl->value.float_point; + + break; + case MB_DT_STRING: + itn->type = _DT_STRING; + itn->data.string = pbl->value.string; + itn->is_ref = true; + + break; + case MB_DT_TYPE: + itn->type = _DT_TYPE; + itn->data.type = pbl->value.type; + + break; + case MB_DT_USERTYPE: + itn->type = _DT_USERTYPE; + memcpy(itn->data.bytes, pbl->value.bytes, sizeof(mb_val_bytes_t)); + + break; +#ifdef MB_ENABLE_USERTYPE_REF + case MB_DT_USERTYPE_REF: + itn->type = _DT_USERTYPE_REF; + itn->data.usertype_ref = (_usertype_ref_t*)pbl->value.usertype_ref; + + break; +#endif /* MB_ENABLE_USERTYPE_REF */ + case MB_DT_ARRAY: + itn->type = _DT_ARRAY; + itn->data.array = (_array_t*)pbl->value.array; + itn->is_ref = false; + + break; +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: + itn->type = _DT_LIST; + itn->data.list = (_list_t*)pbl->value.list; + + break; + case MB_DT_LIST_IT: + itn->type = _DT_LIST_IT; + itn->data.list_it = (_list_it_t*)pbl->value.list_it; + + break; + case MB_DT_DICT: + itn->type = _DT_DICT; + itn->data.dict = (_dict_t*)pbl->value.dict; + + break; + case MB_DT_DICT_IT: + itn->type = _DT_DICT_IT; + itn->data.dict_it = (_dict_it_t*)pbl->value.dict_it; + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case MB_DT_CLASS: + itn->type = _DT_CLASS; + itn->data.instance = (_class_t*)pbl->value.instance; + + break; +#endif /* MB_ENABLE_CLASS */ + case MB_DT_ROUTINE: + itn->type = _DT_ROUTINE; + itn->data.routine = (_routine_t*)pbl->value.routine; + + break; + default: + result = MB_FUNC_ERR; + + break; + } + + return result; +} + +/* Assign an internal _object_t to a public mb_value_t */ +static int _internal_object_to_public_value(_object_t* itn, mb_value_t* pbl) { + int result = MB_FUNC_OK; + + mb_assert(pbl && itn); + + switch(itn->type) { + case _DT_VAR: + if(itn->data.variable) + result = _internal_object_to_public_value(itn->data.variable->data, pbl); + + break; + case _DT_NIL: + mb_make_nil(*pbl); + + break; + case _DT_UNKNOWN: + pbl->type = MB_DT_UNKNOWN; + pbl->value.integer = false; + + break; + case _DT_INT: + pbl->type = MB_DT_INT; + pbl->value.integer = itn->data.integer; + + break; + case _DT_REAL: + pbl->type = MB_DT_REAL; + pbl->value.float_point = itn->data.float_point; + + break; + case _DT_STRING: + pbl->type = MB_DT_STRING; + pbl->value.string = itn->data.string; + + break; + case _DT_TYPE: + pbl->type = MB_DT_TYPE; + pbl->value.type = itn->data.type; + + break; + case _DT_USERTYPE: + pbl->type = MB_DT_USERTYPE; + memcpy(pbl->value.bytes, itn->data.bytes, sizeof(mb_val_bytes_t)); + + break; +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + pbl->type = MB_DT_USERTYPE_REF; + pbl->value.usertype_ref = itn->data.usertype_ref; + + break; +#endif /* MB_ENABLE_USERTYPE_REF */ + case _DT_ARRAY: + pbl->type = MB_DT_ARRAY; + pbl->value.array = itn->data.array; + + break; +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST: + pbl->type = MB_DT_LIST; + pbl->value.list = itn->data.list; + + break; + case _DT_LIST_IT: + pbl->type = MB_DT_LIST_IT; + pbl->value.list_it = itn->data.list; + + break; + case _DT_DICT: + pbl->type = MB_DT_DICT; + pbl->value.dict = itn->data.dict; + + break; + case _DT_DICT_IT: + pbl->type = MB_DT_DICT_IT; + pbl->value.dict_it = itn->data.dict_it; + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + pbl->type = MB_DT_CLASS; + pbl->value.instance = itn->data.instance; + + break; +#endif /* MB_ENABLE_CLASS */ + case _DT_ROUTINE: + pbl->type = MB_DT_ROUTINE; + pbl->value.routine = itn->data.routine; + + break; + default: + result = MB_FUNC_ERR; + + break; + } + + return result; +} + +/* Create an internal object from a public value */ +static int _create_internal_object_from_public_value(mb_value_t* pbl, _object_t** itn) { + int result = MB_FUNC_OK; + + mb_assert(pbl && itn); + + *itn = _create_object(); + _public_value_to_internal_object(pbl, *itn); + if((*itn)->type == _DT_STRING) { + (*itn)->is_ref = false; + (*itn)->data.string = mb_strdup((*itn)->data.string, mb_strlen((*itn)->data.string) + 1); + } + + return result; +} + +/* Compare a public value and an internal object */ +static int _compare_public_value_and_internal_object(mb_value_t* pbl, _object_t* itn) { + int result = 0; + mb_value_t tmp; + + mb_make_nil(tmp); + _internal_object_to_public_value(itn, &tmp); + if(pbl->type != tmp.type) { + result = pbl->type - tmp.type; + } else { + switch(pbl->type) { + case MB_DT_NIL: + result = 0; + + break; + case MB_DT_INT: + result = mb_memcmp(&pbl->value.integer, &tmp.value.integer, sizeof(int_t)); + + break; + case MB_DT_REAL: + result = mb_memcmp(&pbl->value.float_point, &tmp.value.float_point, sizeof(real_t)); + + break; + case MB_DT_STRING: + result = mb_memcmp(&pbl->value.string, &tmp.value.string, sizeof(char*)); + + break; + case MB_DT_TYPE: + result = mb_memcmp(&pbl->value.type, &tmp.value.type, sizeof(mb_data_e)); + + break; + case MB_DT_USERTYPE: + result = mb_memcmp(&pbl->value.bytes, &tmp.value.bytes, sizeof(mb_val_bytes_t)); + + break; +#ifdef MB_ENABLE_USERTYPE_REF + case MB_DT_USERTYPE_REF: + result = mb_memcmp(&pbl->value.usertype_ref, &tmp.value.usertype_ref, sizeof(void*)); + + break; +#endif /* MB_ENABLE_USERTYPE_REF */ + case MB_DT_ARRAY: + result = mb_memcmp(&pbl->value.array, &tmp.value.array, sizeof(void*)); + + break; +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: + result = mb_memcmp(&pbl->value.list, &tmp.value.list, sizeof(void*)); + + break; + case MB_DT_LIST_IT: + result = mb_memcmp(&pbl->value.list_it, &tmp.value.list_it, sizeof(void*)); + + break; + case MB_DT_DICT: + result = mb_memcmp(&pbl->value.dict, &tmp.value.dict, sizeof(void*)); + + break; + case MB_DT_DICT_IT: + result = mb_memcmp(&pbl->value.dict_it, &tmp.value.dict_it, sizeof(void*)); + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case MB_DT_CLASS: + result = mb_memcmp(&pbl->value.instance, &tmp.value.instance, sizeof(void*)); + + break; +#endif /* MB_ENABLE_CLASS */ + case MB_DT_ROUTINE: + result = mb_memcmp(&pbl->value.routine, &tmp.value.routine, sizeof(void*)); + + break; + default: + result = mb_memcmp(pbl->value.bytes, tmp.value.bytes, sizeof(mb_val_bytes_t)); + + break; + } + } + + return result; +} + +/* Try clear the intermediate value */ +static void _try_clear_intermediate_value(void* data, void* extra, mb_interpreter_t* s) { + _object_t* obj = 0; + _running_context_t* running = 0; + mb_unrefvar(extra); + + mb_assert(s); + + if(data == 0) + return; + + obj = (_object_t*)data; + running = s->running_context; + + if(!_compare_public_value_and_internal_object(&running->intermediate_value, obj)) { + mb_make_nil(running->intermediate_value); + } +} + +/* Remove from another list if exists */ +static void _remove_if_exists(void* data, void* extra, _ls_node_t* ls) { + _object_t* obj = 0; + mb_unrefvar(extra); + + obj = (_object_t*)data; + _ls_try_remove(ls, obj, _ls_cmp_data, 0); +} + +/* Destroy an object in variable argument list */ +static void _destroy_var_arg(void* data, void* extra, _gc_t* gc) { + _object_t* obj = 0; + mb_unrefvar(extra); + mb_unrefvar(gc); + + mb_assert(data); + + obj = (_object_t*)data; + _UNREF(obj) + safe_free(obj); +} + +/* Destroy edge destroying objects */ +static void _destroy_edge_objects(mb_interpreter_t* s) { + if(!s) return; + + _LS_FOREACH(s->edge_destroy_objects, _destroy_object, _try_clear_intermediate_value, s); + _ls_clear(s->edge_destroy_objects); +} + +/* Mark a string as an edge destroying object */ +static void _mark_edge_destroy_string(mb_interpreter_t* s, char* ch) { + _object_t* temp_obj = 0; + + mb_assert(s && ch); + + temp_obj = _create_object(); + temp_obj->type = _DT_STRING; + temp_obj->is_ref = false; + temp_obj->data.string = ch; + _ls_pushback(s->edge_destroy_objects, temp_obj); +} + +/* Destroy lazy destroying objects */ +static void _destroy_lazy_objects(mb_interpreter_t* s) { + mb_assert(s); + + _LS_FOREACH(s->lazy_destroy_objects, _destroy_object, _try_clear_intermediate_value, s); + _ls_clear(s->lazy_destroy_objects); +} + +/* Mark a string as a lazy destroying object */ +static void _mark_lazy_destroy_string(mb_interpreter_t* s, char* ch) { + _object_t* temp_obj = 0; + + mb_assert(s && ch); + + temp_obj = _create_object(); + temp_obj->type = _DT_STRING; + temp_obj->is_ref = false; + temp_obj->data.string = ch; + _ls_pushback(s->lazy_destroy_objects, temp_obj); +} + +/* Assign a value with another */ +static void _assign_public_value(mb_interpreter_t* s, mb_value_t* tgt, mb_value_t* src, bool_t pit) { + _object_t obj; + mb_value_t nil; + + mb_assert(tgt); + +#ifdef MB_ENABLE_COLLECTION_LIB + if(pit && _try_purge_it(s, tgt, 0)) + return; +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(s); + mb_unrefvar(pit); +#endif /* MB_ENABLE_COLLECTION_LIB */ + + _MAKE_NIL(&obj); + _public_value_to_internal_object(tgt, &obj); + _UNREF(&obj) + + mb_make_nil(nil); + if(!src) + src = &nil; + memcpy(tgt, src, sizeof(mb_value_t)); + *src = nil; +} + +/* Swap two public values */ +static void _swap_public_value(mb_value_t* tgt, mb_value_t* src) { + mb_value_t tmp; + + mb_assert(tgt && src); + + tmp = *tgt; + *tgt = *src; + *src = tmp; +} + +/* Clear the scope chain */ +static int _clear_scope_chain(mb_interpreter_t* s) { + int result = 0; + _running_context_t* running = 0; + _running_context_t* prev = 0; + + mb_assert(s); + + running = s->running_context; + while(running) { + prev = running->prev; + + _ht_foreach(running->var_dict, _destroy_object); + _ht_clear(running->var_dict); +#ifdef MB_ENABLE_LAMBDA + if(running->refered_lambdas) + _ls_clear(running->refered_lambdas); +#endif /* MB_ENABLE_LAMBDA */ + + result++; + running = prev; + } + + return result; +} + +/* Dispose the scope chain */ +static int _dispose_scope_chain(mb_interpreter_t* s) { + int result = 0; + _running_context_t* running = 0; + _running_context_t* prev = 0; + + mb_assert(s); + + running = s->running_context; + while(running) { + prev = running->prev; + + _ht_foreach(running->var_dict, _destroy_object); + _ht_clear(running->var_dict); + _ht_destroy(running->var_dict); +#ifdef MB_ENABLE_LAMBDA + if(running->refered_lambdas) { + _ls_clear(running->refered_lambdas); + _ls_destroy(running->refered_lambdas); + running->refered_lambdas = 0; + } +#endif /* MB_ENABLE_LAMBDA */ + running->var_dict = 0; + mb_dispose_value(s, running->intermediate_value); + safe_free(running); + + result++; + running = prev; + } + s->running_context = 0; + + return result; +} + +/* Tidy the scope chain */ +static void _tidy_scope_chain(mb_interpreter_t* s) { + _parsing_context_t* context = 0; + + mb_assert(s); + + context = s->parsing_context; + if(!context) { + while(s->running_context->prev) + _pop_scope(s, false); + + return; + } + + while(context->routine_state && s->running_context->meta != _SCOPE_META_ROOT) { + if(_end_routine(s)) { + if(!s->running_context->prev) + break; + _pop_scope(s, false); + } + } +#ifdef MB_ENABLE_CLASS + while(context->class_state != _CLASS_STATE_NONE) { + if(_end_class(s)) { + if(!s->running_context->prev) + break; + _pop_scope(s, false); + } + } +#endif /* MB_ENABLE_CLASS */ +} + +/* Collect the intermediate value in a scope */ +static void _collect_intermediate_value_in_scope(_running_context_t* running, void* data) { + _object_t tmp; + + mb_assert(running && data); + + if(!running) + return; + + _MAKE_NIL(&tmp); + _public_value_to_internal_object(&running->intermediate_value, &tmp); + if(tmp.data.pointer == data) { + switch(tmp.type) { +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: /* Fall through */ +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST: /* Fall through */ + case _DT_DICT: /* Fall through */ +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: /* Fall through */ +#endif /* MB_ENABLE_CLASS */ + case _DT_ARRAY: + mb_make_nil(running->intermediate_value); + + break; + default: /* Do nothing */ + break; + } + } +} + +#ifdef MB_ENABLE_FORK +/* Collect all intermediate values in scope chain */ +static void _collect_intermediate_value_in_scope_chain(void* data, void* extra, void* d) { + mb_interpreter_t* s = 0; + _running_context_t* running = 0; + mb_unrefvar(extra); + + s = (mb_interpreter_t*)data; + running = s->running_context; + while(running) { + _collect_intermediate_value_in_scope(running, d); + running = running->prev; + } +} +#endif /* MB_ENABLE_FORK */ + +/* Collect the intermediate value */ +static void _collect_intermediate_value(_ref_t* ref, void* data) { + mb_interpreter_t* s = 0; + + mb_assert(ref && data); + + s = ref->s; + if(!s) return; + if(s->running_context) + _collect_intermediate_value_in_scope(s->running_context, data); +#ifdef MB_ENABLE_FORK + if(s->all_forked) { + while(mb_get_forked_from(s, &s) == MB_FUNC_OK) { + /* Do nothing */ + } + _LS_FOREACH(s->all_forked, _do_nothing_on_object, _collect_intermediate_value_in_scope_chain, data); + } +#endif /* MB_ENABLE_FORK */ +} + +/* Mark the intermediate value to be collected if it's dangling */ +static void _mark_dangling_intermediate_value(mb_interpreter_t* s, _running_context_t* running) { + mb_assert(s && running); + + switch(running->intermediate_value.type) { +#ifdef MB_ENABLE_USERTYPE_REF + case MB_DT_USERTYPE_REF: /* Fall through */ +#endif /* MB_ENABLE_USERTYPE_REF */ +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: /* Fall through */ + case MB_DT_DICT: /* Fall through */ + case MB_DT_LIST_IT: /* Fall through */ + case MB_DT_DICT_IT: /* Fall through */ +#endif /* MB_ENABLE_COLLECTION_LIB */ + case MB_DT_ROUTINE: { + _object_t tmp; + _MAKE_NIL(&tmp); + _public_value_to_internal_object(&running->intermediate_value, &tmp); +#ifdef MB_ENABLE_COLLECTION_LIB + if(tmp.type == _DT_LIST_IT && tmp.data.list_it->locking) + break; + else if(tmp.type == _DT_DICT_IT && tmp.data.dict_it->locking) + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ + _ADDGC(&tmp, &s->gc, false) /* Process dangling value */ + } + + break; + default: /* Do nothing */ + break; + } +} + +/* Evaluate a variable, this is a helper function for the PRINT statement */ +static _object_t* _eval_var_in_print(mb_interpreter_t* s, _object_t** val_ptr, _ls_node_t** ast, _object_t* obj) { + _object_t tmp; + + mb_assert(s); + + switch(obj->type) { + case _DT_ROUTINE: + _execute_statement(s, ast, true); + _MAKE_NIL(&tmp); + _public_value_to_internal_object(&s->running_context->intermediate_value, &tmp); + if(tmp.type == _DT_STRING) { + tmp.data.string = mb_strdup(tmp.data.string, strlen(tmp.data.string) + 1); + tmp.is_ref = false; + mb_make_nil(s->running_context->intermediate_value); + } + **val_ptr = tmp; + if(obj->data.routine->type != MB_RT_NATIVE) { + if(*ast) + *ast = (*ast)->prev; + } + + break; + case _DT_VAR: + *val_ptr = obj->data.variable->data; + if(*ast) *ast = (*ast)->next; + + break; + default: + *val_ptr = obj; + if(*ast) *ast = (*ast)->next; + + break; + } + + return *val_ptr; +} + +/** Interpretation */ + +/* Callback a stepped debug handler, this function is called before each step */ +static int _prev_stepped(mb_interpreter_t* s, _ls_node_t* ast) { + int result = MB_FUNC_OK; + _object_t* obj = 0; + + mb_assert(s); + + if(s->debug_prev_stepped_handler) { + if(ast && ast->data) { + obj = (_object_t*)ast->data; +#ifdef MB_ENABLE_SOURCE_TRACE + result = s->debug_prev_stepped_handler(s, (void**)&ast, s->source_file, obj->source_pos, obj->source_row, obj->source_col); +#else /* MB_ENABLE_SOURCE_TRACE */ + result = s->debug_prev_stepped_handler(s, (void**)&ast, s->source_file, obj->source_pos, 0, 0); +#endif /* MB_ENABLE_SOURCE_TRACE */ + } else { + result = s->debug_prev_stepped_handler(s, (void**)&ast, s->source_file, -1, 0, 0); + } + } + + return result; +} + +/* Callback a stepped debug handler, this function is called after each step */ +static int _post_stepped(mb_interpreter_t* s, _ls_node_t* ast) { + int result = MB_FUNC_OK; + _object_t* obj = 0; + + mb_assert(s); + + if(s->debug_post_stepped_handler) { + if(ast && ast->data) { + obj = (_object_t*)ast->data; +#ifdef MB_ENABLE_SOURCE_TRACE + result = s->debug_post_stepped_handler(s, (void**)&ast, s->source_file, obj->source_pos, obj->source_row, obj->source_col); +#else /* MB_ENABLE_SOURCE_TRACE */ + result = s->debug_post_stepped_handler(s, (void**)&ast, s->source_file, obj->source_pos, 0, 0); +#endif /* MB_ENABLE_SOURCE_TRACE */ + } else { + result = s->debug_post_stepped_handler(s, (void**)&ast, s->source_file, -1, 0, 0); + } + } + + return result; +} + +/* Execute the ast, this is the core execution function */ +static int _execute_statement(mb_interpreter_t* s, _ls_node_t** l, bool_t force_next) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _running_context_t* running = 0; + _ls_node_t* sub_stack = 0; + bool_t skip_to_eoi = true; + bool_t end_of_ast = false; + + mb_assert(s && l); + + running = s->running_context; + sub_stack = s->sub_stack; + + ast = *l; + + obj = (_object_t*)ast->data; + + result = _prev_stepped(s, ast); + if(result != MB_FUNC_OK) + goto _exit; + +_retry: + switch(obj->type) { + case _DT_FUNC: + if(_is_binary(obj->data.func->pointer)) { + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(_is_flow(obj->data.func->pointer)) { + result = (obj->data.func->pointer)(s, (void**)&ast); + } else { +#ifdef MB_ENABLE_STACK_TRACE + _ls_pushback(s->stack_frames, obj->data.func->name); +#endif /* MB_ENABLE_STACK_TRACE */ + result = (obj->data.func->pointer)(s, (void**)&ast); +#ifdef MB_ENABLE_STACK_TRACE + _ls_popback(s->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ + _mark_dangling_intermediate_value(s, running); + } + if(result == MB_FUNC_IGNORE) { + result = MB_FUNC_OK; + obj = (_object_t*)ast->data; + + goto _retry; + } + + break; + case _DT_VAR: +#ifdef MB_ENABLE_CLASS + if(obj->data.variable->data->type == _DT_ROUTINE) { + if(ast && ast->next && _IS_FUNC(ast->next->data, _core_open_bracket)) { + obj = obj->data.variable->data; + + goto _retry; + } else { + result = _core_let(s, (void**)&ast); + } + } else if(s->last_instance || obj->data.variable->pathing) { + /* Need to path */ + _ls_node_t* pathed = _search_identifier_in_scope_chain(s, 0, obj->data.variable->name, _PU(obj->data.variable->pathing), 0, 0); + if(pathed && pathed->data) { + if(obj != (_object_t*)pathed->data) { + /* Found another node */ +#ifdef MB_ENABLE_USERTYPE_REF + if(_try_call_func_on_usertype_ref(s, &ast, obj, pathed, &result)) + break; +#endif /* MB_ENABLE_USERTYPE_REF */ + + obj = (_object_t*)pathed->data; + + goto _retry; + } else { + /* Final node */ + result = _core_let(s, (void**)&ast); + } + } else { + /* Normal node */ + result = _core_let(s, (void**)&ast); + } + } else { + /* Do not need to path */ + result = _core_let(s, (void**)&ast); + } +#else /* MB_ENABLE_CLASS */ + result = _core_let(s, (void**)&ast); +#endif /* MB_ENABLE_CLASS */ + + break; + case _DT_ARRAY: + result = _core_let(s, (void**)&ast); + + break; + case _DT_INT: /* Fall through */ + case _DT_REAL: /* Fall through */ + case _DT_STRING: + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + + break; +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + + break; +#endif /* MB_ENABLE_CLASS */ + case _DT_ROUTINE: + ast = ast->prev; + result = _core_call(s, (void**)&ast); + _mark_dangling_intermediate_value(s, running); + + break; +#ifdef MB_ENABLE_SOURCE_TRACE + case _DT_PREV_IMPORT: + s->source_file = obj->data.import_info->source_file; + + break; + case _DT_POST_IMPORT: + s->source_file = obj->data.import_info->source_file; + + break; +#endif /* MB_ENABLE_SOURCE_TRACE */ + default: /* Do nothing */ + break; + } + + if(s->schedule_suspend_tag) { + if(s->schedule_suspend_tag == MB_FUNC_SUSPEND) + mb_suspend(s, (void**)&ast); + result = s->schedule_suspend_tag; + s->schedule_suspend_tag = 0; + } + + if(result != MB_FUNC_OK && result != MB_FUNC_SUSPEND && result != MB_SUB_RETURN) + goto _exit; + + if(ast && ast != s->ast) { + obj = DON(ast); + if(!obj) { + /* Do nothing */ + } else if(_IS_EOS(obj)) { + if(force_next || result != MB_SUB_RETURN) + ast = ast->next; + } else if(_IS_SEP(obj, ':')) { + skip_to_eoi = false; + ast = ast->next; + } else if(_IS_VAR(obj)) { +#ifdef MB_ENABLE_CLASS + _ls_node_t* fn = 0; + if(_is_valid_class_accessor_following_routine(s, obj->data.variable, ast, &fn)) { + if(fn) { + if(s->calling) + result = _core_let(s, (void**)&ast); + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + } else { + _handle_error_on_obj(s, SE_RN_COLON_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } +#else /* MB_ENABLE_CLASS */ + _handle_error_on_obj(s, SE_RN_COLON_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_CLASS */ + } else if(_IS_FUNC(obj, _core_enddef) && result != MB_SUB_RETURN) { + ast = (_ls_node_t*)_ls_popback(sub_stack); +#ifdef MB_ENABLE_LAMBDA + } else if(obj && _IS_FUNC(obj, _core_close_bracket) && s->last_routine && s->last_routine->type == MB_RT_LAMBDA) { + /* Do nothing */ +#endif /* MB_ENABLE_LAMBDA */ + } else if(obj && obj->type == _DT_FUNC && (_is_operator(obj->data.func->pointer) || _is_flow(obj->data.func->pointer))) { + ast = ast->next; + } else if(obj && obj->type == _DT_FUNC) { + /* Do nothing */ + } else if(obj && obj->type != _DT_FUNC) { + ast = ast->next; + } else { + _handle_error_on_obj(s, SE_RN_COLON_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + } + + if(skip_to_eoi && s->skip_to_eoi && s->skip_to_eoi == _ls_back(s->sub_stack)) { + s->skip_to_eoi = 0; + obj = (_object_t*)ast->data; + if(!_IS_EOS(obj)) { + result = _skip_to(s, &ast, 0, _DT_EOS); + if(result != MB_FUNC_OK) + goto _exit; + } + } + +_exit: + _destroy_lazy_objects(s); + + *l = ast; + + if(!ast) { + ast = _ls_back(s->ast); + end_of_ast = true; + } + + if(ast == s->ast) { + *l = ast->next; + } else { + int ret = _post_stepped(s, ast); + if(result == MB_FUNC_OK) + result = ret; + + if(end_of_ast && ast && ast->next) /* May be changed when stepping */ + *l = ast->next; + } + + return result; +} + +/* Common function to end current looping */ +static int _common_end_looping(mb_interpreter_t* s, _ls_node_t** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + result = _skip_struct(s, l, _core_for, 0, _core_next); + if(result == MB_FUNC_OK) + result = _skip_to(s, l, 0, _DT_EOS); + + return result; +} + +/* Common function to keep current looping */ +static int _common_keep_looping(mb_interpreter_t* s, _ls_node_t** l, _var_t* var_loop) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _running_context_t* running = 0; + + mb_assert(s && l); + + running = s->running_context; + ast = *l; + + obj = (_object_t*)ast->data; + while(!_IS_FUNC(obj, _core_next)) { + result = _execute_statement(s, &ast, false); + if(result == MB_LOOP_CONTINUE) { /* NEXT */ + if(!running->next_loop_var || running->next_loop_var == var_loop) { /* This loop */ + running->next_loop_var = 0; + result = MB_FUNC_OK; + + break; + } else { /* Not this loop */ + if(_skip_struct(s, &ast, _core_for, 0, _core_next) != MB_FUNC_OK) + goto _exit; + _skip_to(s, &ast, 0, _DT_EOS); + + goto _exit; + } + } else if(result == MB_LOOP_BREAK) { /* EXIT */ + if(_skip_struct(s, &ast, _core_for, 0, _core_next) != MB_FUNC_OK) + goto _exit; + _skip_to(s, &ast, 0, _DT_EOS); + + goto _exit; + } else if(result == MB_SUB_RETURN) { /* RETURN */ + goto _exit; + } else if(result != MB_FUNC_OK) { /* Normally */ + goto _exit; + } + + if(!ast) { + _handle_error_on_obj(s, SE_RN_NEXT_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + obj = (_object_t*)ast->data; + } + +_exit: + *l = ast; + + return result; +} + +/* Execute normal FOR-TO-STEP-NEXT-routine */ +static int _execute_normal_for_loop(mb_interpreter_t* s, _ls_node_t** l, _var_t* var_loop) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _ls_node_t* to_node = 0; + _object_t* obj = 0; + _object_t to_val; + _object_t step_val; + _object_t* to_val_ptr = 0; + _object_t* step_val_ptr = 0; + _tuple3_t ass_tuple3; + _tuple3_t* ass_tuple3_ptr = 0; + + mb_assert(s && l && var_loop); + + ast = *l; + + to_val_ptr = &to_val; + _MAKE_NIL(to_val_ptr); + step_val_ptr = &step_val; + _MAKE_NIL(step_val_ptr); + ass_tuple3_ptr = &ass_tuple3; + + /* Get begin value */ + result = _execute_statement(s, &ast, true); + if(result != MB_FUNC_OK) + goto _exit; + if(!ast) + goto _exit; + ast = ast->prev; + + obj = (_object_t*)ast->data; + if(!_IS_FUNC(obj, _core_to)) { + _handle_error_on_obj(s, SE_RN_TO_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + ast = ast->next; + if(!ast) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + to_node = ast; + +_to: + ast = to_node; + + /* Get end value */ + result = _calc_expression(s, &ast, &to_val_ptr); + if(result != MB_FUNC_OK) + goto _exit; + + if(ast) { + obj = (_object_t*)ast->data; + if(!_IS_FUNC(obj, _core_step)) { + step_val = _OBJ_INT_UNIT; + } else { + ast = ast->next; + if(!ast) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + /* Get step value */ + result = _calc_expression(s, &ast, &step_val_ptr); + if(result != MB_FUNC_OK) + goto _exit; + } + } + + if((_compare_numbers(step_val_ptr, &_OBJ_INT_ZERO) > 0 && _compare_numbers(var_loop->data, to_val_ptr) > 0) || + (_compare_numbers(step_val_ptr, &_OBJ_INT_ZERO) < 0 && _compare_numbers(var_loop->data, to_val_ptr) < 0)) { + /* End looping */ + result = _common_end_looping(s, &ast); + + goto _exit; + } else { + /* Keep looping */ + if(!ast) + goto _exit; + result = _common_keep_looping(s, &ast, var_loop); + if(result == MB_LOOP_BREAK) { + result = MB_FUNC_OK; + + goto _exit; + } else if(result != MB_FUNC_OK || result == MB_SUB_RETURN) { + goto _exit; + } + + ass_tuple3.e1 = var_loop->data; + ass_tuple3.e2 = step_val_ptr; + ass_tuple3.e3 = var_loop->data; + _instruct_num_op_num(+, &ass_tuple3_ptr); + + goto _to; + } + +_exit: + *l = ast; + + return result; +} + +#ifdef MB_ENABLE_COLLECTION_LIB +/* Execute ranged FOR-IN-NEXT-routine */ +static int _execute_ranged_for_loop(mb_interpreter_t* s, _ls_node_t** l, _var_t* var_loop) { + int result = MB_FUNC_ERR; + _ls_node_t* ast = 0; + _running_context_t* running = 0; + _var_t* pathed_var = 0; + _object_t* old_val = 0; + _ref_t* old_val_gc = 0; + _ls_node_t* to_node = 0; + _object_t range; + _object_t* range_ptr = 0; + _list_it_t* lit = 0; + _dict_it_t* dit = 0; + _list_it_t* tlit = 0; + _dict_it_t* tdit = 0; + mb_value_t ref_val; + mb_value_t ref_it; +#if defined MB_ENABLE_USERTYPE_REF || defined MB_ENABLE_CLASS + mb_meta_status_e os = MB_MS_NONE; +#endif /* MB_ENABLE_USERTYPE_REF || MB_ENABLE_CLASS */ + + mb_assert(s && l && var_loop); + + running = s->running_context; +#ifdef MB_ENABLE_CLASS + if(var_loop->pathing) + pathed_var = _search_var_in_scope_chain(s, var_loop, 0); + if(pathed_var) { + _UNREF(pathed_var->data) + _MAKE_NIL(pathed_var->data); + } +#endif /* MB_ENABLE_CLASS */ + if(!pathed_var) + pathed_var = var_loop; + old_val = pathed_var->data; + if(_is_ref(old_val)) { + old_val_gc = (_ref_t*)old_val->data.pointer; + if(!_gc_remove(old_val_gc, (void*)old_val_gc, &old_val_gc->s->gc)) + old_val_gc = 0; + } + range_ptr = ⦥ + _MAKE_NIL(range_ptr); + mb_make_nil(ref_val); + mb_make_nil(ref_it); + + ast = *l; + ast = ast->next; + ast = ast->next; + if(!ast) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + /* Get collection */ + result = _calc_expression(s, &ast, &range_ptr); + if(result != MB_FUNC_OK) + goto _exit; + + /* Create iterator */ + switch(range_ptr->type) { +#ifdef MB_ENABLE_USERTYPE_REF + case _DT_USERTYPE_REF: + _internal_object_to_public_value(range_ptr, &ref_val); + os = _try_overridden(s, (void**)&ast, &ref_val, _COLL_ID_ITERATOR, MB_MF_COLL); + if((os & MB_MS_DONE) != MB_MS_NONE && (os & MB_MS_RETURNED) != MB_MS_NONE) + _swap_public_value(&ref_it, &running->intermediate_value); + + break; +#endif /* MB_ENABLE_USERTYPE_REF */ + case _DT_LIST: + tlit = lit = _create_list_it(range_ptr->data.list, true); + + break; + case _DT_DICT: + tdit = dit = _create_dict_it(range_ptr->data.dict, true); + + break; +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: + _internal_object_to_public_value(range_ptr, &ref_val); + os = _try_overridden(s, (void**)&ast, &ref_val, _COLL_ID_ITERATOR, MB_MF_COLL); + if((os & MB_MS_DONE) != MB_MS_NONE && (os & MB_MS_RETURNED) != MB_MS_NONE) + _swap_public_value(&ref_it, &running->intermediate_value); + + break; +#endif /* MB_ENABLE_CLASS */ + default: + _handle_error_on_obj(s, SE_RN_ITERABLE_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + + break; + } + to_node = ast; + switch(range_ptr->type) { _REF_COLL(range_ptr) _REF_USERTYPE_REF(range_ptr) default: /* Do nothing */ break; } + +_to: + ast = to_node; + + /* Move next */ +#ifdef MB_ENABLE_USERTYPE_REF + if(ref_it.type != MB_DT_NIL) { + mb_value_t moved_next; + mb_value_t curr_val; + _object_t curr_obj; + + mb_make_nil(moved_next); + mb_make_nil(curr_val); + _MAKE_NIL(&curr_obj); + + /* Move next */ + os = _try_overridden(s, (void**)&ast, &ref_it, _COLL_ID_MOVE_NEXT, MB_MF_COLL); + if((os & MB_MS_DONE) != MB_MS_NONE && (os & MB_MS_RETURNED) != MB_MS_NONE) + _swap_public_value(&moved_next, &running->intermediate_value); + + if(moved_next.type == MB_DT_INT && moved_next.value.integer) { + /* Get current value */ + os = _try_overridden(s, (void**)&ast, &ref_it, _STD_ID_GET, MB_MF_FUNC); + if((os & MB_MS_DONE) != MB_MS_NONE && (os & MB_MS_RETURNED) != MB_MS_NONE) + _swap_public_value(&curr_val, &running->intermediate_value); + + /* Assign loop variable */ + _public_value_to_internal_object(&curr_val, &curr_obj); + pathed_var->data = &curr_obj; + /* Keep looping */ + result = _common_keep_looping(s, &ast, var_loop); + _UNREF(&curr_obj) + if(result == MB_LOOP_BREAK) { + result = MB_FUNC_OK; + + goto _exit; + } else if(result != MB_FUNC_OK || result == MB_SUB_RETURN) { + goto _exit; + } + + goto _to; + } else { + /* End looping */ + result = _common_end_looping(s, &ast); + + goto _exit; + } + } +#endif /* MB_ENABLE_USERTYPE_REF */ + if(lit) lit = _move_list_it_next(lit); + else if(dit) dit = _move_dict_it_next(dit); + if((lit && _invalid_list_it(lit)) || (dit && _invalid_dict_it(dit))) { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(!lit && !dit) { + /* End looping */ + result = _common_end_looping(s, &ast); + + goto _exit; + } else { + /* Assign loop variable */ + if(lit && !lit->list->range_begin && lit->curr.node && lit->curr.node->data) { + pathed_var->data = (_object_t*)lit->curr.node->data; + } else if(lit && lit->list->range_begin) { + _dispose_object(pathed_var->data); + pathed_var->data->type = _DT_INT; + pathed_var->data->data.integer = lit->curr.ranging; + } else if(dit && dit->curr_node && dit->curr_node->extra) { + pathed_var->data = (_object_t*)dit->curr_node->extra; + } + /* Keep looping */ + result = _common_keep_looping(s, &ast, var_loop); + if(result == MB_LOOP_BREAK) { + result = MB_FUNC_OK; + + goto _exit; + } else if(result != MB_FUNC_OK || result == MB_SUB_RETURN) { + goto _exit; + } + + goto _to; + } + +_exit: +#ifdef MB_ENABLE_USERTYPE_REF + if(ref_it.type != MB_DT_NIL) { + _object_t it_obj; + _MAKE_NIL(&it_obj); + + _public_value_to_internal_object(&ref_it, &it_obj); + _UNREF(&it_obj) + } +#endif /* MB_ENABLE_USERTYPE_REF */ + if(tlit) _destroy_list_it(tlit); + else if(tdit) _destroy_dict_it(tdit); + switch(range_ptr->type) { _UNREF_COLL(range_ptr) _UNREF_USERTYPE_REF(range_ptr) default: /* Do nothing */ break; } + + *l = ast; + + pathed_var->data = old_val; + if(old_val_gc) + _gc_add(old_val_gc, (void*)old_val_gc, &old_val_gc->s->gc); + + return result; +} +#endif /* MB_ENABLE_COLLECTION_LIB */ + +/* Skip current execution flow to a specific function */ +static int _skip_to(mb_interpreter_t* s, _ls_node_t** l, mb_func_t f, _data_e t) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _ls_node_t* tmp = 0; + _object_t* obj = 0; + + mb_assert(s && l); + + ast = *l; + do { + if(!ast) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(tmp), MB_FUNC_ERR, _exit, result); + } + tmp = ast; + obj = (_object_t*)ast->data; + *l = ast; + ast = ast->next; + } while(!(_IS_FUNC(obj, f)) && obj->type != t); + +_exit: + return result; +} + +/* Skip single line structure */ +static bool_t _skip_single_line_struct(_ls_node_t** ast, mb_func_t func) { + _ls_node_t* post = *ast; + while(post && !_IS_EOS(post->data)) + post = post->next; + if(post && post->prev && !_IS_FUNC(post->prev->data, func)) { + *ast = post; + + return true; + } + + return false; +} + +/* Skip current IF execution flow to next chunk */ +static int _skip_if_chunk(mb_interpreter_t* s, _ls_node_t** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _ls_node_t* tmp = 0; + _object_t* obj = 0; + int nested = 0; + unsigned mask = 0; + + mb_assert(s && l); + + ast = *l; + mb_assert(ast && ast->prev); + do { + if(!ast) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + tmp = ast; + obj = (_object_t*)ast->data; + *l = ast; + ast = ast->next; + if(ast && _IS_FUNC((_object_t*)ast->data, _core_if)) { + if(_skip_single_line_struct(&ast, _core_then)) + continue; + if(++nested > sizeof(mask) * 8) { + _handle_error_on_obj(s, SE_RN_TOO_MANY_NESTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } else if(ast && nested && _IS_FUNC((_object_t*)ast->data, _core_then)) { + if(!(ast && ast->next && _IS_EOS(ast->next->data))) + mask |= 1 << (nested - 1); + } else if(ast && nested && + (((mask & (1 << (nested - 1))) && _IS_EOS(ast->data)) || + (!(mask & (1 << (nested - 1))) && _IS_FUNC((_object_t*)ast->data, _core_endif))) + ) { + if(--nested < 0) { + _handle_error_on_obj(s, SE_RN_INCOMPLETE_STRUCTURE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + } + } while(nested || (!_IS_FUNC(obj, _core_elseif) && !_IS_FUNC(obj, _core_else) && !_IS_FUNC(obj, _core_endif))); + +_exit: + return result; +} + +/* Skip current structure */ +static int _skip_struct(mb_interpreter_t* s, _ls_node_t** l, mb_func_t open_func, mb_func_t post_open_func, mb_func_t close_func) { + int result = MB_FUNC_OK; + int count = 0; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _object_t* obj_prev = 0; + + mb_assert(s && l && open_func && close_func); + + ast = *l; + + count = 1; + do { + if(!ast || !ast->next) { + _handle_error_on_obj(s, SE_RN_INCOMPLETE_STRUCTURE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + obj_prev = (_object_t*)ast->data; + ast = ast->next; + obj = (_object_t*)ast->data; + if(_IS_FUNC(obj, open_func)) { + if(post_open_func) { + if(_skip_single_line_struct(&ast, post_open_func)) + continue; + } + ++count; + } else if(_IS_FUNC(obj, close_func) && _IS_EOS(obj_prev)) { + --count; + } + } while(count); + +_exit: + *l = ast; + + return result; +} + +/* Check whether multiline statement is allowed */ +static bool_t _multiline_statement(mb_interpreter_t* s) { +#if _MULTILINE_STATEMENT + if(_ls_empty(s->multiline_enabled)) + return false; + + return (bool_t)(intptr_t)_ls_back(s->multiline_enabled)->data; +#else /* _MULTILINE_STATEMENT */ + mb_unrefvar(s); + + return false; +#endif /* _MULTILINE_STATEMENT */ +} + +/* Create a running context */ +static _running_context_t* _create_running_context(bool_t create_var_dict) { + _running_context_t* result = 0; + + result = (_running_context_t*)mb_malloc(sizeof(_running_context_t)); + memset(result, 0, sizeof(_running_context_t)); + result->calc_depth = _INFINITY_CALC_DEPTH; + if(create_var_dict) + result->var_dict = _ht_create(0, _ht_cmp_string, _ht_hash_string, 0); + + return result; +} + +/* Reset the parsing context of a MY-BASIC environment */ +static _parsing_context_t* _reset_parsing_context(_parsing_context_t* context) { + _ls_node_t* imp = 0; + + if(!context) + context = (_parsing_context_t*)mb_malloc(sizeof(_parsing_context_t)); + else + imp = context->imported; + memset(context, 0, sizeof(_parsing_context_t)); + context->parsing_row = 1; + if(!imp) { + imp = _ls_create(); + } else { + _ls_foreach(imp, _destroy_memory); + _ls_clear(imp); + } + context->imported = imp; + _end_of_file(context); + + return context; +} + +/* Destroy the parsing context of a MY-BASIC environment */ +static void _destroy_parsing_context(_parsing_context_t* _UNALIGNED_ARG * context) { + if(!context || !(*context)) + return; + + if(*context) { + if((*context)->imported) { + _ls_foreach((*context)->imported, _destroy_memory); + _ls_destroy((*context)->imported); + } + safe_free(*context); + } +} + +/** Interface processors */ + +#ifdef MB_ENABLE_MODULE +/* Create a module function structure */ +static _module_func_t* _create_module_func(mb_interpreter_t* s, mb_func_t f) { + _module_func_t* result = 0; + + mb_assert(s); + + if(!s->with_module) + return result; + + result = (_module_func_t*)mb_malloc(sizeof(_module_func_t)); + result->module = mb_strdup(s->with_module, 0); + result->func = f; + + return result; +} + +/* Destroy a module function structure */ +static int _ls_destroy_module_func(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + _module_func_t* mod = 0; + mb_unrefvar(extra); + + mb_assert(data); + + mod = (_module_func_t*)data; + safe_free(mod->module); + safe_free(mod); + + return result; +} + +/* Destroy all module function structures */ +static int _ht_destroy_module_func_list(void* data, void* extra) { + int result = _OP_RESULT_DEL_NODE; + _ls_node_t* lst = 0; + char* n = 0; + + mb_assert(data); + + lst = (_ls_node_t*)data; + n = (char*)extra; + _ls_foreach(lst, _ls_destroy_module_func); + _ls_destroy(lst); + safe_free(n); + + return result; +} +#endif /* MB_ENABLE_MODULE */ + +/* Generate a function name to be registered according to module information */ +static char* _generate_func_name(mb_interpreter_t* s, char* n, bool_t with_mod) { + char* name = 0; + size_t _sl = 0; + + mb_assert(s && n); + + _sl = strlen(n); +#ifdef MB_ENABLE_MODULE + if(with_mod && s->with_module) { + size_t _ml = strlen(s->with_module); + name = (char*)mb_malloc(_ml + 1 + _sl + 1); + memcpy(name, s->with_module, _ml); + name[_ml] = '.'; + memcpy(name + _ml + 1, n, _sl + 1); + } else { + name = (char*)mb_malloc(_sl + 1); + memcpy(name, n, _sl + 1); + } +#else /* MB_ENABLE_MODULE */ + mb_unrefvar(with_mod); + name = (char*)mb_malloc(_sl + 1); + memcpy(name, n, _sl + 1); +#endif /* MB_ENABLE_MODULE */ + mb_strupr(name); + + return name; +} + +/* Register a function to a MY-BASIC environment */ +static int _register_func(mb_interpreter_t* s, char* n, mb_func_t f, bool_t local) { + int result = 0; + _ht_node_t* scope = 0; + _ls_node_t* exists = 0; + char* name = 0; + + mb_assert(s); + + if(!n) + return result; + + n = mb_strdup(n, strlen(n) + 1); + mb_strupr(n); + + scope = local ? s->local_func_dict : s->global_func_dict; +#ifdef MB_ENABLE_MODULE + if(s->with_module) + name = _generate_func_name(s, n, true); +#endif /* MB_ENABLE_MODULE */ + if(!name) + name = mb_strdup(n, strlen(n) + 1); + exists = _ht_find(scope, (void*)name); + if(!exists) { + result += _ht_set_or_insert(scope, (void*)name, (void*)(intptr_t)f); + } else { + _set_current_error(s, SE_CM_FUNC_EXISTS, 0); + safe_free(name); + } + +#ifdef MB_ENABLE_MODULE + if(s->with_module) { + _ls_node_t* tmp = 0; + exists = _ht_find(s->module_func_dict, (void*)n); + if(!exists) { + name = _generate_func_name(s, n, false); + result += _ht_set_or_insert(s->module_func_dict, (void*)name, _ls_create()); + } + exists = _ht_find(s->module_func_dict, (void*)n); + exists = (_ls_node_t*)exists->data; + tmp = _ls_find(exists, s, _ls_cmp_module_func, 0); + if(!tmp) + _ls_pushback(exists, _create_module_func(s, f)); + else + _set_current_error(s, SE_CM_FUNC_EXISTS, 0); + } +#endif /* MB_ENABLE_MODULE */ + + safe_free(n); + + return result; +} + +/* Remove a function from a MY-BASIC environment */ +static int _remove_func(mb_interpreter_t* s, char* n, bool_t local) { + int result = 0; + _ht_node_t* scope = 0; + _ls_node_t* exists = 0; + char* name = 0; + + mb_assert(s); + + if(!n) + return result; + + n = mb_strdup(n, strlen(n) + 1); + mb_strupr(n); + + scope = local ? s->local_func_dict : s->global_func_dict; +#ifdef MB_ENABLE_MODULE + if(s->with_module) + name = _generate_func_name(s, n, true); +#endif /* MB_ENABLE_MODULE */ + if(!name) + name = mb_strdup(n, strlen(n) + 1); + exists = _ht_find(scope, (void*)name); + if(exists) + result += _ht_remove(scope, (void*)name, _ls_cmp_extra_string); + else + _set_current_error(s, SE_CM_FUNC_DOES_NOT_EXIST, 0); + safe_free(name); + +#ifdef MB_ENABLE_MODULE + if(s->with_module) { + _ls_node_t* tmp = 0; + exists = _ht_find(s->module_func_dict, (void*)n); + if(exists) { + exists = (_ls_node_t*)exists->data; + tmp = _ls_find(exists, s, _ls_cmp_module_func, 0); + if(tmp) + _ls_remove(exists, tmp, _ls_destroy_module_func); + } + } +#endif /* MB_ENABLE_MODULE */ + + safe_free(n); + + return result; +} + +/* Find function interface in the function dictionaries */ +static _ls_node_t* _find_func(mb_interpreter_t* s, char* n, bool_t* mod) { + _ls_node_t* result = 0; + mb_unrefvar(mod); + + mb_assert(s && n); + + n = mb_strdup(n, strlen(n) + 1); + mb_strupr(n); + + result = _ht_find(s->local_func_dict, (void*)n); + if(!result) + result = _ht_find(s->global_func_dict, (void*)n); + +#ifdef MB_ENABLE_MODULE + if(!result) { + result = _ht_find(s->module_func_dict, (void*)n); + if(result && result->data) { + _module_func_t* mp = 0; + result = (_ls_node_t*)result->data; + result = result->next; + while(result) { + mp = (_module_func_t*)result->data; + if(_ls_find(s->using_modules, mp->module, (_ls_compare_t)_ht_cmp_string, 0)) + break; + result = result->next; + } + *mod = true; + } + } +#endif /* MB_ENABLE_MODULE */ + + safe_free(n); + + return result; +} + +/* Open global constants */ +static int _open_constant(mb_interpreter_t* s) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + unsigned long ul = 0; + + mb_assert(s); + + running = s->running_context; + + ul = _ht_set_or_insert(running->var_dict, (void*)MB_TRUE, _OBJ_BOOL_TRUE); + mb_assert(ul); + ul = _ht_set_or_insert(running->var_dict, (void*)MB_FALSE, _OBJ_BOOL_FALSE); + mb_assert(ul); + + return result; +} + +/* Close global constants */ +static int _close_constant(mb_interpreter_t* s) { + int result = MB_FUNC_OK; + + mb_assert(s); + + return result; +} + +/* Open the core function library */ +static int _open_core_lib(mb_interpreter_t* s) { + int result = 0; + int i = 0; + + mb_assert(s); + + for(i = 0; i < countof(_core_libs); ++i) + result += _register_func(s, _core_libs[i].name, _core_libs[i].pointer, true); + + return result; +} + +/* Close the core function library */ +static int _close_core_lib(mb_interpreter_t* s) { + int result = 0; + int i = 0; + + mb_assert(s); + + for(i = 0; i < countof(_core_libs); ++i) + result += _remove_func(s, _core_libs[i].name, true); + + return result; +} + +/* Open the standard function library */ +static int _open_std_lib(mb_interpreter_t* s) { + int result = 0; + int i = 0; + + mb_assert(s); + + for(i = 0; i < countof(_std_libs); ++i) + result += _register_func(s, _std_libs[i].name, _std_libs[i].pointer, true); + + return result; +} + +/* Close the standard function library */ +static int _close_std_lib(mb_interpreter_t* s) { + int result = 0; + int i = 0; + + mb_assert(s); + + for(i = 0; i < countof(_std_libs); ++i) + result += _remove_func(s, _std_libs[i].name, true); + + return result; +} + +#ifdef MB_ENABLE_COLLECTION_LIB +/* Open the collection function library */ +static int _open_coll_lib(mb_interpreter_t* s) { + int result = 0; + int i = 0; + + mb_assert(s); + + for(i = 0; i < countof(_coll_libs); ++i) + result += _register_func(s, _coll_libs[i].name, _coll_libs[i].pointer, true); + + return result; +} + +/* Close the collection function library */ +static int _close_coll_lib(mb_interpreter_t* s) { + int result = 0; + int i = 0; + + mb_assert(s); + + for(i = 0; i < countof(_coll_libs); ++i) + result += _remove_func(s, _coll_libs[i].name, true); + + return result; +} +#endif /* MB_ENABLE_COLLECTION_LIB */ + +/* ========================================================} */ + +/* +** {======================================================== +** Public functions definitions +*/ + +/* Get the version number of this MY-BASIC system */ +unsigned long mb_ver(void) { + return MB_VERSION; +} + +/* Get the version text of this MY-BASIC system */ +const char* mb_ver_string(void) { + return MB_VERSION_STRING; +} + +/* Initialize the MY-BASIC system */ +int mb_init(void) { + if(_exp_assign) + return MB_FUNC_ERR; + + _exp_assign = _create_object(); + _exp_assign->type = _DT_FUNC; + _exp_assign->data.func = (_func_t*)mb_malloc(sizeof(_func_t)); + memset(_exp_assign->data.func, 0, sizeof(_func_t)); + _exp_assign->data.func->name = (char*)mb_malloc(strlen(_DUMMY_ASSIGN_CHAR) + 1); + memcpy(_exp_assign->data.func->name, _DUMMY_ASSIGN_CHAR, strlen(_DUMMY_ASSIGN_CHAR) + 1); + _exp_assign->data.func->pointer = _core_dummy_assign; + + mb_assert(!_OBJ_BOOL_TRUE); + if(!_OBJ_BOOL_TRUE) { + _var_t* bvar = _create_var(&_OBJ_BOOL_TRUE, MB_TRUE, strlen(MB_TRUE) + 1, true); + bvar->data->type = _DT_INT; + bvar->data->data.integer = 1; + } + mb_assert(!_OBJ_BOOL_FALSE); + if(!_OBJ_BOOL_FALSE) { + _var_t* bvar = _create_var(&_OBJ_BOOL_FALSE, MB_FALSE, strlen(MB_FALSE) + 1, true); + bvar->data->type = _DT_INT; + bvar->data->data.integer = 0; + } + + return MB_FUNC_OK; +} + +/* Close the MY-BASIC system */ +int mb_dispose(void) { + if(!_exp_assign) + return MB_FUNC_ERR; + + safe_free(_exp_assign->data.func->name); + safe_free(_exp_assign->data.func); + safe_free(_exp_assign); + _exp_assign = 0; + + mb_assert(_OBJ_BOOL_TRUE); + if(_OBJ_BOOL_TRUE) { + safe_free(_OBJ_BOOL_TRUE->data.variable->data); + safe_free(_OBJ_BOOL_TRUE->data.variable->name); + safe_free(_OBJ_BOOL_TRUE->data.variable); + safe_free(_OBJ_BOOL_TRUE); + _OBJ_BOOL_TRUE = 0; + } + mb_assert(_OBJ_BOOL_FALSE); + if(_OBJ_BOOL_FALSE) { + safe_free(_OBJ_BOOL_FALSE->data.variable->data); + safe_free(_OBJ_BOOL_FALSE->data.variable->name); + safe_free(_OBJ_BOOL_FALSE->data.variable); + safe_free(_OBJ_BOOL_FALSE); + _OBJ_BOOL_FALSE = 0; + } + + return MB_FUNC_OK; +} + +/* Open a MY-BASIC environment */ +int mb_open(struct mb_interpreter_t** s) { + int result = MB_FUNC_OK; + _ht_node_t* local_scope = 0; + _ht_node_t* global_scope = 0; + _running_context_t* running = 0; + + if(!s) + return MB_FUNC_ERR; + + *s = (mb_interpreter_t*)mb_malloc(sizeof(mb_interpreter_t)); + memset(*s, 0, sizeof(mb_interpreter_t)); + (*s)->valid = true; + + local_scope = _ht_create(0, _ht_cmp_string, _ht_hash_string, _ls_free_extra); + (*s)->local_func_dict = local_scope; + + global_scope = _ht_create(0, _ht_cmp_string, _ht_hash_string, _ls_free_extra); + (*s)->global_func_dict = global_scope; + +#ifdef MB_ENABLE_MODULE + global_scope = _ht_create(0, _ht_cmp_string, _ht_hash_string, _ht_destroy_module_func_list); + (*s)->module_func_dict = global_scope; + (*s)->using_modules = _ls_create(); +#endif /* MB_ENABLE_MODULE */ + + (*s)->parsing_context = _reset_parsing_context((*s)->parsing_context); + + (*s)->edge_destroy_objects = _ls_create(); + (*s)->lazy_destroy_objects = _ls_create(); + + (*s)->gc.table = _ht_create(0, _ht_cmp_ref, _ht_hash_ref, _do_nothing_on_object); + (*s)->gc.recursive_table = _ht_create(0, _ht_cmp_ref, _ht_hash_ref, _do_nothing_on_object); + (*s)->gc.collected_table = _ht_create(0, _ht_cmp_ref, _ht_hash_ref, _do_nothing_on_object); + (*s)->gc.valid_table = 0; + (*s)->gc.collecting = 0; + + running = _create_running_context(true); + running->meta = _SCOPE_META_ROOT; + (*s)->running_context = running; + + (*s)->sub_stack = _ls_create(); + + (*s)->in_neg_expr = _ls_create(); + +#ifdef MB_ENABLE_STACK_TRACE + (*s)->stack_frames = _ls_create(); +#endif /* MB_ENABLE_STACK_TRACE */ +#if _MULTILINE_STATEMENT + (*s)->multiline_enabled = _ls_create(); +#endif /* _MULTILINE_STATEMENT */ + + (*s)->ast = _ls_create(); + + _open_core_lib(*s); + _open_std_lib(*s); +#ifdef MB_ENABLE_COLLECTION_LIB + _open_coll_lib(*s); +#endif /* MB_ENABLE_COLLECTION_LIB */ + + result = _open_constant(*s); + mb_assert(MB_FUNC_OK == result); + + return result; +} + +/* Close a MY-BASIC environment */ +int mb_close(struct mb_interpreter_t** s) { + _ht_node_t* local_scope = 0; + _ht_node_t* global_scope = 0; + _ls_node_t* ast = 0; + + if(!s || !(*s)) + return MB_FUNC_ERR; + +#ifdef MB_ENABLE_FORK + if((*s)->forked_from) + return mb_join(s); +#endif /* MB_ENABLE_FORK */ + + (*s)->valid = false; + +#ifdef MB_ENABLE_COLLECTION_LIB + _close_coll_lib(*s); +#endif /* MB_ENABLE_COLLECTION_LIB */ + _close_std_lib(*s); + _close_core_lib(*s); + + ast = (*s)->ast; + _LS_FOREACH(ast, _destroy_object, _try_clear_intermediate_value, *s); + _ls_destroy(ast); + + _ls_destroy((*s)->sub_stack); + + _ls_destroy((*s)->in_neg_expr); + +#ifdef MB_ENABLE_STACK_TRACE + _ls_destroy((*s)->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ +#if _MULTILINE_STATEMENT + _ls_destroy((*s)->multiline_enabled); +#endif /* _MULTILINE_STATEMENT */ + + _tidy_scope_chain(*s); + _dispose_scope_chain(*s); + + (*s)->gc.disabled = false; + _gc_collect_garbage(*s, -1); + _ht_destroy((*s)->gc.table); + _ht_destroy((*s)->gc.recursive_table); + _ht_destroy((*s)->gc.collected_table); + (*s)->gc.table = 0; + (*s)->gc.recursive_table = 0; + (*s)->gc.collected_table = 0; + +#ifdef MB_ENABLE_FORK + if((*s)->all_forked) { + mb_assert(_ls_count((*s)->all_forked) == 0); + _ls_destroy((*s)->all_forked); + } +#endif /* MB_ENABLE_FORK */ + + _ls_foreach((*s)->edge_destroy_objects, _destroy_object); + _ls_destroy((*s)->edge_destroy_objects); + _ls_foreach((*s)->lazy_destroy_objects, _destroy_object); + _ls_destroy((*s)->lazy_destroy_objects); + + _destroy_parsing_context(&(*s)->parsing_context); + +#ifdef MB_ENABLE_MODULE + global_scope = (*s)->module_func_dict; + _ht_foreach(global_scope, _ht_destroy_module_func_list); + _ht_destroy(global_scope); + _ls_foreach((*s)->using_modules, _destroy_memory); + _ls_destroy((*s)->using_modules); +#endif /* MB_ENABLE_MODULE */ + + global_scope = (*s)->global_func_dict; + _ht_foreach(global_scope, _ls_free_extra); + _ht_destroy(global_scope); + + local_scope = (*s)->local_func_dict; + _ht_foreach(local_scope, _ls_free_extra); + _ht_destroy(local_scope); + + _close_constant(*s); + + safe_free(*s); + + return MB_FUNC_OK; +} + +/* Reset a MY-BASIC environment */ +int mb_reset(struct mb_interpreter_t** s, bool_t clear_funcs, bool_t clear_vars) { + int result = MB_FUNC_OK; + _ht_node_t* global_scope = 0; + _ls_node_t* ast; + _running_context_t* running = 0; + + if(!s || !(*s)) + return MB_FUNC_ERR; + + (*s)->valid = false; + + (*s)->run_count = 0; + (*s)->has_run = false; + (*s)->jump_set = _JMP_NIL; +#ifdef MB_ENABLE_CLASS + (*s)->last_instance = 0; + (*s)->calling = false; +#endif /* MB_ENABLE_CLASS */ + (*s)->last_routine = 0; + (*s)->no_eat_comma_mark = 0; + (*s)->handled_error = false; + (*s)->last_error = SE_NO_ERR; + (*s)->last_error_file = 0; + + running = (*s)->running_context; + (*s)->suspent_point = 0; + running->next_loop_var = 0; + memset(&(running->intermediate_value), 0, sizeof(mb_value_t)); + + ast = (*s)->ast; + _LS_FOREACH(ast, _destroy_object, _try_clear_intermediate_value, *s); + _ls_clear(ast); + + _ls_clear((*s)->sub_stack); + +#ifdef MB_ENABLE_STACK_TRACE + _ls_clear((*s)->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ +#if _MULTILINE_STATEMENT + _ls_clear((*s)->multiline_enabled); +#endif /* _MULTILINE_STATEMENT */ + + if(clear_vars) { + _tidy_scope_chain(*s); + _clear_scope_chain(*s); + } + +#ifdef MB_ENABLE_FORK + if((*s)->all_forked) { + mb_assert(_ls_count((*s)->all_forked) == 0); + _ls_clear((*s)->all_forked); + } +#endif /* MB_ENABLE_FORK */ + + (*s)->parsing_context = _reset_parsing_context((*s)->parsing_context); + + if(clear_funcs) { +#ifdef MB_ENABLE_MODULE + global_scope = (*s)->module_func_dict; + _ht_foreach(global_scope, _ht_destroy_module_func_list); + _ht_clear(global_scope); + _ls_foreach((*s)->using_modules, _destroy_memory); + _ls_clear((*s)->using_modules); +#endif /* MB_ENABLE_MODULE */ + + global_scope = (*s)->global_func_dict; + _ht_foreach(global_scope, _ls_free_extra); + _ht_clear(global_scope); + } + + result = _open_constant(*s); + mb_assert(MB_FUNC_OK == result); + + (*s)->valid = true; + + return result; +} + +/* Fork a new MY-BASIC environment */ +int mb_fork(struct mb_interpreter_t** s, struct mb_interpreter_t* r, bool_t clear_forked) { +#ifdef MB_ENABLE_FORK + int result = MB_FUNC_OK; + _running_context_t* running = 0; + + if(!s || !r) + return MB_FUNC_ERR; + + *s = (mb_interpreter_t*)mb_malloc(sizeof(mb_interpreter_t)); + memcpy(*s, r, sizeof(mb_interpreter_t)); + + (*s)->edge_destroy_objects = _ls_create(); + (*s)->lazy_destroy_objects = _ls_create(); + + running = _create_running_context(true); + running->meta = _SCOPE_META_ROOT; + (*s)->forked_context = (*s)->running_context = running; + running->prev = _get_root_scope(r->running_context); + + (*s)->var_args = 0; + + (*s)->sub_stack = _ls_create(); + + (*s)->in_neg_expr = _ls_create(); + +#ifdef MB_ENABLE_STACK_TRACE + (*s)->stack_frames = _ls_create(); +#endif /* MB_ENABLE_STACK_TRACE */ +#if _MULTILINE_STATEMENT + (*s)->multiline_enabled = _ls_create(); +#endif /* _MULTILINE_STATEMENT */ + + (*s)->forked_from = r; + + if(clear_forked) { + if(!r->all_forked) + r->all_forked = _ls_create(); + _ls_pushback(r->all_forked, *s); + } + + mb_assert(MB_FUNC_OK == result); + + return result; +#else /* MB_ENABLE_FORK */ + mb_unrefvar(s); + mb_unrefvar(r); + mb_unrefvar(clear_forked); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_FORK */ +} + +/* Join a forked MY-BASIC environment */ +int mb_join(struct mb_interpreter_t** s) { +#ifdef MB_ENABLE_FORK + int result = MB_FUNC_OK; + mb_interpreter_t* src = 0; + + if(!s || !(*s) || !(*s)->forked_from) + return MB_FUNC_ERR; + + src = *s; + while(mb_get_forked_from(src, &src) == MB_FUNC_OK) { + /* Do nothing */ + } + + (*s)->valid = false; + + _ls_destroy((*s)->sub_stack); + + _ls_destroy((*s)->in_neg_expr); + +#ifdef MB_ENABLE_STACK_TRACE + _ls_destroy((*s)->stack_frames); +#endif /* MB_ENABLE_STACK_TRACE */ +#if _MULTILINE_STATEMENT + _ls_destroy((*s)->multiline_enabled); +#endif /* _MULTILINE_STATEMENT */ + + (*s)->forked_context->prev = 0; + (*s)->running_context = (*s)->forked_context; + _dispose_scope_chain(*s); + + _ls_foreach((*s)->edge_destroy_objects, _destroy_object); + _ls_destroy((*s)->edge_destroy_objects); + _ls_foreach((*s)->lazy_destroy_objects, _destroy_object); + _ls_destroy((*s)->lazy_destroy_objects); + + if(src->all_forked) + _ls_try_remove(src->all_forked, *s, _ls_cmp_data, 0); + + safe_free(*s); + + return result; +#else /* MB_ENABLE_FORK */ + mb_unrefvar(s); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_FORK */ +} + +/* Get the source MY-BASIC environment of a forked one */ +int mb_get_forked_from(struct mb_interpreter_t* s, struct mb_interpreter_t** src) { +#ifdef MB_ENABLE_FORK + int result = MB_FUNC_OK; + + if(!s || !src) + result = MB_FUNC_ERR; + else if(s->forked_from == 0) + result = MB_FUNC_ERR; + else + *src = s->forked_from; + + return result; +#else /* MB_ENABLE_FORK */ + mb_unrefvar(s); + mb_unrefvar(src); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_FORK */ +} + +/* Register an API function to a MY-BASIC environment */ +int mb_register_func(struct mb_interpreter_t* s, const char* n, mb_func_t f) { + if(!s || !n || !f) return 0; + + return _register_func(s, (char*)n, f, false); +} + +/* Remove an API function from a MY-BASIC environment */ +int mb_remove_func(struct mb_interpreter_t* s, const char* n) { + if(!s || !n) return 0; + + return _remove_func(s, (char*)n, false); +} + +/* Remove a reserved API from a MY-BASIC environment */ +int mb_remove_reserved_func(struct mb_interpreter_t* s, const char* n) { + if(!s || !n) return 0; + + return _remove_func(s, (char*)n, true); +} + +/* Begin a module, all functions registered within a module will put inside it */ +int mb_begin_module(struct mb_interpreter_t* s, const char* n) { + int result = MB_FUNC_OK; + + if(!s || !n) { + result = MB_FUNC_ERR; + + goto _exit; + } + +#ifdef MB_ENABLE_MODULE + if(s->with_module) { + _handle_error_on_obj(s, SE_PS_INVALID_MODULE, s->source_file, (_object_t*)0, MB_FUNC_ERR, _exit, result); + } else { + s->with_module = mb_strdup(n, strlen(n) + 1); + } +#else /* MB_ENABLE_MODULE */ + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, (_object_t*)0, MB_FUNC_WARNING, _exit, result); +#endif /* MB_ENABLE_MODULE */ + +_exit: + return result; +} + +/* End a module */ +int mb_end_module(struct mb_interpreter_t* s) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + +#ifdef MB_ENABLE_MODULE + if(s->with_module) { + safe_free(s->with_module); + } else { + _handle_error_on_obj(s, SE_PS_INVALID_MODULE, s->source_file, (_object_t*)0, MB_FUNC_ERR, _exit, result); + } + +#else /* MB_ENABLE_MODULE */ + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, (_object_t*)0, MB_FUNC_WARNING, _exit, result); +#endif /* MB_ENABLE_MODULE */ + +_exit: + return result; +} + +/* Try attempting to begin an API function */ +int mb_attempt_func_begin(struct mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + +#if _MULTILINE_STATEMENT + _ls_pushback(s->multiline_enabled, (void*)(intptr_t)false); +#endif /* _MULTILINE_STATEMENT */ + ast = (_ls_node_t*)*l; + if(!ast) { + result = MB_FUNC_ERR; + + goto _exit; + } + obj = (_object_t*)ast->data; + if(!(obj->type == _DT_FUNC)) { +#if _MULTILINE_STATEMENT + _ls_popback(s->multiline_enabled); +#endif /* _MULTILINE_STATEMENT */ + _handle_error_on_obj(s, SE_RN_INCOMPLETE_STRUCTURE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + + ++s->no_eat_comma_mark; + +_exit: + *l = ast; + + return result; +} + +/* Try attempting to end an API function */ +int mb_attempt_func_end(struct mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + if(!s || !l) { + result = MB_FUNC_ERR; + } else { +#if _MULTILINE_STATEMENT + _ls_popback(s->multiline_enabled); +#endif /* _MULTILINE_STATEMENT */ + --s->no_eat_comma_mark; + } + + return result; +} + +/* Try attempting an open bracket */ +int mb_attempt_open_bracket(struct mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + ast = (_ls_node_t*)*l; +#if _MULTILINE_STATEMENT + _ls_pushback(s->multiline_enabled, (void*)(intptr_t)true); + do { + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; + } while(_IS_EOS(obj)); +#else /* _MULTILINE_STATEMENT */ + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; +#endif /* _MULTILINE_STATEMENT */ + if(!obj || !_IS_FUNC(obj, _core_open_bracket)) { +#if _MULTILINE_STATEMENT + _ls_popback(s->multiline_enabled); +#endif /* _MULTILINE_STATEMENT */ + _handle_error_on_obj(s, SE_RN_OPEN_BRACKET_EXPECTED, s->source_file, ast ? DON(ast) : DON2(l), MB_FUNC_ERR, _exit, result); + } + if(ast) ast = ast->next; + +_exit: + *l = ast; + + return result; +} + +/* Try attempting a close bracket */ +int mb_attempt_close_bracket(struct mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + ast = (_ls_node_t*)*l; + if(!ast) { + _handle_error_on_obj(s, SE_RN_CLOSE_BRACKET_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } +#if _MULTILINE_STATEMENT + _ls_popback(s->multiline_enabled); + obj = (_object_t*)ast->data; + while(_IS_EOS(obj)) { + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; + } +#else /* _MULTILINE_STATEMENT */ + obj = ast ? (_object_t*)ast->data : 0; +#endif /* _MULTILINE_STATEMENT */ + if(!obj || !_IS_FUNC(obj, _core_close_bracket)) { + _handle_error_on_obj(s, SE_RN_CLOSE_BRACKET_EXPECTED, s->source_file, ast ? DON(ast) : DON2(l), MB_FUNC_ERR, _exit, result); + } + if(ast) ast = ast->next; + +_exit: + *l = ast; + + return result; +} + +/* Detect if there is any more argument */ +int mb_has_arg(struct mb_interpreter_t* s, void** l) { + int result = 0; + _ls_node_t* ast = 0; + _object_t* obj = 0; + + if(!s || !l) + goto _exit; + + ast = (_ls_node_t*)*l; + if(ast) { +#if _MULTILINE_STATEMENT + if(_multiline_statement(s)) { + obj = (_object_t*)ast->data; + while(_IS_EOS(obj)) { + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; + } + } else { + obj = (_object_t*)ast->data; + } +#else /* _MULTILINE_STATEMENT */ + obj = ast ? (_object_t*)ast->data : 0; +#endif /* _MULTILINE_STATEMENT */ + if(obj && !_IS_FUNC(obj, _core_close_bracket) && !_IS_EOS(obj)) + result = obj->type != _DT_SEP && obj->type != _DT_EOS; + } + +_exit: + return result; +} + +/* Pop an integer argument */ +int mb_pop_int(struct mb_interpreter_t* s, void** l, int_t* val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(val) *val = 0; + + if(!s || !l || !val) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(arg); + + mb_check(mb_pop_value(s, l, &arg)); + + switch(arg.type) { + case MB_DT_INT: + *val = arg.value.integer; + + break; + case MB_DT_REAL: + *val = (int_t)(arg.value.float_point); + + break; + default: + _assign_public_value(s, &arg, 0, false); +#if _SIMPLE_ARG_ERROR + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#else /* _SIMPLE_ARG_ERROR */ + result = MB_FUNC_ERR; +#endif /* _SIMPLE_ARG_ERROR */ + + goto _exit; + } + +_exit: + return result; +} + +/* Pop a float point argument */ +int mb_pop_real(struct mb_interpreter_t* s, void** l, real_t* val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(val) *val = 0; + + if(!s || !l || !val) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(arg); + + mb_check(mb_pop_value(s, l, &arg)); + + switch(arg.type) { + case MB_DT_INT: + *val = (real_t)(arg.value.integer); + + break; + case MB_DT_REAL: + *val = arg.value.float_point; + + break; + default: + _assign_public_value(s, &arg, 0, false); +#if _SIMPLE_ARG_ERROR + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#else /* _SIMPLE_ARG_ERROR */ + result = MB_FUNC_ERR; +#endif /* _SIMPLE_ARG_ERROR */ + + goto _exit; + } + +_exit: + return result; +} + +/* Pop a string argument */ +int mb_pop_string(struct mb_interpreter_t* s, void** l, char** val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(val) *val = 0; + + if(!s || !l || !val) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(arg); + + mb_check(mb_pop_value(s, l, &arg)); + + switch(arg.type) { + case MB_DT_STRING: + *val = arg.value.string; + + break; + default: + _assign_public_value(s, &arg, 0, false); +#if _SIMPLE_ARG_ERROR + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#else /* _SIMPLE_ARG_ERROR */ + result = MB_FUNC_ERR; +#endif /* _SIMPLE_ARG_ERROR */ + + goto _exit; + } + +_exit: + return result; +} + +/* Pop a usertype argument */ +int mb_pop_usertype(struct mb_interpreter_t* s, void** l, void** val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(val) *val = 0; + + if(!s || !l || !val) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(arg); + + mb_check(mb_pop_value(s, l, &arg)); + + switch(arg.type) { + case MB_DT_USERTYPE: + *val = arg.value.usertype; + + break; + default: + _assign_public_value(s, &arg, 0, false); + result = MB_FUNC_ERR; + + goto _exit; + } + +_exit: + return result; +} + +/* Pop an argument value */ +int mb_pop_value(struct mb_interpreter_t* s, void** l, mb_value_t* val) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t val_obj; + _object_t* val_ptr = 0; + _running_context_t* running = 0; + int* inep = 0; + + if(!s || !l || !val) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + + if(!_ls_empty(s->in_neg_expr)) + inep = (int*)_ls_back(s->in_neg_expr)->data; + + val_ptr = &val_obj; + _MAKE_NIL(val_ptr); + +#ifdef MB_ENABLE_USERTYPE_REF + if(s->usertype_ref_ahead) { + ast = (_ls_node_t*)*l; + memcpy(val_ptr, s->usertype_ref_ahead, sizeof(_object_t)); + s->usertype_ref_ahead = 0; + + goto _got; + } +#endif /* MB_ENABLE_USERTYPE_REF */ + ast = (_ls_node_t*)*l; + if(!ast) + goto _exit; +#if _MULTILINE_STATEMENT + if(_multiline_statement(s)) { + _object_t* obj = 0; + obj = (_object_t*)ast->data; + while(_IS_EOS(obj)) { + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; + } + } +#endif /* _MULTILINE_STATEMENT */ + result = _calc_expression(s, &ast, &val_ptr); + if(result != MB_FUNC_OK) + goto _exit; + +#ifdef MB_ENABLE_USERTYPE_REF +_got: +#endif /* MB_ENABLE_USERTYPE_REF */ + if(val_ptr->type == _DT_STRING && !val_ptr->is_ref) { + _destroy_edge_objects(s); + _mark_edge_destroy_string(s, val_ptr->data.string); + } + _REF(val_ptr) + + if(s->no_eat_comma_mark < _NO_EAT_COMMA && (!inep || (inep && !(*inep)))) { + if(ast && _IS_SEP(ast->data, ',')) + ast = ast->next; + } + + result = _internal_object_to_public_value(val_ptr, val); + if(result != MB_FUNC_OK) + goto _exit; + +_exit: + *l = ast; + + return result; +} + +/* Push an integer argument */ +int mb_push_int(struct mb_interpreter_t* s, void** l, int_t val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_int(arg, val); + mb_check(mb_push_value(s, l, arg)); + +_exit: + return result; +} + +/* Push a float point argument */ +int mb_push_real(struct mb_interpreter_t* s, void** l, real_t val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_real(arg, val); + mb_convert_to_int_if_posible(arg); + mb_check(mb_push_value(s, l, arg)); + +_exit: + return result; +} + +/* Push a string argument */ +int mb_push_string(struct mb_interpreter_t* s, void** l, char* val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_string(arg, val); + mb_check(mb_push_value(s, l, arg)); + _mark_lazy_destroy_string(s, val); + +_exit: + return result; +} + +/* Push a usertype argument */ +int mb_push_usertype(struct mb_interpreter_t* s, void** l, void* val) { + int result = MB_FUNC_OK; + mb_value_t arg; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_usertype(arg, val); + mb_check(mb_push_value(s, l, arg)); + +_exit: + return result; +} + +/* Push an argument value */ +int mb_push_value(struct mb_interpreter_t* s, void** l, mb_value_t val) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + _object_t obj; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + _assign_public_value(s, &running->intermediate_value, &val, false); + + _MAKE_NIL(&obj); + _public_value_to_internal_object(&running->intermediate_value, &obj); + _REF(&obj) + + _gc_try_trigger(s); + +_exit: + return result; +} + +/* Push an argument value, and specify whether it's managed by the interpreter */ +int mb_push_managed_value(struct mb_interpreter_t* s, void** l, mb_value_t val, bool_t managed) { + const int result = mb_push_value(s, l, val); + _running_context_t* running = 0; + + if(result != MB_FUNC_OK) + goto _exit; + + running = s->running_context; + running->donot_ref_intermediate_value = managed; + +_exit: + return result; +} + +/* Begin a class */ +int mb_begin_class(struct mb_interpreter_t* s, void** l, const char* n, mb_value_t** meta, int c, mb_value_t* out) { +#ifdef MB_ENABLE_CLASS + int result = MB_FUNC_OK; + _class_t* instance = 0; + _object_t* obj = 0; + _running_context_t* running = 0; + _ls_node_t* tmp = 0; + _var_t* var = 0; + int i = 0; + _object_t mo; + _class_t* mi = 0; + + if(!s || !l || !n || !out) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + + tmp = (_ls_node_t*)*l; + + _using_jump_set_of_structured(s, tmp, _exit, result); + + tmp = _search_identifier_in_scope_chain(s, 0, n, _PATHING_NONE, 0, 0); + if(tmp && tmp->data) { + obj = (_object_t*)tmp->data; + if(_IS_VAR(obj)) + var = obj->data.variable; + } + if(s->last_instance || (obj && !var)) { + _handle_error_on_obj(s, SE_RN_DUPLICATE_CLASS, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + obj = _create_object(); + obj->type = _DT_CLASS; + + instance = (_class_t*)mb_malloc(sizeof(_class_t)); + _init_class(s, instance, mb_strdup(n, strlen(n) + 1)); + + for(i = 0; i < c; i++) { + if(meta[i]->type != MB_DT_CLASS) { + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + _MAKE_NIL(&mo); + _public_value_to_internal_object(meta[i], &mo); + mi = mo.data.instance; + _link_meta_class(s, instance, mi); + } + + _push_scope_by_class(s, instance->scope); + obj->data.instance = instance; + + if(var) { + _destroy_object(var->data, 0); + var->data = obj; + } else { + _ht_set_or_insert(running->var_dict, (void*)n, obj); + } + + s->last_instance = instance; + + if(out) { + out->type = MB_DT_CLASS; + out->value.instance = instance; + } + +_exit: + return result; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(s); + mb_unrefvar(l); + mb_unrefvar(n); + mb_unrefvar(meta); + mb_unrefvar(c); + mb_unrefvar(out); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_CLASS */ +} + +/* End a class */ +int mb_end_class(struct mb_interpreter_t* s, void** l) { +#ifdef MB_ENABLE_CLASS + int result = MB_FUNC_OK; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + _pop_scope(s, false); + + s->last_instance = 0; + +_exit: + return result; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(s); + mb_unrefvar(l); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_CLASS */ +} + +/* Get the userdata of a class instance */ +int mb_get_class_userdata(struct mb_interpreter_t* s, void** l, void** d) { +#ifdef MB_ENABLE_CLASS + int result = MB_FUNC_OK; + + if(!s || !d) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(s && s->last_instance) { + if(d) + *d = s->last_instance->userdata; + } else if(s && s->last_routine && s->last_routine->instance) { + if(d) + *d = s->last_routine->instance->userdata; + } else { + if(d) *d = 0; + + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + +_exit: + return result; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(s); + mb_unrefvar(l); + mb_unrefvar(d); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_CLASS */ +} + +/* Set the userdata of a class instance */ +int mb_set_class_userdata(struct mb_interpreter_t* s, void** l, void* d) { +#ifdef MB_ENABLE_CLASS + int result = MB_FUNC_OK; + + if(!s || !d) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(s && s->last_instance) { + s->last_instance->userdata = d; + } else { + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + +_exit: + return result; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(s); + mb_unrefvar(l); + mb_unrefvar(d); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_CLASS */ +} + +/* Get a value by its identifier name */ +int mb_get_value_by_name(struct mb_interpreter_t* s, void** l, const char* n, mb_value_t* val) { + int result = MB_FUNC_OK; + _ls_node_t* tmp = 0; + _object_t* obj = 0; + mb_unrefvar(l); + + if(!s || !n) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(*val); + + tmp = _search_identifier_in_scope_chain(s, 0, n, _PATHING_NORMAL, 0, 0); + if(tmp && tmp->data) { + obj = (_object_t*)tmp->data; + _internal_object_to_public_value(obj, val); + } + +_exit: + return result; +} + +/* Retrieve all variables at the current frame */ +int mb_get_vars(struct mb_interpreter_t* s, void** l, mb_var_retrieving_func_t r, int stack_offset) { + int result = 0; + _running_context_t* running = 0; + _tuple3_t tuple; + mb_unrefvar(l); + + if(!s) + goto _exit; + + running = s->running_context; + if(stack_offset == -1) { + running = _get_root_scope(running); + } else { + while(stack_offset > 0 && running) { + running = running->prev; + --stack_offset; + } + } + if(!running) + goto _exit; + tuple.e1 = s; + tuple.e2 = (void*)(uintptr_t)r; + tuple.e3 = &result; + _HT_FOREACH(running->var_dict, _do_nothing_on_object, _retrieve_var, &tuple); + +_exit: + return result; +} + +/* Add a variable with a specific name */ +int mb_add_var(struct mb_interpreter_t* s, void** l, const char* n, mb_value_t val, bool_t force) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + _object_t* obj = 0; + _var_t* var = 0; + _ls_node_t* tmp = 0; + + if(!s || !n) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + + tmp = _ht_find(running->var_dict, (void*)n); + + if(tmp) { + if(force) { + result = mb_set_var_value(s, tmp->data, val); + + goto _exit; + } else { + _handle_error_on_obj(s, SE_RN_DUPLICATE_ID, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + + var = _create_var(&obj, n, strlen(n) + 1, true); + _public_value_to_internal_object(&val, var->data); + + _ht_set_or_insert(running->var_dict, var->name, obj); + +_exit: + return result; +} + +/* Get a token literally, store it in an argument if it's a variable */ +int mb_get_var(struct mb_interpreter_t* s, void** l, void** v, bool_t redir) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(v) *v = 0; + + ast = (_ls_node_t*)*l; + if(ast) { + obj = (_object_t*)ast->data; + if(_IS_SEP(obj, ',')) { + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; + } + if(ast) ast = ast->next; + } + + if(_IS_VAR(obj)) { +#ifdef MB_ENABLE_CLASS + if(redir && obj->data.variable->pathing) + _search_var_in_scope_chain(s, obj->data.variable, &obj); +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(redir); +#endif /* MB_ENABLE_CLASS */ + if(v) + *v = obj; + } + + if(ast && _IS_SEP(ast->data, ',')) + ast = ast->next; + + *l = ast; + +_exit: + return result; +} + +/* Get the name of a variable */ +int mb_get_var_name(struct mb_interpreter_t* s, void* v, char** n) { + int result = MB_FUNC_OK; + _object_t* obj = 0; + + if(n) *n = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(!n || !v) + goto _exit; + + obj = (_object_t*)v; + if(!_IS_VAR(obj)) + goto _exit; + + if(n) *n = obj->data.variable->name; + +_exit: + return result; +} + +/* Get the value of a variable */ +int mb_get_var_value(struct mb_interpreter_t* s, void* v, mb_value_t* val) { + int result = MB_FUNC_OK; + _object_t* obj = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(!val || !v) + goto _exit; + + obj = (_object_t*)v; + if(!_IS_VAR(obj)) + goto _exit; + + _internal_object_to_public_value(obj->data.variable->data, val); + +_exit: + return result; +} + +/* Set the value of a variable */ +int mb_set_var_value(struct mb_interpreter_t* s, void* v, mb_value_t val) { + int result = MB_FUNC_OK; + _object_t* obj = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(!v) + goto _exit; + obj = (_object_t*)v; + if(!_IS_VAR(obj)) + goto _exit; + + _public_value_to_internal_object(&val, obj->data.variable->data); + +_exit: + return result; +} + +/* Create an array */ +int mb_init_array(struct mb_interpreter_t* s, void** l, mb_data_e t, int* d, int c, void** a) { + int result = MB_FUNC_OK; + _array_t* arr = 0; + _data_e type = _DT_NIL; + int j = 0; + int n = 0; + + if(!s || !l || !d || !a) { + result = MB_FUNC_ERR; + + goto _exit; + } + + *a = 0; + if(c > MB_MAX_DIMENSION_COUNT) { + _handle_error_on_obj(s, SE_RN_TOO_MANY_DIMENSIONS, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + for(j = 0; j < c; j++) { + n = d[j]; + if(n <= 0) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + +#ifdef MB_SIMPLE_ARRAY + if(t == MB_DT_REAL) { + type = _DT_REAL; + } else if(t == MB_DT_STRING) { + type = _DT_STRING; + } else { + _handle_error_on_obj(s, SE_RN_UNEXPECTED_TYPE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } +#else /* MB_SIMPLE_ARRAY */ + mb_unrefvar(t); + type = _DT_REAL; +#endif /* MB_SIMPLE_ARRAY */ + + arr = _create_array(s, 0, type); + for(j = 0; j < c; j++) { + n = d[j]; + arr->dimensions[arr->dimension_count++] = n; + if(arr->count) + arr->count *= (unsigned)n; + else + arr->count += (unsigned)n; + } + _init_array(arr); + if(!arr->raw) { + arr->dimension_count = 0; + arr->dimensions[0] = 0; + arr->count = 0; + } + *a = arr; + +_exit: + return result; +} + +/* Get the length of an array */ +int mb_get_array_len(struct mb_interpreter_t* s, void** l, void* a, int r, int* i) { + int result = 0; + _array_t* arr = 0; + + if(!s || !l) + goto _exit; + + arr = (_array_t*)a; + if(r < 0 || r >= arr->dimension_count) { + _handle_error_on_obj(s, SE_RN_RANK_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + if(i) + *i = arr->dimensions[r]; + +_exit: + return result; +} + +/* Get an element of an array with a specific index */ +int mb_get_array_elem(struct mb_interpreter_t* s, void** l, void* a, int* d, int c, mb_value_t* val) { + int result = MB_FUNC_OK; + _array_t* arr = 0; + int index = 0; + _data_e type = _DT_NIL; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + arr = (_array_t*)a; + if(c < 0 || c > arr->dimension_count) { + _handle_error_on_obj(s, SE_RN_TOO_MANY_DIMENSIONS, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + if(!val) + goto _exit; + + index = _get_array_pos(s, arr, d, c); + if(index < 0) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + _get_array_elem(s, arr, index, &val->value, &type); + val->type = _internal_type_to_public_type(type); + +_exit: + return result; +} + +/* Set an element of an array with a specific index */ +int mb_set_array_elem(struct mb_interpreter_t* s, void** l, void* a, int* d, int c, mb_value_t val) { + int result = MB_FUNC_OK; + _array_t* arr = 0; + int index = 0; + _data_e type = _DT_NIL; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + arr = (_array_t*)a; + if(c < 0 || c > arr->dimension_count) { + _handle_error_on_obj(s, SE_RN_TOO_MANY_DIMENSIONS, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + index = _get_array_pos(s, arr, d, c); + if(index < 0) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + type = _public_type_to_internal_type(val.type); + _set_array_elem(s, 0, arr, (unsigned)index, &val.value, &type); + +_exit: + return result; +} + +/* Initialize a collection */ +int mb_init_coll(struct mb_interpreter_t* s, void** l, mb_value_t* coll) { + int result = MB_FUNC_OK; + + if(!s || !coll) { + result = MB_FUNC_ERR; + + goto _exit; + } + +#ifdef MB_ENABLE_COLLECTION_LIB + switch(coll->type) { + case MB_DT_LIST: + coll->value.list = _create_list(s); + + break; + case MB_DT_DICT: + coll->value.dict = _create_dict(s); + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(coll); + + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +_exit: + return result; +} + +/* Get an element of a collection */ +int mb_get_coll(struct mb_interpreter_t* s, void** l, mb_value_t coll, mb_value_t idx, mb_value_t* val) { + int result = MB_FUNC_OK; + _object_t ocoll; + int_t i = 0; + mb_value_t ret; + + mb_make_nil(ret); + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + _MAKE_NIL(&ocoll); +#ifdef MB_ENABLE_COLLECTION_LIB + switch(coll.type) { + case MB_DT_LIST: + mb_int_val(idx, i); + _public_value_to_internal_object(&coll, &ocoll); + if(!_at_list(ocoll.data.list, i, &ret)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + if(!_find_dict(ocoll.data.dict, &idx, &ret)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(idx); + mb_unrefvar(coll); + mb_unrefvar(i); + + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +_exit: + if(val) *val = ret; + + return result; +} + +/* Set an element of a collection */ +int mb_set_coll(struct mb_interpreter_t* s, void** l, mb_value_t coll, mb_value_t idx, mb_value_t val) { + int result = MB_FUNC_OK; + _object_t ocoll; + int_t i = 0; + _object_t* oval = 0; + mb_value_t ret; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(ret); + + _MAKE_NIL(&ocoll); +#ifdef MB_ENABLE_COLLECTION_LIB + switch(coll.type) { + case MB_DT_LIST: + mb_int_val(idx, i); + _public_value_to_internal_object(&coll, &ocoll); + while((int)ocoll.data.list->count <= i) + _push_list(ocoll.data.list, &ret, 0); + if(!_set_list(ocoll.data.list, i, &val, &oval)) { + if(oval) + _destroy_object(oval, 0); + + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + _set_dict(ocoll.data.dict, &idx, &val, 0, 0); + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(val); + mb_unrefvar(idx); + mb_unrefvar(coll); + mb_unrefvar(oval); + mb_unrefvar(i); + + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +_exit: + return result; +} + +/* Remove an element from a collection */ +int mb_remove_coll(struct mb_interpreter_t* s, void** l, mb_value_t coll, mb_value_t idx) { + int result = MB_FUNC_OK; + _object_t ocoll; + int_t i = 0; + mb_value_t ret; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(ret); + + _MAKE_NIL(&ocoll); +#ifdef MB_ENABLE_COLLECTION_LIB + switch(coll.type) { + case MB_DT_LIST: + mb_int_val(idx, i); + _public_value_to_internal_object(&coll, &ocoll); + if(!_remove_at_list(ocoll.data.list, i)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + if(!_remove_dict(ocoll.data.dict, &idx)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(coll); + mb_unrefvar(idx); + mb_unrefvar(i); + + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +_exit: + return result; +} + +/* Tell the element count of a collection */ +int mb_count_coll(struct mb_interpreter_t* s, void** l, mb_value_t coll, int* c) { + int result = MB_FUNC_OK; + _object_t ocoll; +#ifdef MB_ENABLE_COLLECTION_LIB + _list_t* lst = 0; + _dict_t* dct = 0; +#endif /* MB_ENABLE_COLLECTION_LIB */ + int ret = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + _MAKE_NIL(&ocoll); +#ifdef MB_ENABLE_COLLECTION_LIB + switch(coll.type) { + case MB_DT_LIST: + lst = (_list_t*)coll.value.list; + ret = (int)lst->count; + + break; + case MB_DT_DICT: + dct = (_dict_t*)coll.value.dict; + ret = (int)_ht_count(dct->dict); + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(coll); + + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +_exit: + if(c) *c = ret; + + return result; +} + +/* Get all keys of a collection */ +int mb_keys_of_coll(struct mb_interpreter_t* s, void** l, mb_value_t coll, mb_value_t* keys, int c) { + int result = MB_FUNC_OK; + _object_t ocoll; +#ifdef MB_ENABLE_COLLECTION_LIB + _list_t* lst = 0; + _dict_t* dct = 0; + int i = 0; + _keys_helper_t helper; +#endif /* MB_ENABLE_COLLECTION_LIB */ + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + _MAKE_NIL(&ocoll); +#ifdef MB_ENABLE_COLLECTION_LIB + switch(coll.type) { + case MB_DT_LIST: + lst = (_list_t*)coll.value.list; + for(i = 0; i < c && i < (int)lst->count; ++i) { + mb_make_int(keys[i], i); + } + + break; + case MB_DT_DICT: + dct = (_dict_t*)coll.value.dict; + helper.keys = keys; + helper.size = c; + helper.index = 0; + _HT_FOREACH(dct->dict, _do_nothing_on_object, _copy_keys_to_value_array, &helper); + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(coll); + mb_unrefvar(keys); + mb_unrefvar(c); + + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +_exit: + return result; +} + +/* Create a referenced usertype value */ +int mb_make_ref_value(struct mb_interpreter_t* s, void* val, mb_value_t* out, mb_dtor_func_t un, mb_clone_func_t cl, mb_hash_func_t hs, mb_cmp_func_t cp, mb_fmt_func_t ft) { +#ifdef MB_ENABLE_USERTYPE_REF + int result = MB_FUNC_OK; + _usertype_ref_t* ref = 0; + + if(!s || !out) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(out) { + ref = _create_usertype_ref(s, val, un, cl, hs, cp, ft); + out->type = MB_DT_USERTYPE_REF; + out->value.usertype_ref = ref; + } + +_exit: + return result; +#else /* MB_ENABLE_USERTYPE_REF */ + mb_unrefvar(s); + mb_unrefvar(val); + mb_unrefvar(out); + mb_unrefvar(un); + mb_unrefvar(cl); + mb_unrefvar(hs); + mb_unrefvar(cp); + mb_unrefvar(ft); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_USERTYPE_REF */ +} + +/* Get the data of a referenced usertype value */ +int mb_get_ref_value(struct mb_interpreter_t* s, void** l, mb_value_t val, void** out) { +#ifdef MB_ENABLE_USERTYPE_REF + int result = MB_FUNC_OK; + _usertype_ref_t* ref = 0; + + if(!s || !out) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(val.type != MB_DT_USERTYPE_REF) { + _handle_error_on_obj(s, SE_RN_UNEXPECTED_TYPE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + if(out) { + ref = (_usertype_ref_t*)val.value.usertype_ref; + *out = ref->usertype; + } + +_exit: + return result; +#else /* MB_ENABLE_USERTYPE_REF */ + mb_unrefvar(s); + mb_unrefvar(l); + mb_unrefvar(val); + mb_unrefvar(out); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_USERTYPE_REF */ +} + +/* Increase the reference of a value by 1 */ +int mb_ref_value(struct mb_interpreter_t* s, void** l, mb_value_t val) { + int result = MB_FUNC_OK; + _object_t obj; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + +#ifdef MB_ENABLE_COLLECTION_LIB + if(val.type == MB_DT_LIST_IT || val.type == MB_DT_DICT_IT) + goto _exit; +#endif /* MB_ENABLE_COLLECTION_LIB */ + + _MAKE_NIL(&obj); + _public_value_to_internal_object(&val, &obj); + if( +#ifdef MB_ENABLE_USERTYPE_REF + obj.type != _DT_USERTYPE_REF && +#endif /* MB_ENABLE_USERTYPE_REF */ + obj.type != _DT_ARRAY && +#ifdef MB_ENABLE_COLLECTION_LIB + obj.type != _DT_LIST && obj.type != _DT_DICT && +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + obj.type != _DT_CLASS && +#endif /* MB_ENABLE_CLASS */ + obj.type != _DT_ROUTINE + ) { + _handle_error_on_obj(s, SE_RN_REFERENCED_TYPE_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + _REF(&obj) + +_exit: + return result; +} + +/* Decrease the reference of a value by 1 */ +int mb_unref_value(struct mb_interpreter_t* s, void** l, mb_value_t val) { + int result = MB_FUNC_OK; + _object_t obj; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + +#ifdef MB_ENABLE_COLLECTION_LIB + if(_try_purge_it(s, &val, 0)) + goto _exit; +#endif /* MB_ENABLE_COLLECTION_LIB */ + + _MAKE_NIL(&obj); + _public_value_to_internal_object(&val, &obj); + if( +#ifdef MB_ENABLE_USERTYPE_REF + obj.type != _DT_USERTYPE_REF && +#endif /* MB_ENABLE_USERTYPE_REF */ + obj.type != _DT_ARRAY && +#ifdef MB_ENABLE_COLLECTION_LIB + obj.type != _DT_LIST && obj.type != _DT_DICT && +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + obj.type != _DT_CLASS && +#endif /* MB_ENABLE_CLASS */ + obj.type != _DT_ROUTINE + ) { + _handle_error_on_obj(s, SE_RN_REFERENCED_TYPE_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + _UNREF(&obj) + +_exit: + return result; +} + +/* Set the global alive checker */ +int mb_set_alive_checker(struct mb_interpreter_t* s, mb_alive_checker_t f) { + int result = MB_FUNC_OK; + + if(!s) + result = MB_FUNC_ERR; + else + s->alive_check_handler = f; + + return result; +} + +/* Set the alive checker of a value */ +int mb_set_alive_checker_of_value(struct mb_interpreter_t* s, void** l, mb_value_t val, mb_alive_value_checker_t f) { +#ifdef MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF + int result = MB_FUNC_OK; + _object_t obj; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(val.type != MB_DT_USERTYPE_REF) { + _handle_error_on_obj(s, SE_RN_REFERENCED_TYPE_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + _MAKE_NIL(&obj); + _public_value_to_internal_object(&val, &obj); + obj.data.usertype_ref->alive_checker = f; + +_exit: + return result; +#else /* MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF */ + mb_unrefvar(s); + mb_unrefvar(l); + mb_unrefvar(val); + mb_unrefvar(f); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_ALIVE_CHECKING_ON_USERTYPE_REF */ +} + +/* Override a meta function of a value */ +int mb_override_value(struct mb_interpreter_t* s, void** l, mb_value_t val, mb_meta_func_e m, void* f) { + int result = MB_FUNC_OK; + _object_t obj; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + +#ifdef MB_ENABLE_USERTYPE_REF + _MAKE_NIL(&obj); + if(val.type == MB_DT_USERTYPE_REF) { + _usertype_ref_t* user = 0; + _public_value_to_internal_object(&val, &obj); + user = obj.data.usertype_ref; + if(m & MB_MF_CALC) { + if(!user->calc_operators) { + user->calc_operators = (_calculation_operator_info_t*)mb_malloc(sizeof(_calculation_operator_info_t)); + memset(user->calc_operators, 0, sizeof(_calculation_operator_info_t)); + } + } + switch(m) { + case MB_MF_IS: + user->calc_operators->is = (mb_meta_operator_t)(intptr_t)f; + + break; + case MB_MF_ADD: + user->calc_operators->add = (mb_meta_operator_t)(intptr_t)f; + + break; + case MB_MF_SUB: + user->calc_operators->sub = (mb_meta_operator_t)(intptr_t)f; + + break; + case MB_MF_MUL: + user->calc_operators->mul = (mb_meta_operator_t)(intptr_t)f; + + break; + case MB_MF_DIV: + user->calc_operators->div = (mb_meta_operator_t)(intptr_t)f; + + break; + case MB_MF_NEG: + user->calc_operators->neg = (mb_meta_operator_t)(intptr_t)f; + + break; + case MB_MF_COLL: + user->coll_func = (mb_meta_func_t)(intptr_t)f; + + break; + case MB_MF_FUNC: + user->generic_func = (mb_meta_func_t)(intptr_t)f; + + break; + default: /* Do nothing */ + break; + } + } else { + result = MB_FUNC_ERR; + } +#else /* MB_ENABLE_USERTYPE_REF */ + mb_unrefvar(obj); + mb_unrefvar(val); + mb_unrefvar(m); + mb_unrefvar(f); +#endif /* MB_ENABLE_USERTYPE_REF */ + +_exit: + return result; +} + +/* Dispose a value */ +int mb_dispose_value(struct mb_interpreter_t* s, mb_value_t val) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(val.type == MB_DT_STRING) { + safe_free(val.value.string); + } + + _assign_public_value(s, &val, 0, false); + +_exit: + return result; +} + +/* Get a sub routine with a specific name */ +int mb_get_routine(struct mb_interpreter_t* s, void** l, const char* n, mb_value_t* val) { + int result = MB_FUNC_OK; + _object_t* obj = 0; + _ls_node_t* scp = 0; + + if(!s || !n || !val) { + result = MB_FUNC_ERR; + + goto _exit; + } + + mb_make_nil(*val); + + scp = _search_identifier_in_scope_chain(s, 0, n, _PATHING_NONE, 0, 0); + if(scp) { + obj = (_object_t*)scp->data; + if(obj) { + if(obj->type == _DT_ROUTINE) { + _internal_object_to_public_value(obj, val); + } else { + _handle_error_on_obj(s, SE_RN_ROUTINE_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + } + +_exit: + return result; +} + +/* Set a sub routine with a specific name and native function pointer */ +int mb_set_routine(struct mb_interpreter_t* s, void** l, const char* n, mb_routine_func_t f, bool_t force) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + _object_t* obj = 0; + _routine_t* routine = 0; + _ls_node_t* tmp = 0; + _var_t* var = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + + tmp = _ht_find(running->var_dict, (void*)n); + + if(tmp) { + if(force) { + obj = (_object_t*)tmp->data; + if(_IS_VAR(obj)) + var = obj->data.variable; + } else { + _handle_error_on_obj(s, SE_RN_DUPLICATE_ROUTINE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + + routine = (_routine_t*)mb_malloc(sizeof(_routine_t)); + _init_routine(s, routine, mb_strdup(n, strlen(n) + 1), f); + + obj = _create_object(); + obj->type = _DT_ROUTINE; + obj->data.routine = routine; + obj->is_ref = false; + +#ifdef MB_ENABLE_CLASS + routine->instance = s->last_instance; +#endif /* MB_ENABLE_CLASS */ + + if(var && force) { + _destroy_object(var->data, 0); + var->data = obj; + } else { + _ht_set_or_insert(running->var_dict, routine->name, obj); + } + +_exit: + return result; +} + +/* Evaluate a sub routine */ +int mb_eval_routine(struct mb_interpreter_t* s, void** l, mb_value_t val, mb_value_t* args, unsigned argc, mb_value_t* ret) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + _object_t obj; + _ls_node_t* ast = 0; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + + if(val.type != MB_DT_ROUTINE) { + _handle_error_on_obj(s, SE_RN_ROUTINE_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + _MAKE_NIL(&obj); + _public_value_to_internal_object(&val, &obj); + ast = (_ls_node_t*)(*l); + result = _eval_routine(s, &ast, args, argc, obj.data.routine, _has_routine_fun_arg, _pop_routine_fun_arg); + + if(ret) { + _assign_public_value(s, ret, &running->intermediate_value, false); + _MAKE_NIL(&obj); + _public_value_to_internal_object(ret, &obj); + _ADDGC(&obj, &s->gc, false) + } + +_exit: + return result; +} + +/* Get the type of a routine */ +int mb_get_routine_type(struct mb_interpreter_t* s, mb_value_t val, mb_routine_type_e* y) { + int result = MB_FUNC_OK; + _object_t obj; + mb_unrefvar(s); + + if(!y) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(val.type != MB_DT_ROUTINE) { + result = MB_FUNC_ERR; + + goto _exit; + } + + _MAKE_NIL(&obj); + _public_value_to_internal_object(&val, &obj); + *y = obj.data.routine->type; + +_exit: + return result; +} + +/* Load and parse a script string */ +int mb_load_string(struct mb_interpreter_t* s, const char* l, bool_t reset) { + int result = MB_FUNC_OK; + unsigned short _row = 0; + unsigned short _col = 0; + char wrapped = _ZERO_CHAR; + _parsing_context_t* context = 0; + + if(!s || !l) { + result = MB_FUNC_ERR; + + goto _exit; + } + + s->run_count = 0; + s->last_error = SE_NO_ERR; + s->last_error_file = 0; + + if(!s->parsing_context) + s->parsing_context = _reset_parsing_context(s->parsing_context); + + context = s->parsing_context; + + mb_uu_getbom(&l); + while(*l) { + int n = 1; +#ifdef MB_ENABLE_UNICODE_ID + if(context->parsing_state == _PS_NORMAL) + n = mb_uu_ischar(l); +#endif /* MB_ENABLE_UNICODE_ID */ + do { + if(n == 1) { + char ch = *l; + if((ch == _NEWLINE_CHAR || ch == _RETURN_CHAR) && (!wrapped || wrapped == ch)) { + unsigned short before = 0; + wrapped = ch; + before = context->parsing_row++; + context->parsing_col = 0; + if(before > context->parsing_row) { + context->parsing_col = 1; + _handle_error_now(s, SE_RN_PROGRAM_TOO_LONG, s->last_error_file, MB_FUNC_ERR); + + goto _exit; + } + + break; + } + } + wrapped = _ZERO_CHAR; + ++context->parsing_col; + } while(0); + result = _parse_char(s, l, n, context->parsing_pos, _row, _col); + if(result != MB_FUNC_OK) { + _set_error_pos(s, context->parsing_pos, _row, _col); + _handle_error_now(s, s->last_error, s->last_error_file, result); + + goto _exit; + } + _row = context->parsing_row; + _col = context->parsing_col; + ++context->parsing_pos; + l += n; + }; + result = _parse_char(s, 0, 1, context->parsing_pos, context->parsing_row, context->parsing_col); + +_exit: + if(reset) + _end_of_file(context); + + return result; +} + +/* Load and parse a script file */ +int mb_load_file(struct mb_interpreter_t* s, const char* f) { + int result = MB_FUNC_OK; + char* buf = 0; + _parsing_context_t* context = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + s->parsing_context = context = _reset_parsing_context(s->parsing_context); + + buf = _load_file(s, f, 0, false); + if(buf) { + result = mb_load_string(s, buf, true); + safe_free(buf); + + if(result) + goto _exit; + } else { + _set_current_error(s, SE_PS_FAILED_TO_OPEN_FILE, 0); + + result = MB_FUNC_ERR; + } + +_exit: + if(context) + context->parsing_state = _PS_NORMAL; + + return result; +} + +/* Run the current AST */ +int mb_run(struct mb_interpreter_t* s, bool_t clear_parser) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + ++s->run_count; + + if(s->parsing_context) { + if(s->parsing_context->routine_state) { + s->parsing_context->routine_state = 0; + result = MB_FUNC_ERR; + _handle_error_now(s, SE_RN_INCOMPLETE_STRUCTURE, s->source_file, result); + _tidy_scope_chain(s); + + goto _exit; + } +#ifdef MB_ENABLE_CLASS + if(s->parsing_context->class_state != _CLASS_STATE_NONE) { + s->parsing_context->class_state = _CLASS_STATE_NONE; + result = MB_FUNC_ERR; + _handle_error_now(s, SE_RN_INCOMPLETE_STRUCTURE, s->source_file, result); + _tidy_scope_chain(s); + + goto _exit; + } +#endif /* MB_ENABLE_CLASS */ + } + + if(clear_parser) + _destroy_parsing_context(&s->parsing_context); + + s->handled_error = false; + + if(s->suspent_point) { + ast = s->suspent_point; + ast = ast->next; + s->suspent_point = 0; + } else { + s->source_file = 0; +#ifdef MB_ENABLE_CLASS + s->last_instance = 0; + s->calling = false; +#endif /* MB_ENABLE_CLASS */ + s->last_routine = 0; + s->last_error = SE_NO_ERR; + s->last_error_file = 0; + +#if _MULTILINE_STATEMENT + _ls_clear(s->multiline_enabled); +#endif /* _MULTILINE_STATEMENT */ + + mb_assert(!s->no_eat_comma_mark); + while(s->running_context->prev) + s->running_context = s->running_context->prev; + ast = s->ast; + ast = ast->next; + if(!ast) { + result = MB_FUNC_ERR; + _set_error_pos(s, 0, 0, 0); + _handle_error_now(s, SE_RN_EMPTY_PROGRAM, s->source_file, result); + + goto _exit; + } + } + + do { + _ls_node_t* p = ast; + result = _execute_statement(s, &ast, true); + if(ast == p) { + _handle_error_now(s, SE_RN_INVALID_EXPRESSION, s->last_error_file, result); + + goto _exit; + } + if(result != MB_FUNC_OK && result != MB_SUB_RETURN) { + if(result != MB_FUNC_SUSPEND) { + if(result >= MB_EXTENDED_ABORT) + s->last_error = SE_EA_EXTENDED_ABORT; + _handle_error_now(s, s->last_error, s->last_error_file, result); + } + + goto _exit; + } + } while(ast); + +_exit: + if(s) { + if(!s->suspent_point) + s->source_file = 0; + if(clear_parser) + _destroy_parsing_context(&s->parsing_context); + + _destroy_edge_objects(s); + + s->has_run = true; + } + + return result; +} + +/* Suspend current execution and save the context */ +int mb_suspend(struct mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + + if(!s || !l || !(*l)) { + result = MB_FUNC_ERR; + + goto _exit; + } + + ast = (_ls_node_t*)*l; + s->suspent_point = ast; + +_exit: + return result; +} + +/* Schedule to suspend current execution */ +int mb_schedule_suspend(struct mb_interpreter_t* s, int t) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(t == MB_FUNC_OK) + t = MB_FUNC_SUSPEND; + s->schedule_suspend_tag = t; + +_exit: + return result; +} + +/* Get the value of an identifier */ +int mb_debug_get(struct mb_interpreter_t* s, const char* n, mb_value_t* val) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + _ls_node_t* v = 0; + _object_t* obj = 0; + mb_value_t tmp; + + if(!s || !n) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + + v = _search_identifier_in_scope_chain(s, 0, n, _PATHING_NONE, 0, 0); + if(v) { + obj = (_object_t*)v->data; + mb_assert(_IS_VAR(obj)); + if(val) + result = _internal_object_to_public_value(obj->data.variable->data, val); + else + result = _internal_object_to_public_value(obj->data.variable->data, &tmp); + } else { + if(val) { + mb_make_nil(*val); + } + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, (_object_t*)0, MB_FUNC_ERR, _exit, result); + } + +_exit: + return result; +} + +/* Set the value of an identifier */ +int mb_debug_set(struct mb_interpreter_t* s, const char* n, mb_value_t val) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + _ls_node_t* v = 0; + _object_t* obj = 0; + + if(!s || !n) { + result = MB_FUNC_ERR; + + goto _exit; + } + + running = s->running_context; + + v = _search_identifier_in_scope_chain(s, 0, n, _PATHING_NONE, 0, 0); + if(v) { + obj = (_object_t*)v->data; + mb_assert(_IS_VAR(obj)); + result = _public_value_to_internal_object(&val, obj->data.variable->data); + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, (_object_t*)0, MB_FUNC_ERR, _exit, result); + } + +_exit: + return result; +} + +/* Get stack frame count of a MY-BASIC environment */ +int mb_debug_count_stack_frames(struct mb_interpreter_t* s) { +#ifdef MB_ENABLE_STACK_TRACE + int result = 0; + + if(!s) { + goto _exit; + } + + result = _ls_count(s->stack_frames); + +_exit: + return result; +#else /* MB_ENABLE_STACK_TRACE */ + int result = 0; + mb_unrefvar(s); + mb_unrefvar(l); + + return result; +#endif /* MB_ENABLE_STACK_TRACE */ +} + +/* Get stack frame names of a MY-BASIC environment */ +int mb_debug_get_stack_trace(struct mb_interpreter_t* s, char** fs, unsigned fc) { +#ifdef MB_ENABLE_STACK_TRACE + int result = MB_FUNC_OK; + _ls_node_t* f = 0; + unsigned i = 0; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(fs && fc) { + if(_ls_count(s->stack_frames) > fc) + fs[--fc] = "..."; + f = s->stack_frames->prev; + while(f != s->stack_frames && f && f->data && i < fc) { + fs[i++] = (char*)f->data; + f = f->prev; + } + } + while(i < fc) + fs[i++] = 0; + +_exit: + return result; +#else /* MB_ENABLE_STACK_TRACE */ + int result = MB_FUNC_ERR; + mb_unrefvar(s); + mb_unrefvar(fs); + mb_unrefvar(fc); + + return result; +#endif /* MB_ENABLE_STACK_TRACE */ +} + +/* Set a stepped handler to a MY-BASIC environment */ +int mb_debug_set_stepped_handler(struct mb_interpreter_t* s, mb_debug_stepped_handler_t prev, mb_debug_stepped_handler_t post) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + s->debug_prev_stepped_handler = prev; + s->debug_post_stepped_handler = post; + +_exit: + return result; +} + +/* Get type description text */ +const char* mb_get_type_string(mb_data_e t) { + switch(t) { + case MB_DT_NIL: + return "NIL"; + case MB_DT_UNKNOWN: + return "UNKNOWN"; + case MB_DT_INT: + return "INTEGER"; + case MB_DT_REAL: + return "REAL"; + case MB_DT_NUM: + return "NUMBER"; + case MB_DT_STRING: + return "STRING"; + case MB_DT_TYPE: + return "TYPE"; + case MB_DT_USERTYPE: + return "USERTYPE"; +#ifdef MB_ENABLE_USERTYPE_REF + case MB_DT_USERTYPE_REF: + return "USERTYPE_REF"; +#endif /* MB_ENABLE_USERTYPE_REF */ + case MB_DT_ARRAY: + return "ARRAY"; +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: + return "LIST"; + case MB_DT_LIST_IT: + return "LIST_ITERATOR"; + case MB_DT_DICT: + return "DICT"; + case MB_DT_DICT_IT: + return "DICT_ITERATOR"; + case MB_DT_COLLECTION: + return "COLLECTION"; + case MB_DT_ITERATOR: + return "ITERATOR"; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case MB_DT_CLASS: + return "CLASS"; +#endif /* MB_ENABLE_CLASS */ + case MB_DT_ROUTINE: + return "ROUTINE"; + default: /* Return a not existing string */ + return ""; + } +} + +/* Raise an error */ +int mb_raise_error(struct mb_interpreter_t* s, void** l, mb_error_e err, int ret) { + int result = MB_FUNC_ERR; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + _handle_error_on_obj(s, err, s->source_file, DON2(l), ret, _exit, result); + +_exit: + return result; +} + +/* Get the last error information */ +mb_error_e mb_get_last_error(struct mb_interpreter_t* s, const char** file, int* pos, unsigned short* row, unsigned short* col) { + mb_error_e result = SE_NO_ERR; + + if(!s) + goto _exit; + + result = s->last_error; + s->last_error = SE_NO_ERR; /* Clear error state */ + if(file) *file = s->last_error_file; + if(pos) *pos = s->last_error_pos; + if(row) *row = s->last_error_row; + if(col) *col = s->last_error_col; + s->last_error_file = 0; + +_exit: + return result; +} + +/* Get the error description text */ +const char* mb_get_error_desc(mb_error_e err) { +#ifdef MB_ENABLE_FULL_ERROR + if(err < countof(_ERR_DESC)) + return _ERR_DESC[err]; + + return "Unknown error"; +#else /* MB_ENABLE_FULL_ERROR */ + mb_unrefvar(err); + + return "Error occurred"; +#endif /* MB_ENABLE_FULL_ERROR */ +} + +/* Set an error handler to a MY-BASIC environment */ +int mb_set_error_handler(struct mb_interpreter_t* s, mb_error_handler_t h) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + s->error_handler = h; + +_exit: + return result; +} + +/* Set a print functor to a MY-BASIC environment */ +int mb_set_printer(struct mb_interpreter_t* s, mb_print_func_t p) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + s->printer = p; + +_exit: + return result; +} + +/* Set an input functor to a MY-BASIC environment */ +int mb_set_inputer(struct mb_interpreter_t* s, mb_input_func_t p) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + s->inputer = p; + +_exit: + return result; +} + +/* Set an import handler to a MY-BASIC environment */ +int mb_set_import_handler(struct mb_interpreter_t* s, mb_import_handler_t h) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + s->import_handler = h; + +_exit: + return result; +} + +/* Register a string measurer globally */ +int mb_set_string_measurer(mb_string_measure_func_t m) { + _mb_strlen_func = m; + + return MB_FUNC_OK; +} + +/* Register an allocator and a freer globally */ +int mb_set_memory_manager(mb_memory_allocate_func_t a, mb_memory_free_func_t f) { + _mb_allocate_func = a; + _mb_free_func = f; + + return MB_FUNC_OK; +} + +/* Get whether GC is enabled */ +bool_t mb_get_gc_enabled(struct mb_interpreter_t* s) { + if(!s) return false; + + return !s->gc.disabled; +} + +/* Sets whether GC is enabled */ +int mb_set_gc_enabled(struct mb_interpreter_t* s, bool_t gc) { + if(!s) return MB_FUNC_ERR; + + s->gc.disabled = !gc; + + return MB_FUNC_OK; +} + +/* Trigger GC */ +int mb_gc(struct mb_interpreter_t* s, int_t* collected) { + int_t diff = 0; + + if(!s) + return MB_FUNC_ERR; + + diff = (int_t)_mb_allocated; + _gc_collect_garbage(s, 1); + diff = (int_t)(_mb_allocated - diff); + if(collected) + *collected = diff; + + return MB_FUNC_OK; +} + +/* Get the userdata of a MY-BASIC environment */ +int mb_get_userdata(struct mb_interpreter_t* s, void** d) { + int result = MB_FUNC_OK; + + if(!s || !d) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(s && d) + *d = s->userdata; + +_exit: + return result; +} + +/* Set the userdata of a MY-BASIC environment */ +int mb_set_userdata(struct mb_interpreter_t* s, void* d) { + int result = MB_FUNC_OK; + + if(!s) { + result = MB_FUNC_ERR; + + goto _exit; + } + + if(s) + s->userdata = d; + +_exit: + return result; +} + +/* Safe stdin reader function */ +int mb_gets(struct mb_interpreter_t* s, const char* pmt, char* buf, int n) { + int result = 0; + mb_unrefvar(s); + mb_unrefvar(pmt); + + if(buf && n) { + if(fgets(buf, n, stdin) == 0) { + fprintf(stderr, "Error reading.\n"); + + exit(1); + } + result = (int)strlen(buf); + if(buf[result - 1] == _NEWLINE_CHAR) { + buf[result - 1] = _ZERO_CHAR; + result--; + } + } + + return result; +} + +/* Duplicate a string for internal use */ +char* mb_memdup(const char* val, unsigned size) { + char* result = 0; + + if(val != 0) { + result = (char*)mb_malloc(size); + if(result) + memcpy(result, val, size); + } + + return result; +} + +/* ========================================================} */ + +/* +** {======================================================== +** Lib definitions +*/ + +/** Core lib */ + +/* Operator #, dummy assignment */ +static int _core_dummy_assign(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* Operator + */ +static int _core_add(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _ONCALC(s, l, add, result, _exit); + _instruct_obj_meta_obj(s, l, add, result, _exit); + if(_is_string(((_tuple3_t*)*l)->e1) || _is_string(((_tuple3_t*)*l)->e2)) { + if(_is_string(((_tuple3_t*)*l)->e1) && _is_string(((_tuple3_t*)*l)->e2)) { + _instruct_connect_strings(l); + } else { + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, TON(l), MB_FUNC_ERR, _exit, result); + } + } else { + _instruct_num_op_num(+, l); + } + +_exit: + return result; +} + +/* Operator - (minus) */ +static int _core_min(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _ONCALC(s, l, sub, result, _exit); + _instruct_obj_meta_obj(s, l, sub, result, _exit); + _instruct_num_op_num(-, l); + + goto _exit; /* Avoid an unreferenced label warning */ + +_exit: + return result; +} + +/* Operator * */ +static int _core_mul(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _ONCALC(s, l, mul, result, _exit); + _instruct_obj_meta_obj(s, l, mul, result, _exit); + _instruct_num_op_num(*, l); + + goto _exit; /* Avoid an unreferenced label warning */ + +_exit: + return result; +} + +/* Operator / */ +static int _core_div(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _ONCALC(s, l, div, result, _exit); + _instruct_obj_meta_obj(s, l, div, result, _exit); + _proc_div_by_zero(s, l, real_t, _exit, result, SE_RN_DIVIDE_BY_ZERO); + _instruct_num_op_num(/, l); + +_exit: + return result; +} + +/* Operator MOD */ +static int _core_mod(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _proc_div_by_zero(s, l, int_t, _exit, result, SE_RN_DIVIDE_BY_ZERO); + _instruct_int_op_int(%, l); + +_exit: + return result; +} + +/* Operator ^ */ +static int _core_pow(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _instruct_fun_num_num(pow, l); + + return result; +} + +/* Operator ( */ +static int _core_open_bracket(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* Operator ) */ +static int _core_close_bracket(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + +#ifdef MB_ENABLE_LAMBDA + if(s->last_routine && s->last_routine->type == MB_RT_LAMBDA) { + result = _core_return(s, l); + + goto _exit; + } +#endif /* MB_ENABLE_LAMBDA */ + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* Operator - (negative) */ +static int _core_neg(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + mb_value_t arg; + _running_context_t* running = 0; + int* inep = 0; + int calc_depth = 0; +#ifdef MB_ENABLE_USERTYPE_REF + _object_t obj; +#endif /* MB_ENABLE_USERTYPE_REF */ + + mb_assert(s && l); + + running = s->running_context; + ast = (_ls_node_t*)*l; + if(ast) ast = ast->next; + + if(!_ls_empty(s->in_neg_expr)) + inep = (int*)_ls_back(s->in_neg_expr)->data; + + if(inep) + (*inep)++; + + calc_depth = running->calc_depth; + + mb_make_nil(arg); + if(ast && _IS_FUNC((_object_t*)ast->data, _core_open_bracket)) { + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_value(s, l, &arg)); + + mb_check(mb_attempt_close_bracket(s, l)); + } else { + running->calc_depth = 1; + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_pop_value(s, l, &arg)); + + mb_check(mb_attempt_func_end(s, l)); + } + + if(inep) + (*inep)--; + + _ONNEG(s, l, arg, result, _exit); + switch(arg.type) { + case MB_DT_INT: + arg.value.integer = -(arg.value.integer); + + break; + case MB_DT_REAL: + arg.value.float_point = -(arg.value.float_point); + + break; +#ifdef MB_ENABLE_USERTYPE_REF + case MB_DT_USERTYPE_REF: + _MAKE_NIL(&obj); + _public_value_to_internal_object(&arg, &obj); + if(obj.data.usertype_ref->calc_operators && obj.data.usertype_ref->calc_operators->neg) { + mb_meta_operator_t neg = obj.data.usertype_ref->calc_operators->neg; + mb_check(mb_ref_value(s, l, arg)); + mb_check(mb_unref_value(s, l, arg)); + mb_check(neg(s, l, &arg, 0, &arg)); + + break; + } + /* Fall through */ +#endif /* MB_ENABLE_USERTYPE_REF */ + default: + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, TON(l), MB_FUNC_WARNING, _exit, result); + + break; + } + +_exit: + mb_check(mb_push_value(s, l, arg)); + + running->calc_depth = calc_depth; + + return result; +} + +/* Operator = (equal) */ +static int _core_equal(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _tuple3_t* tpr = 0; + + mb_assert(s && l); + + if(_is_string(((_tuple3_t*)*l)->e1) || _is_string(((_tuple3_t*)*l)->e2)) { + if(_is_string(((_tuple3_t*)*l)->e1) && _is_string(((_tuple3_t*)*l)->e2)) { + _instruct_compare_strings(==, l); + } else if(_is_nil(((_tuple3_t*)*l)->e1) || _is_nil(((_tuple3_t*)*l)->e2)) { + tpr = (_tuple3_t*)*l; + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = false; + } else { + _set_tuple3_result(l, 0); + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, TON(l), MB_FUNC_WARNING, _exit, result); + } + } else if(_is_number(((_tuple3_t*)*l)->e1) && _is_number(((_tuple3_t*)*l)->e2)) { + _instruct_num_op_num(==, l); + tpr = (_tuple3_t*)*l; + if(((_object_t*)tpr->e3)->type != _DT_INT) { + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = ((_object_t*)tpr->e3)->data.float_point != 0.0f; + } + } else { + _instruct_obj_op_obj(==, l); + } + +_exit: + return result; +} + +/* Operator < */ +static int _core_less(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _tuple3_t* tpr = 0; + + mb_assert(s && l); + + if(_is_string(((_tuple3_t*)*l)->e1) || _is_string(((_tuple3_t*)*l)->e2)) { + if(_is_string(((_tuple3_t*)*l)->e1) && _is_string(((_tuple3_t*)*l)->e2)) { + _instruct_compare_strings(<, l); + } else { + if(_is_string(((_tuple3_t*)*l)->e1)) { + _set_tuple3_result(l, 0); + } else { + _set_tuple3_result(l, 1); + } + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, TON(l), MB_FUNC_WARNING, _exit, result); + } + } else if(_is_number(((_tuple3_t*)*l)->e1) && _is_number(((_tuple3_t*)*l)->e2)) { + _instruct_num_op_num(<, l); + tpr = (_tuple3_t*)*l; + if(((_object_t*)tpr->e3)->type != _DT_INT) { + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = ((_object_t*)tpr->e3)->data.float_point != 0.0f; + } + } else { + _instruct_obj_op_obj(<, l); + } + +_exit: + return result; +} + +/* Operator > */ +static int _core_greater(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _tuple3_t* tpr = 0; + + mb_assert(s && l); + + if(_is_string(((_tuple3_t*)*l)->e1) || _is_string(((_tuple3_t*)*l)->e2)) { + if(_is_string(((_tuple3_t*)*l)->e1) && _is_string(((_tuple3_t*)*l)->e2)) { + _instruct_compare_strings(>, l); + } else { + if(_is_string(((_tuple3_t*)*l)->e1)) { + _set_tuple3_result(l, 1); + } else { + _set_tuple3_result(l, 0); + } + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, TON(l), MB_FUNC_WARNING, _exit, result); + } + } else if(_is_number(((_tuple3_t*)*l)->e1) && _is_number(((_tuple3_t*)*l)->e2)) { + _instruct_num_op_num(>, l); + tpr = (_tuple3_t*)*l; + if(((_object_t*)tpr->e3)->type != _DT_INT) { + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = ((_object_t*)tpr->e3)->data.float_point != 0.0f; + } + } else { + _instruct_obj_op_obj(>, l); + } + +_exit: + return result; +} + +/* Operator <= */ +static int _core_less_equal(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _tuple3_t* tpr = 0; + + mb_assert(s && l); + + if(_is_string(((_tuple3_t*)*l)->e1) || _is_string(((_tuple3_t*)*l)->e2)) { + if(_is_string(((_tuple3_t*)*l)->e1) && _is_string(((_tuple3_t*)*l)->e2)) { + _instruct_compare_strings(<=, l); + } else { + if(_is_string(((_tuple3_t*)*l)->e1)) { + _set_tuple3_result(l, 0); + } else { + _set_tuple3_result(l, 1); + } + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, TON(l), MB_FUNC_WARNING, _exit, result); + } + } else if(_is_number(((_tuple3_t*)*l)->e1) && _is_number(((_tuple3_t*)*l)->e2)) { + _instruct_num_op_num(<=, l); + tpr = (_tuple3_t*)*l; + if(((_object_t*)tpr->e3)->type != _DT_INT) { + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = ((_object_t*)tpr->e3)->data.float_point != 0.0f; + } + } else { + _instruct_obj_op_obj(<=, l); + } + +_exit: + return result; +} + +/* Operator >= */ +static int _core_greater_equal(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _tuple3_t* tpr = 0; + + mb_assert(s && l); + + if(_is_string(((_tuple3_t*)*l)->e1) || _is_string(((_tuple3_t*)*l)->e2)) { + if(_is_string(((_tuple3_t*)*l)->e1) && _is_string(((_tuple3_t*)*l)->e2)) { + _instruct_compare_strings(>=, l); + } else { + if(_is_string(((_tuple3_t*)*l)->e1)) { + _set_tuple3_result(l, 1); + } else { + _set_tuple3_result(l, 0); + } + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, TON(l), MB_FUNC_WARNING, _exit, result); + } + } else if(_is_number(((_tuple3_t*)*l)->e1) && _is_number(((_tuple3_t*)*l)->e2)) { + _instruct_num_op_num(>=, l); + tpr = (_tuple3_t*)*l; + if(((_object_t*)tpr->e3)->type != _DT_INT) { + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = ((_object_t*)tpr->e3)->data.float_point != 0.0f; + } + } else { + _instruct_obj_op_obj(>=, l); + } + +_exit: + return result; +} + +/* Operator <> */ +static int _core_not_equal(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _tuple3_t* tpr = 0; + + mb_assert(s && l); + + if(_is_string(((_tuple3_t*)*l)->e1) || _is_string(((_tuple3_t*)*l)->e2)) { + if(_is_string(((_tuple3_t*)*l)->e1) && _is_string(((_tuple3_t*)*l)->e2)) { + _instruct_compare_strings(!=, l); + } else if(_is_nil(((_tuple3_t*)*l)->e1) || _is_nil(((_tuple3_t*)*l)->e2)) { + tpr = (_tuple3_t*)*l; + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = true; + } else { + _set_tuple3_result(l, 1); + _handle_error_on_obj(s, SE_RN_STRING_EXPECTED, s->source_file, TON(l), MB_FUNC_WARNING, _exit, result); + } + } else if(_is_number(((_tuple3_t*)*l)->e1) && _is_number(((_tuple3_t*)*l)->e2)) { + _instruct_num_op_num(!=, l); + tpr = (_tuple3_t*)*l; + if(((_object_t*)tpr->e3)->type != _DT_INT) { + ((_object_t*)tpr->e3)->type = _DT_INT; + ((_object_t*)tpr->e3)->data.integer = ((_object_t*)tpr->e3)->data.float_point != 0.0f; + } + } else { + _instruct_obj_op_obj(!=, l); + } + +_exit: + return result; +} + +/* Operator AND */ +static int _core_and(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _instruct_bool_op_bool(&&, l); + + return result; +} + +/* Operator OR */ +static int _core_or(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + _instruct_bool_op_bool(||, l); + + return result; +} + +/* Operator NOT */ +static int _core_not(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + mb_value_t arg; + mb_value_t ret; + _running_context_t* running = 0; + int calc_depth = 0; + + mb_assert(s && l); + + running = s->running_context; + ast = (_ls_node_t*)*l; + if(ast) ast = ast->next; + + calc_depth = running->calc_depth; + + mb_make_nil(arg); + mb_make_nil(ret); + if(ast && _IS_FUNC((_object_t*)ast->data, _core_open_bracket)) { + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_value(s, l, &arg)); + + mb_check(mb_attempt_close_bracket(s, l)); + } else { + running->calc_depth = 1; + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_pop_value(s, l, &arg)); + + mb_check(mb_attempt_func_end(s, l)); + } + _ONCOND(s, 0, &arg); + + switch(arg.type) { + case MB_DT_NIL: + mb_make_bool(ret, true); + + break; + case MB_DT_INT: + mb_make_bool(ret, !arg.value.integer); + + break; + case MB_DT_REAL: + mb_make_bool(ret, arg.value.float_point == (real_t)0); + + break; + default: + mb_make_bool(ret, false); + + break; + } + _assign_public_value(s, &arg, 0, true); + mb_check(mb_push_int(s, l, ret.value.integer)); + + running->calc_depth = calc_depth; + + return result; +} + +/* Operator IS */ +static int _core_is(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _object_t* fst = 0; + _object_t* scd = 0; + _object_t* val = 0; + bool_t is_a = 0; + + mb_assert(s && l); + + _instruct_obj_meta_obj(s, l, is, result, _exit); + + fst = (_object_t*)((_tuple3_t*)*l)->e1; + scd = (_object_t*)((_tuple3_t*)*l)->e2; + val = (_object_t*)((_tuple3_t*)*l)->e3; + + if(_IS_VAR(fst)) fst = fst->data.variable->data; + if(_IS_VAR(scd)) scd = scd->data.variable->data; + if(!fst || !scd) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, TON(l), MB_FUNC_ERR, _exit, result); + } + if(scd->type == _DT_TYPE) { + val->type = _DT_INT; + val->data.integer = (int_t)(!!(_internal_type_to_public_type(fst->type) & scd->data.type)); + } else { +#ifdef MB_ENABLE_CLASS + if(!_IS_CLASS(fst) || !_IS_CLASS(scd)) { + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, TON(l), MB_FUNC_ERR, _exit, result); + } + _traverse_class(fst->data.instance, 0, _is_a_class, _META_LIST_MAX_DEPTH, true, scd->data.instance, &is_a); + val->type = _DT_INT; + val->data.integer = (int_t)is_a; +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(is_a); + + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, TON(l), MB_FUNC_ERR, _exit, result); +#endif /* MB_ENABLE_CLASS */ + } + +_exit: + return result; +} + +/* LET statement */ +static int _core_let(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _running_context_t* running = 0; + _var_t* var = 0; + _var_t* evar = 0; + int refc = 1; + _array_t* arr = 0; + _object_t* arr_obj = 0; + unsigned arr_idx = 0; + bool_t literally = false; + _object_t* val = 0; +#ifdef MB_ENABLE_COLLECTION_LIB + int_t idx = 0; + mb_value_t key; + bool_t is_coll = false; +#endif /* MB_ENABLE_COLLECTION_LIB */ + + mb_assert(s && l); + + running = s->running_context; + + ast = (_ls_node_t*)*l; + obj = (_object_t*)ast->data; + if(obj->type == _DT_FUNC) + ast = ast->next; + if(!ast || !ast->data) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + obj = (_object_t*)ast->data; +#ifdef MB_ENABLE_CLASS + if(_IS_VAR(obj)) { + _ls_node_t* fn = 0; + if(_is_valid_class_accessor_following_routine(s, obj->data.variable, ast, &fn)) { + if(fn) + obj = (_object_t*)fn->data; + } + } +#endif /* MB_ENABLE_CLASS */ + if(obj->type == _DT_ARRAY) { + arr_obj = obj; + arr = _search_array_in_scope_chain(s, obj->data.array, &arr_obj); + result = _get_array_index(s, &ast, 0, &arr_idx, &literally); + if(result != MB_FUNC_OK) + goto _exit; + } else if(obj->type == _DT_VAR && obj->data.variable->data->type == _DT_ARRAY) { + arr_obj = obj->data.variable->data; + arr = _search_array_in_scope_chain(s, obj->data.variable->data->data.array, &arr_obj); + result = _get_array_index(s, &ast, 0, &arr_idx, &literally); + if(result != MB_FUNC_OK) + goto _exit; + } else if(obj->type == _DT_VAR) { + if(_IS_ME(obj->data.variable)) { + _handle_error_on_obj(s, SE_RN_CANNOT_ASSIGN_TO_RESERVED_WORD, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } else { + evar = obj->data.variable; + var = _search_var_in_scope_chain(s, obj->data.variable, 0); + if(var == evar) evar = 0; +#ifdef MB_ENABLE_CLASS + if(evar && evar->pathing == _PATHING_UPVALUE) evar = 0; +#endif /* MB_ENABLE_CLASS */ + if(var == _OBJ_BOOL_TRUE->data.variable || var == _OBJ_BOOL_FALSE->data.variable) { + _handle_error_on_obj(s, SE_RN_CANNOT_ASSIGN_TO_RESERVED_WORD, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + } + } else { + _handle_error_on_obj(s, SE_RN_CANNOT_ASSIGN_TO_RESERVED_WORD, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + ast = ast->next; + if(!ast || !ast->data) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } +#ifdef MB_ENABLE_COLLECTION_LIB + if(var && _IS_COLL(var->data)) { + obj = (_object_t*)ast->data; + if(_IS_FUNC(obj, _core_open_bracket)) { + mb_check(mb_attempt_open_bracket(s, l)); + + switch(var->data->type) { + case _DT_LIST: + mb_check(mb_pop_int(s, l, &idx)); + + break; + case _DT_DICT: + mb_make_nil(key); + mb_check(mb_pop_value(s, l, &key)); + + break; + default: /* Do nothing */ + break; + } + + mb_check(mb_attempt_close_bracket(s, l)); + + ast = (_ls_node_t*)*l; + is_coll = true; + } + } +#endif /* MB_ENABLE_COLLECTION_LIB */ + obj = (_object_t*)ast->data; + if(!_IS_FUNC(obj, _core_equal)) { /* Is it an assign operator? */ + _handle_error_on_obj(s, SE_RN_ASSIGN_OPERATOR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + ast = ast->next; + val = _create_object(); + result = _calc_expression(s, &ast, &val); + + if(var) { +#ifdef MB_ENABLE_COLLECTION_LIB + if(is_coll) { + switch(var->data->type) { + case _DT_LIST: + if(!_set_list(var->data->data.list, idx, 0, &val)) { + safe_free(val); + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + case _DT_DICT: + if(!_set_dict(var->data->data.dict, &key, 0, 0, val)) { + safe_free(val); + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + default: /* Do nothing */ + break; + } + + goto _exit; + } +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS +_proc_extra_var: +#endif /* MB_ENABLE_CLASS */ + _dispose_object(var->data); + var->data->type = val->type; +#ifdef MB_ENABLE_COLLECTION_LIB + if(val->type == _DT_LIST_IT || val->type == _DT_DICT_IT) + _assign_with_it(var->data, val); + else + var->data->data = val->data; +#else /* MB_ENABLE_COLLECTION_LIB */ + var->data->data = val->data; +#endif /* MB_ENABLE_COLLECTION_LIB */ + if(val->type == _DT_ROUTINE) { +#ifdef MB_ENABLE_LAMBDA + if(val->data.routine->type == MB_RT_LAMBDA) + var->data->is_ref = val->is_ref; + else + var->data->is_ref = true; +#else /* MB_ENABLE_LAMBDA */ + var->data->is_ref = true; +#endif /* MB_ENABLE_LAMBDA */ +#ifndef MB_ENABLE_ARRAY_REF + } else if(val->type == _DT_ARRAY) { + var->data->is_ref = true; +#endif /* MB_ENABLE_ARRAY_REF */ + } else { + var->data->is_ref = val->is_ref; + } +#ifdef MB_ENABLE_CLASS + if(evar && evar->pathing) { + if(var->data->type == _DT_STRING) { + var->data->data.string = mb_strdup(var->data->data.string, mb_strlen(var->data->data.string) + 1); + var->data->is_ref = false; + } + var = evar; + evar = 0; + refc++; + + goto _proc_extra_var; + } +#endif /* MB_ENABLE_CLASS */ + } else if(arr && literally) { + if(val->type != _DT_UNKNOWN) { + if(arr->name) { + _destroy_object(val, 0); + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } +#ifdef MB_ENABLE_ARRAY_REF + _unref(&arr_obj->data.array->ref, arr_obj->data.array); +#endif /* MB_ENABLE_ARRAY_REF */ + arr_obj->type = val->type; +#ifdef MB_ENABLE_COLLECTION_LIB + if(val->type == _DT_LIST_IT || val->type == _DT_DICT_IT) + _assign_with_it(arr_obj, val); + else + arr_obj->data = val->data; +#else /* MB_ENABLE_COLLECTION_LIB */ + arr_obj->data = val->data; +#endif /* MB_ENABLE_COLLECTION_LIB */ + arr_obj->is_ref = val->is_ref; + } + } else if(arr) { + mb_value_u _val; + switch(val->type) { +#ifdef MB_SIMPLE_ARRAY + case _DT_INT: + if(arr->type == _DT_STRING) goto _default; + _val.integer = val->data.integer; + + break; + case _DT_REAL: + if(arr->type == _DT_STRING) goto _default; + _val.float_point = val->data.float_point; + + break; + case _DT_STRING: + if(arr->type != _DT_STRING) goto _default; + _val.string = val->data.string; + + break; +_default: +#else /* MB_SIMPLE_ARRAY */ + case _DT_NIL: /* Fall through */ + case _DT_UNKNOWN: /* Fall through */ + case _DT_INT: /* Fall through */ + case _DT_REAL: /* Fall through */ + case _DT_STRING: /* Fall through */ + case _DT_TYPE: /* Fall through */ + case _DT_USERTYPE: + _copy_bytes(_val.bytes, val->data.bytes); + + break; +#endif /* MB_SIMPLE_ARRAY */ + default: + _dispose_object(val); + safe_free(val); + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + result = _set_array_elem(s, ast, arr, arr_idx, &_val, &val->type); + if(result != MB_FUNC_OK) + goto _exit; + if(val->type == _DT_STRING && !val->is_ref) { + safe_free(val->data.string); + } + } + while(refc--) { + _REF(val) + } + safe_free(val); + +_exit: + *l = ast; + + return result; +} + +/* DIM statement */ +static int _core_dim(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* arr = 0; + _object_t* len = 0; + mb_value_u val; + _array_t dummy; + + mb_assert(s && l); + + /* Array name */ + ast = (_ls_node_t*)*l; + if(!ast->next || ((_object_t*)ast->next->data)->type != _DT_ARRAY) { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, (ast && ast->next) ? ((_object_t*)ast->next->data) : 0, MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + arr = (_object_t*)ast->data; + memset(&dummy, 0, sizeof(_array_t)); + dummy.type = arr->data.array->type; + dummy.name = arr->data.array->name; + /* ( */ + if(!ast->next || ((_object_t*)ast->next->data)->type != _DT_FUNC || ((_object_t*)ast->next->data)->data.func->pointer != _core_open_bracket) { + _handle_error_on_obj(s, SE_RN_OPEN_BRACKET_EXPECTED, s->source_file, (ast && ast->next) ? ((_object_t*)ast->next->data) : 0, MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + /* Array subscript */ + if(!ast->next) { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, (ast && ast->next) ? ((_object_t*)ast->next->data) : 0, MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + while(((_object_t*)ast->data)->type != _DT_FUNC || ((_object_t*)ast->data)->data.func->pointer != _core_close_bracket) { + /* Get an integer value */ + len = (_object_t*)ast->data; + if(!_try_get_value(len, &val, _DT_INT)) { + _handle_error_on_obj(s, SE_RN_UNEXPECTED_TYPE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(val.integer <= 0) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(dummy.dimension_count >= MB_MAX_DIMENSION_COUNT) { + _handle_error_on_obj(s, SE_RN_TOO_MANY_DIMENSIONS, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + dummy.dimensions[dummy.dimension_count++] = (int)val.integer; + if(dummy.count) + dummy.count *= (unsigned)val.integer; + else + dummy.count += (unsigned)val.integer; + ast = ast->next; + /* Comma? */ + if(_IS_SEP(ast->data, ',')) + ast = ast->next; + } + /* Create or modify raw data */ + _clear_array(arr->data.array); +#ifdef MB_ENABLE_ARRAY_REF + dummy.ref = arr->data.array->ref; +#endif /* MB_ENABLE_ARRAY_REF */ + *(arr->data.array) = dummy; + _init_array(arr->data.array); + if(!arr->data.array->raw) { + arr->data.array->dimension_count = 0; + arr->data.array->dimensions[0] = 0; + arr->data.array->count = 0; + _handle_error_on_obj(s, SE_RN_OUT_OF_MEMORY, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + +_exit: + *l = ast; + + return result; +} + +/* IF statement */ +static int _core_if(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* val = 0; + _object_t* obj = 0; + bool_t multi_line = false; + bool_t skip = false; + _running_context_t* running = 0; + + mb_assert(s && l); + + running = s->running_context; + + ast = (_ls_node_t*)*l; + ast = ast->next; + + val = _create_object(); + +_elseif: + _MAKE_NIL(val); + result = _calc_expression(s, &ast, &val); + _ONCOND(s, val, 0); + _REF(val) + if(result != MB_FUNC_OK) + goto _exit; + if(!ast) + goto _exit; + + obj = (_object_t*)ast->data; + if(val->data.integer) { + skip = true; + + if(!_IS_FUNC(obj, _core_then)) { + if(ast->prev && _IS_FUNC(ast->prev->data, _core_then)) { + ast = ast->prev; + } else { + _handle_error_on_obj(s, SE_RN_THEN_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + } + + if(ast && ast->next && _IS_EOS(ast->next->data)) { + multi_line = true; + } else { + if(!s->jump_set || (s->jump_set & _JMP_INS)) + s->skip_to_eoi = _ls_back(s->sub_stack); + } + do { + ast = ast->next; + while(ast && _IS_EOS(ast->data)) + ast = ast->next; + if(ast && _IS_FUNC(ast->data, _core_endif)) { + ast = ast->prev; + + break; + } + if(!ast) + break; + if(multi_line && (_IS_FUNC(ast->data, _core_else) || _IS_FUNC(ast->data, _core_elseif))) + break; + result = _execute_statement(s, &ast, true); + if(result != MB_FUNC_OK) + goto _exit; + if(ast) { + obj = (_object_t*)ast->data; + if(obj->type == _DT_PREV_IMPORT || obj->type == _DT_POST_IMPORT) + _execute_statement(s, &ast, true); + else + ast = ast->prev; + } + } while(ast && ( + (!multi_line && _IS_SEP(ast->data, ':')) || ( + multi_line && ast->next && ( + !_IS_FUNC(ast->next->data, _core_elseif) && + !_IS_FUNC(ast->next->data, _core_else) && + !_IS_FUNC(ast->next->data, _core_endif) + ) + ) + ) + ); + + if(!ast) + goto _exit; + + obj = (_object_t*)ast->data; + if(!_IS_EOS(obj)) { + s->skip_to_eoi = 0; + result = _skip_to(s, &ast, 0, _DT_EOS); + if(result != MB_FUNC_OK) + goto _exit; + } + } else { + if(ast && ast->next && _IS_EOS(ast->next->data)) { + multi_line = true; + + result = _skip_if_chunk(s, &ast); + if(result != MB_FUNC_OK) + goto _exit; + } + if(multi_line && ast && _IS_FUNC(ast->data, _core_elseif)) { + ast = ast->next; + + goto _elseif; + } + if(multi_line && ast && _IS_FUNC(ast->data, _core_endif)) + goto _exit; + + result = _skip_to(s, &ast, _core_else, _DT_EOS); + if(result != MB_FUNC_OK) + goto _exit; + + obj = (_object_t*)ast->data; + if(!_IS_EOS(obj)) { + skip = true; + + if(!_IS_FUNC(obj, _core_else)) { + _handle_error_on_obj(s, SE_RN_ELSE_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + do { + _ls_node_t* la = 0; + la = ast = ast->next; + while(ast && _IS_EOS(ast->data)) { + ast = ast->next; + if(ast) la = ast; + } + if(!ast) { + mb_get_last_error(s, 0, 0, 0, 0); + _handle_error_on_obj(s, SE_RN_ENDIF_EXPECTED, s->source_file, DON(la), MB_FUNC_ERR, _exit, result); + } + if(ast && _IS_FUNC(ast->data, _core_endif)) { + ast = ast->prev; + + break; + } + result = _execute_statement(s, &ast, true); + if(result != MB_FUNC_OK) + goto _exit; + if(ast) + ast = ast->prev; + } while(ast && ( + (!multi_line && _IS_SEP(ast->data, ':')) || + (multi_line && !_IS_FUNC(ast->next->data, _core_endif)) + ) + ); + } + } + +_exit: + if(result == MB_SUB_RETURN) { + if(ast) + ast = ast->prev; + } else { + if(multi_line) { + int ret = MB_FUNC_OK; + if(skip) + ret = _skip_struct(s, &ast, _core_if, _core_then, _core_endif); + if(result != MB_FUNC_END && result != MB_LOOP_BREAK && result != MB_LOOP_CONTINUE && result != MB_SUB_RETURN) { + if(ret != MB_FUNC_OK) + result = ret; + } + } + } + + *l = ast; + + switch(val->type) { +#ifdef MB_ENABLE_COLLECTION_LIB + case _DT_LIST_IT: /* Fall through */ + case _DT_DICT_IT: + _destroy_object_capsule_only(val, 0); + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ + case _DT_UNKNOWN: /* Do nothing */ + break; + default: + _destroy_object(val, 0); + + break; + } + + return result; +} + +/* THEN statement */ +static int _core_then(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* ELSEIF statement */ +static int _core_elseif(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* ELSE statement */ +static int _core_else(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* ENDIF statement */ +static int _core_endif(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* FOR statement */ +static int _core_for(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _var_t* var_loop = 0; + + mb_assert(s && l); + + ast = (_ls_node_t*)*l; + ast = ast->next; + + obj = (_object_t*)ast->data; + if(!_IS_VAR(obj)) { + _handle_error_on_obj(s, SE_RN_LOOP_VAR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + var_loop = obj->data.variable; + +#ifdef MB_ENABLE_COLLECTION_LIB + if(ast && ast->next && _IS_FUNC(ast->next->data, _core_in)) + result = _execute_ranged_for_loop(s, &ast, var_loop); + else + result = _execute_normal_for_loop(s, &ast, var_loop); +#else /* MB_ENABLE_COLLECTION_LIB */ + result = _execute_normal_for_loop(s, &ast, var_loop); +#endif /* MB_ENABLE_COLLECTION_LIB */ + +_exit: + *l = ast; + + return result; +} + +/* IN statement */ +static int _core_in(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* TO statement */ +static int _core_to(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* STEP statement */ +static int _core_step(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* NEXT statement */ +static int _core_next(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _running_context_t* running = 0; + + mb_assert(s && l); + + running = s->running_context; + ast = (_ls_node_t*)*l; + + result = MB_LOOP_CONTINUE; + + ast = ast->next; + if(ast) + obj = (_object_t*)ast->data; + if(_IS_VAR(obj)) + running->next_loop_var = obj->data.variable; + + *l = ast; + + return result; +} + +/* WHILE statement */ +static int _core_while(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _ls_node_t* loop_begin_node = 0; + _object_t* obj = 0; + _object_t loop_cond; + _object_t* loop_cond_ptr = 0; + + mb_assert(s && l); + + ast = (_ls_node_t*)*l; + ast = ast->next; + + loop_cond_ptr = &loop_cond; + _MAKE_NIL(loop_cond_ptr); + + loop_begin_node = ast; + +_loop_begin: + ast = loop_begin_node; + + result = _calc_expression(s, &ast, &loop_cond_ptr); + _ONCOND(s, loop_cond_ptr, 0); + if(result != MB_FUNC_OK) + goto _exit; + + if(loop_cond_ptr->data.integer) { + /* Keep looping */ + if(!ast) + goto _exit; + obj = (_object_t*)ast->data; + while(!_IS_FUNC(obj, _core_wend)) { + result = _execute_statement(s, &ast, true); + if(result == MB_LOOP_BREAK) { /* EXIT */ + if(_skip_struct(s, &ast, _core_while, 0, _core_wend) != MB_FUNC_OK) + goto _exit; + _skip_to(s, &ast, 0, _DT_EOS); + result = MB_FUNC_OK; + + goto _exit; + } else if(result == MB_SUB_RETURN && s->last_routine) { /* RETURN */ + if(ast) ast = ast->prev; + if(ast) ast = ast->prev; + + goto _exit; + } else if(result != MB_FUNC_OK && result != MB_SUB_RETURN) { /* Normally */ + goto _exit; + } + + if(!ast) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + obj = (_object_t*)ast->data; + } + + goto _loop_begin; + } else { + /* End looping */ + if(_skip_struct(s, &ast, _core_while, 0, _core_wend) != MB_FUNC_OK) + goto _exit; + _skip_to(s, &ast, 0, _DT_EOS); + + goto _exit; + } + +_exit: + *l = ast; + + return result; +} + +/* WEND statement */ +static int _core_wend(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* DO statement */ +static int _core_do(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _ls_node_t* loop_begin_node = 0; + _object_t* obj = 0; + _object_t loop_cond; + _object_t* loop_cond_ptr = 0; + + mb_assert(s && l); + + ast = (_ls_node_t*)*l; + ast = ast->next; + + obj = (_object_t*)ast->data; + if(!_IS_EOS(obj)) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + + loop_cond_ptr = &loop_cond; + _MAKE_NIL(loop_cond_ptr); + + loop_begin_node = ast; + +_loop_begin: + ast = loop_begin_node; + + obj = ast ? (_object_t*)ast->data : 0; + while(obj && !_IS_FUNC(obj, _core_until)) { + result = _execute_statement(s, &ast, true); + if(result == MB_LOOP_BREAK) { /* EXIT */ + if(_skip_struct(s, &ast, _core_do, 0, _core_until) != MB_FUNC_OK) + goto _exit; + _skip_to(s, &ast, 0, _DT_EOS); + result = MB_FUNC_OK; + + goto _exit; + } else if(result == MB_SUB_RETURN && s->last_routine) { /* RETURN */ + if(ast) ast = ast->prev; + if(ast) ast = ast->prev; + + goto _exit; + } else if(result != MB_FUNC_OK && result != MB_SUB_RETURN) { /* Normally */ + goto _exit; + } + + obj = ast ? (_object_t*)ast->data : 0; + } + + obj = ast ? (_object_t*)ast->data : 0; + if(!obj || !_IS_FUNC(obj, _core_until)) { + _handle_error_on_obj(s, SE_RN_UNTIL_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + + result = _calc_expression(s, &ast, &loop_cond_ptr); + _ONCOND(s, loop_cond_ptr, 0); + if(result != MB_FUNC_OK) + goto _exit; + + if(loop_cond_ptr->data.integer) { + /* End looping */ + if(ast) + _skip_to(s, &ast, 0, _DT_EOS); + + goto _exit; + } else { + /* Keep looping */ + goto _loop_begin; + } + +_exit: + *l = ast; + + return result; +} + +/* UNTIL statement */ +static int _core_until(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* EXIT statement */ +static int _core_exit(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + result = MB_LOOP_BREAK; + + return result; +} + +/* GOTO statement */ +static int _core_goto(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _running_context_t* running = 0; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _label_t* label = 0; + _ls_node_t* glbsyminscope = 0; + + mb_assert(s && l); + + running = s->running_context; + + ast = (_ls_node_t*)*l; + ast = ast->next; + + _using_jump_set_of_instructional(s, ast, _exit, result); + + obj = (_object_t*)ast->data; + if(obj->type != _DT_LABEL) { + _handle_error_on_obj(s, SE_RN_JUMP_LABEL_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + + label = (_label_t*)obj->data.label; + if(!label->node) { + glbsyminscope = _ht_find(running->var_dict, label->name); + if(!(glbsyminscope && ((_object_t*)glbsyminscope->data)->type == _DT_LABEL)) { + _handle_error_on_obj(s, SE_RN_LABEL_DOES_NOT_EXIST, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + label->node = ((_object_t*)glbsyminscope->data)->data.label->node; + } + + mb_assert(label->node && label->node->prev); + ast = label->node->prev; + if(ast && !ast->data) + ast = ast->next; + +_exit: + *l = ast; + + return result; +} + +/* GOSUB statement */ +static int _core_gosub(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _running_context_t* running = 0; + + mb_assert(s && l); + + running = s->running_context; + ast = (_ls_node_t*)*l; + + _using_jump_set_of_instructional(s, ast, _exit, result); + + result = _core_goto(s, l); + if(result == MB_FUNC_OK) + _ls_pushback(s->sub_stack, ast); + +_exit: + return result; +} + +/* RETURN statement */ +static int _core_return(mb_interpreter_t* s, void** l) { + int result = MB_SUB_RETURN; + _ls_node_t* ast = 0; + _ls_node_t* sub_stack = 0; + _running_context_t* running = 0; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + running = s->running_context; + sub_stack = s->sub_stack; + + if(running->prev) { + ast = (_ls_node_t*)*l; + ast = ast->next; + if(mb_has_arg(s, (void**)&ast)) { + mb_check(mb_pop_value(s, (void**)&ast, &arg)); + mb_check(mb_push_value(s, (void**)&ast, arg)); + } + } + ast = (_ls_node_t*)_ls_popback(sub_stack); + if(!ast) { + _handle_error_on_obj(s, SE_RN_NO_RETURN_POINT, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + *l = ast; + +_exit: + return result; +} + +/* CALL statement */ +static int _core_call(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _routine_t* routine = 0; + mb_value_t ret; + _ls_node_t* pathed = 0; + + mb_assert(s && l); + + ast = (_ls_node_t*)*l; + ast = ast->next; + + obj = (_object_t*)ast->data; + +_retry: + switch(obj->type) { + case _DT_FUNC: + if(_IS_FUNC(obj, _core_open_bracket)) { + mb_check(mb_attempt_open_bracket(s, l)); + + ast = ast->next; + obj = (_object_t*)ast->data; +#ifdef MB_ENABLE_CLASS + if(_IS_VAR(obj)) { + pathed = _search_identifier_in_scope_chain(s, 0, obj->data.variable->name, _PN(obj->data.variable->pathing), 0, 0); + if(pathed && pathed->data) + obj = (_object_t*)pathed->data; + } +#endif /* MB_ENABLE_CLASS */ + if(!obj || obj->type != _DT_ROUTINE) { + _handle_error_on_obj(s, SE_RN_ROUTINE_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ret.type = MB_DT_ROUTINE; + ret.value.routine = obj->data.routine; + ast = ast->next; + *l = ast; + + mb_check(mb_attempt_close_bracket(s, l)); + + mb_check(mb_push_value(s, l, ret)); + + ast = (_ls_node_t*)*l; + } + + break; + case _DT_VAR: +#ifdef MB_ENABLE_LAMBDA + if(obj->data.variable->data->type == _DT_ROUTINE && obj->data.variable->data->data.routine->type == MB_RT_LAMBDA) { +#ifdef MB_ENABLE_CLASS + int fp = _PN(obj->data.variable->pathing); +#else /* MB_ENABLE_CLASS */ + int fp = _PATHING_NORMAL; +#endif /* MB_ENABLE_CLASS */ + pathed = _search_identifier_in_scope_chain(s, 0, obj->data.variable->name, fp, 0, 0); + if(pathed && pathed->data) + obj = (_object_t*)pathed->data; + if(!_IS_VAR(obj)) + goto _retry; + } +#endif /* MB_ENABLE_LAMBDA */ + if(obj->data.variable->data->type == _DT_ROUTINE) { + obj = obj->data.variable->data; + + goto _retry; + } +#ifdef MB_ENABLE_CLASS + pathed = _search_identifier_in_scope_chain(s, 0, obj->data.variable->name, _PN(obj->data.variable->pathing), 0, 0); + if(pathed && pathed->data) + obj = (_object_t*)pathed->data; + /* Fall through */ +#else /* MB_ENABLE_CLASS */ + mb_unrefvar(pathed); +#endif /* MB_ENABLE_CLASS */ + case _DT_ROUTINE: + obj = _GET_ROUTINE(obj); + routine = obj->data.routine; +#ifdef MB_ENABLE_CLASS +#ifdef MB_ENABLE_LAMBDA + if(routine->type != MB_RT_LAMBDA) { +#else /* MB_ENABLE_LAMBDA */ + { +#endif /* MB_ENABLE_LAMBDA */ + bool_t is_a0 = false; + bool_t is_a1 = false; + if(s->last_instance && routine->instance) { + _traverse_class(s->last_instance->created_from, 0, _is_a_class, _META_LIST_MAX_DEPTH, true, routine->instance->created_from, &is_a0); + _traverse_class(routine->instance->created_from, 0, _is_a_class, _META_LIST_MAX_DEPTH, true, s->last_instance->created_from, &is_a1); + } + if(routine->instance && ( + !s->last_instance || ( + s->last_instance && + !is_a0 && !is_a1 && + s->last_instance->created_from != routine->instance && + routine->instance->created_from != s->last_instance + ) + ) + ) { + pathed = _search_identifier_in_class(s, routine->instance, routine->name, 0, 0); + } else { + pathed = _search_identifier_in_scope_chain(s, 0, routine->name, _PATHING_NONE, 0, 0); + } + if(pathed && pathed->data) { + obj = (_object_t*)pathed->data; + obj = _GET_ROUTINE(obj); + routine = obj->data.routine; + } + } +#endif /* MB_ENABLE_CLASS */ +#ifdef MB_ENABLE_CLASS + s->calling = true; +#endif /* MB_ENABLE_CLASS */ + result = _eval_routine(s, &ast, 0, 0, routine, _has_routine_lex_arg, _pop_routine_lex_arg); +#ifdef MB_ENABLE_CLASS + s->calling = false; +#endif /* MB_ENABLE_CLASS */ + if(ast) + ast = ast->prev; + + break; + default: /* Do nothing */ + break; + } + + *l = ast; + +_exit: + return result; +} + +/* DEF statement */ +static int _core_def(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _running_context_t* running = 0; + _object_t* obj = 0; + _var_t* var = 0; + _ls_node_t* rnode = 0; + _routine_t* routine = 0; + + mb_assert(s && l); + + running = s->running_context; + + ast = (_ls_node_t*)*l; + ast = ast->next; + + _using_jump_set_of_structured(s, ast, _exit, result); + + if(s->has_run) + goto _skip; + + obj = ast ? (_object_t*)ast->data : 0; + if(!_IS_ROUTINE(obj)) { + _handle_error_on_obj(s, SE_RN_ROUTINE_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(obj->data.routine->func.basic.entry) { + _handle_error_on_obj(s, SE_RN_DUPLICATE_ROUTINE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + routine = (_routine_t*)((_object_t*)ast->data)->data.routine; + ast = ast->next; + obj = (_object_t*)ast->data; + if(!_IS_FUNC(obj, _core_open_bracket)) { + _handle_error_on_obj(s, SE_RN_OPEN_BRACKET_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + obj = (_object_t*)ast->data; + while(obj && !_IS_FUNC(obj, _core_close_bracket)) { + if(_IS_VAR(obj)) { + var = obj->data.variable; + rnode = _search_identifier_in_scope_chain(s, routine->func.basic.scope, var->name, _PATHING_NONE, 0, 0); + if(rnode) + var = ((_object_t*)rnode->data)->data.variable; + if(!routine->func.basic.parameters) + routine->func.basic.parameters = _ls_create(); + _ls_pushback(routine->func.basic.parameters, var); + } else if(_IS_FUNC(obj, _core_args)) { + if(!routine->func.basic.parameters) + routine->func.basic.parameters = _ls_create(); + _ls_pushback(routine->func.basic.parameters, (void*)&_VAR_ARGS); + ast = ast->next; + obj = (_object_t*)ast->data; + + break; + } + + ast = ast->next; + obj = ast ? (_object_t*)ast->data : 0; + } + if(ast) ast = ast->next; + routine->func.basic.entry = ast; + +_skip: + _skip_to(s, &ast, _core_enddef, _DT_INVALID); + + if(ast) ast = ast->next; + +_exit: + *l = ast; + + return result; +} + +/* ENDDEF statement */ +static int _core_enddef(mb_interpreter_t* s, void** l) { + int result = MB_SUB_RETURN; + _ls_node_t* ast = 0; + _ls_node_t* sub_stack = 0; + + mb_assert(s && l); + + sub_stack = s->sub_stack; + + ast = (_ls_node_t*)_ls_popback(sub_stack); + if(!ast) { + _handle_error_on_obj(s, SE_RN_NO_RETURN_POINT, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + *l = ast; + +_exit: + return result; +} + +/* ... (variable argument list) statement */ +static int _core_args(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _ls_node_t* var_args = 0; + bool_t pushed = false; + + mb_assert(s && l); + + ast = (_ls_node_t*)*l; + if(ast) ast = ast->next; + *l = ast; + + var_args = s->var_args; + if(var_args) { + _object_t* obj = (_object_t*)_ls_popfront(var_args); + if(obj) { + mb_value_t arg; + mb_make_nil(arg); + _internal_object_to_public_value(obj, &arg); + mb_check(mb_push_value(s, l, arg)); + _UNREF(obj) + pushed = true; + _destroy_object_capsule_only(obj, 0); + } + } + + if(!pushed) { + mb_value_t arg; + mb_make_nil(arg); + arg.type = MB_DT_UNKNOWN; + mb_check(mb_push_value(s, l, arg)); + } + + return result; +} + +#ifdef MB_ENABLE_CLASS +/* CLASS statement */ +static int _core_class(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _running_context_t* running = 0; + _object_t* obj = 0; + _class_t* instance = 0; + _class_t* inherit = 0; + _class_t* last_inst = 0; + + mb_assert(s && l); + + running = s->running_context; + + ast = (_ls_node_t*)*l; + ast = ast->next; + + _using_jump_set_of_structured(s, ast, _exit, result); + + if(s->has_run) { + if(ast) { + _skip_to(s, &ast, _core_endclass, _DT_NIL); + + ast = ast->next; + } + + *l = ast; + + return result; + } + + obj = (_object_t*)ast->data; + obj = _GET_CLASS(obj); + if(!_IS_CLASS(obj)) { + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + instance = obj->data.instance; + ast = ast->next; + obj = (_object_t*)ast->data; + + last_inst = s->last_instance; + s->last_instance = instance; + + /* Process meta prototype list */ + if(_IS_FUNC(obj, _core_open_bracket)) { + do { + ast = ast->next; + obj = (_object_t*)ast->data; + if(_IS_VAR(obj)) { + _ls_node_t* tmp =_search_identifier_in_scope_chain(s, _OUTTER_SCOPE(running), obj->data.variable->name, _PATHING_NONE, 0, 0); + if(tmp && tmp->data) + obj = (_object_t*)tmp->data; + } + obj = _GET_CLASS(obj); + if(!_IS_CLASS(obj)) { + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + inherit = obj->data.instance; + _link_meta_class(s, instance, inherit); + ast = ast->next; + obj = (_object_t*)ast->data; + } while(_IS_CLASS(obj) || _IS_SEP(obj, ',')); + if(_IS_FUNC(obj, _core_close_bracket)) { + ast = ast->next; + } else { + _handle_error_on_obj(s, SE_RN_CLOSE_BRACKET_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + } + + *l = ast; + + /* Execute class body */ + running = _push_scope_by_class(s, instance->scope); + do { + result = _execute_statement(s, (_ls_node_t**)l, true); + if(result != MB_FUNC_OK) { + if(result >= MB_EXTENDED_ABORT) + s->last_error = SE_EA_EXTENDED_ABORT; + _handle_error_now(s, s->last_error, s->last_error_file, result); + + goto _pop_exit; + } + ast = (_ls_node_t*)*l; + if(!ast) + break; + obj = (_object_t*)ast->data; + } while(ast && !_IS_FUNC(obj, _core_endclass)); + _pop_scope(s, false); + + /* Search for meta functions */ + if(_search_class_hash_and_compare_functions(s, instance) != MB_FUNC_OK) { + _handle_error_on_obj(s, SE_RN_HASH_AND_COMPARE_MUST_BE_PROVIDED_TOGETHER, s->source_file, DON(ast), MB_FUNC_WARNING, _exit, result); + } + + /* Finished */ + if(ast) { + _skip_to(s, &ast, _core_endclass, _DT_NIL); + + ast = ast->next; + } + +_pop_exit: + if(result != MB_FUNC_OK) + _pop_scope(s, false); + +_exit: + *l = ast; + + s->last_instance = last_inst; + + return result; +} + +/* ENDCLASS statement */ +static int _core_endclass(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + _do_nothing(s, l, _exit, result); + +_exit: + return result; +} + +/* NEW statement */ +static int _core_new(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + _object_t obj; + _object_t tgt; + mb_value_t ret; + _class_t* instance = 0; + + mb_assert(s && l); + + mb_make_nil(ret); + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_pop_value(s, l, &arg)); + + mb_check(mb_attempt_func_end(s, l)); + + _MAKE_NIL(&obj); + _MAKE_NIL(&tgt); + switch(arg.type) { + case MB_DT_STRING: + arg.value.string = mb_strupr(arg.value.string); + if((instance = _reflect_string_to_class(s, arg.value.string, &arg)) == 0) + goto _default; + _ref(&instance->ref, instance); + /* Fall through */ + case MB_DT_CLASS: + _public_value_to_internal_object(&arg, &obj); + _clone_object(s, &obj, &tgt, false, true); + ret.type = MB_DT_CLASS; + ret.value.instance = tgt.data.instance; + + break; + default: +_default: + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + + mb_check(mb_push_value(s, l, ret)); + _assign_public_value(s, &ret, 0, false); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* VAR statement */ +static int _core_var(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_IGNORE; + _ls_node_t* ast = 0; + mb_unrefvar(s); + + ast = (_ls_node_t*)*l; + ast = ast->next; + + if(!s->last_instance) { + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + } + +_exit: + *l = ast; + + return result; +} + +/* REFLECT statement */ +static int _core_reflect(mb_interpreter_t* s, void** l) { +#ifdef MB_ENABLE_COLLECTION_LIB + int result = MB_FUNC_OK; + mb_value_t arg; + _object_t obj; + mb_value_t ret; + _class_t* instance = 0; + _dict_t* coll = 0; + + mb_assert(s && l); + + mb_make_nil(ret); + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_pop_value(s, l, &arg)); + + mb_check(mb_attempt_func_end(s, l)); + + _MAKE_NIL(&obj); + switch(arg.type) { + case MB_DT_STRING: + arg.value.string = mb_strupr(arg.value.string); + if((instance = _reflect_string_to_class(s, arg.value.string, &arg)) == 0) + goto _default; + _ref(&instance->ref, instance); + /* Fall through */ + case MB_DT_CLASS: + _public_value_to_internal_object(&arg, &obj); + coll = _create_dict(s); + _traverse_class(obj.data.instance, _reflect_class_field, 0, _META_LIST_MAX_DEPTH, false, coll, 0); + ret.type = MB_DT_DICT; + ret.value.dict = coll; + + break; + default: +_default: + _handle_error_on_obj(s, SE_RN_CLASS_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + + mb_check(mb_push_value(s, l, ret)); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +#else /* MB_ENABLE_COLLECTION_LIB */ + mb_unrefvar(s); + mb_unrefvar(l); + + return MB_FUNC_ERR; +#endif /* MB_ENABLE_COLLECTION_LIB */ +} +#endif /* MB_ENABLE_CLASS */ + +#ifdef MB_ENABLE_LAMBDA +/* LAMBDA statement */ +static int _core_lambda(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t ret; + _running_context_t* running = 0; + _routine_t* routine = 0; + _ls_node_t* ast = 0; + int brackets = 0; + _var_t* var = 0; + _object_t* obj = 0; + unsigned ul = 0; + bool_t popped = false; + + mb_assert(s && l); + + /* Create lambda struct */ + routine = (_routine_t*)mb_malloc(sizeof(_routine_t)); + running = _init_lambda(s, routine); + + /* Parameter list */ + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _error); + + while(mb_has_arg(s, l)) { +#ifdef MB_ENABLE_CLASS + unsigned char fp = _PATHING_NONE; +#endif /* MB_ENABLE_CLASS */ + void* v = 0; + + if(!routine->func.lambda.parameters) + routine->func.lambda.parameters = _ls_create(); + + ast = (_ls_node_t*)*l; + if(ast && _IS_FUNC(ast->data, _core_args)) { + _ls_pushback(routine->func.lambda.parameters, (void*)&_VAR_ARGS); + ast = ast->next; + *l = ast; + + break; + } + + _mb_check_mark_exit(mb_get_var(s, l, &v, true), result, _error); + + if(!_IS_VAR(v)) { + _handle_error_on_obj(s, SE_RN_INVALID_LAMBDA, s->source_file, DON2(l), MB_FUNC_ERR, _error, result); + } + var = ((_object_t*)v)->data.variable; +#ifdef MB_ENABLE_CLASS + fp = var->pathing; +#endif /* MB_ENABLE_CLASS */ + + /* Add lambda parameters */ + obj = 0; + var = _create_var(&obj, var->name, 0, true); +#ifdef MB_ENABLE_CLASS + var->pathing = fp; +#endif /* MB_ENABLE_CLASS */ + ul = _ht_set_or_insert(routine->func.lambda.scope->var_dict, var->name, obj); + mb_assert(ul); + _ls_pushback(routine->func.lambda.parameters, var); + + ast = (_ls_node_t*)*l; + if(_IS_FUNC(ast->data, _core_close_bracket)) + break; + *l = ast; + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _error); + + /* Lambda body */ + ast = (_ls_node_t*)*l; + if(ast) ast = ast->prev; + while(ast && _IS_EOS(ast->next->data)) + ast = ast->next; + *l = ast; + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _error); + + ast = (_ls_node_t*)*l; + routine->func.lambda.entry = ast; + while(ast && (brackets || !_IS_FUNC(ast->data, _core_close_bracket))) { + if(_IS_FUNC(ast->data, _core_open_bracket)) + brackets++; + else if(_IS_FUNC(ast->data, _core_close_bracket)) + brackets--; + + if(ast && !_is_valid_lambda_body_node(s, &routine->func.lambda, (_object_t*)ast->data)) { + _handle_error_on_obj(s, SE_RN_INVALID_LAMBDA, s->source_file, DON2(l), MB_FUNC_ERR, _error, result); + } + + /* Mark upvalues */ + if(ast) + _try_mark_upvalue(s, routine, (_object_t*)ast->data); + + ast = ast->next; + } + *l = ast; + routine->func.lambda.end = ast; + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _error); + + _pop_scope(s, false); + popped = true; + + /* Push the return value */ + ret.type = MB_DT_ROUTINE; + ret.value.routine = routine; + + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _error); + + /* Error processing */ + while(0) { +_error: + if(!popped) + _pop_scope(s, false); + if(routine) + _destroy_routine(s, routine); + } + + return result; +} +#endif /* MB_ENABLE_LAMBDA */ + +#ifdef MB_ENABLE_ALLOC_STAT +/* MEM statement */ +static int _core_mem(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_attempt_func_end(s, l)); + + mb_check(mb_push_int(s, l, (int_t)_mb_allocated)); + + return result; +} +#endif /* MB_ENABLE_ALLOC_STAT */ + +/* TYPE statement */ +static int _core_type(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + int i = 0; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(arg); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_value(s, l, &arg)); + if(arg.type == MB_DT_STRING) { + mb_data_e types[] = { + MB_DT_NIL, + MB_DT_UNKNOWN, + MB_DT_INT, + MB_DT_REAL, + MB_DT_NUM, + MB_DT_STRING, + MB_DT_TYPE, + MB_DT_USERTYPE, +#ifdef MB_ENABLE_USERTYPE_REF + MB_DT_USERTYPE_REF, +#endif /* MB_ENABLE_USERTYPE_REF */ + MB_DT_ARRAY, +#ifdef MB_ENABLE_COLLECTION_LIB + MB_DT_LIST, + MB_DT_LIST_IT, + MB_DT_DICT, + MB_DT_DICT_IT, + MB_DT_COLLECTION, + MB_DT_ITERATOR, +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + MB_DT_CLASS, +#endif /* MB_ENABLE_CLASS */ + MB_DT_ROUTINE + }; + for(i = 0; i < countof(types); i++) { + unsigned e = types[i]; + if(!mb_stricmp(mb_get_type_string((mb_data_e)e), arg.value.string)) { + arg.value.type = (mb_data_e)e; + arg.type = MB_DT_TYPE; + + goto _found; + } + } + } + os = _try_overridden(s, l, &arg, _CORE_ID_TYPE, MB_MF_FUNC); + if((os & MB_MS_DONE) == MB_MS_NONE) { + arg.value.type = arg.type; + arg.type = MB_DT_TYPE; + } + +_found: + mb_check(mb_attempt_close_bracket(s, l)); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + mb_check(mb_push_value(s, l, arg)); + } + + return result; +} + +/* IMPORT statement */ +static int _core_import(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_attempt_func_end(s, l)); + + return result; +} + +/* END statement */ +static int _core_end(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + + mb_assert(s && l); + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_attempt_func_end(s, l)); + + result = MB_FUNC_END; + + return result; +} + +/** Standard lib */ + +/* Get the absolute value of a number */ +static int _std_abs(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + switch(arg.type) { + case MB_DT_INT: + arg.value.integer = (int_t)abs(arg.value.integer); + + break; + case MB_DT_REAL: + arg.value.float_point = (real_t)fabs(arg.value.float_point); + + break; + default: + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + + break; + } + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the sign of a number */ +static int _std_sgn(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + switch(arg.type) { + case MB_DT_INT: + arg.value.integer = sgn(arg.value.integer); + + break; + case MB_DT_REAL: + arg.value.integer = sgn(arg.value.float_point); + arg.type = MB_DT_INT; + + break; + default: + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + + break; + } + _mb_check_mark_exit(mb_push_int(s, l, arg.value.integer), result, _exit); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* Get the square root of a number */ +static int _std_sqr(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, sqrt, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the greatest integer not greater than a number */ +static int _std_floor(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + switch(arg.type) { + case MB_DT_INT: /* Do nothing */ + break; + case MB_DT_REAL: + arg.value.integer = (int_t)floor(arg.value.float_point); + arg.type = MB_DT_INT; + + break; + default: + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + + break; + } + _mb_check_mark_exit(mb_push_int(s, l, arg.value.integer), result, _exit); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* Get the least integer not less than a number */ +static int _std_ceil(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + switch(arg.type) { + case MB_DT_INT: /* Do nothing */ + break; + case MB_DT_REAL: + arg.value.integer = (int_t)ceil(arg.value.float_point); + arg.type = MB_DT_INT; + + break; + default: + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + + break; + } + _mb_check_mark_exit(mb_push_int(s, l, arg.value.integer), result, _exit); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* Get the integer format of a number */ +static int _std_fix(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + switch(arg.type) { + case MB_DT_INT: /* Do nothing */ + break; + case MB_DT_REAL: + arg.value.integer = (int_t)(arg.value.float_point); + arg.type = MB_DT_INT; + + break; + default: + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + + break; + } + _mb_check_mark_exit(mb_push_int(s, l, arg.value.integer), result, _exit); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* Get the rounded integer of a number */ +static int _std_round(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + switch(arg.type) { + case MB_DT_INT: /* Do nothing */ + break; + case MB_DT_REAL: + arg.value.integer = (int_t)(arg.value.float_point + (real_t)0.5f); + arg.type = MB_DT_INT; + + break; + default: + _handle_error_on_obj(s, SE_RN_NUMBER_EXPECTED, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + + break; + } + _mb_check_mark_exit(mb_push_int(s, l, arg.value.integer), result, _exit); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* Set a random seed */ +static int _std_srnd(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + int_t seed = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_int(s, l, &seed)); + + mb_check(mb_attempt_close_bracket(s, l)); + + srand((unsigned)seed); + + return result; +} + +/* Get a random value among 0 ~ 1 or among given bounds */ +static int _std_rnd(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + real_t rnd = (real_t)0.0f; + + mb_assert(s && l); + + ast = (_ls_node_t*)*l; + + if(ast && ast->next && _IS_FUNC(ast->next->data, _core_open_bracket)) { + int_t lw = 0; + int_t hg = 0; + + mb_check(mb_attempt_open_bracket(s, l)); + + if(mb_has_arg(s, l)) { + mb_check(mb_pop_int(s, l, &hg)); + } + if(mb_has_arg(s, l)) { + lw = hg; + mb_check(mb_pop_int(s, l, &hg)); + } + + mb_check(mb_attempt_close_bracket(s, l)); + + if(lw >= hg || hg > RAND_MAX) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + rnd = (real_t)(rand() % (hg - lw + 1) + lw); /* [LOW, HIGH] */ + + mb_check(mb_push_int(s, l, (int_t)rnd)); + } else { + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_attempt_func_end(s, l)); + + rnd = (real_t)(((real_t)(rand() % 101)) / 100.0f); /* [0.0, 1.0] */ + + mb_check(mb_push_real(s, l, rnd)); + } + +_exit: + return result; +} + +/* Get the sin value of a number */ +static int _std_sin(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, sin, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the cos value of a number */ +static int _std_cos(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, cos, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the tan value of a number */ +static int _std_tan(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, tan, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the asin value of a number */ +static int _std_asin(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, asin, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the acos value of a number */ +static int _std_acos(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, acos, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the atan value of a number */ +static int _std_atan(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, atan, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the exp value of a number */ +static int _std_exp(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, exp, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the log value of a number */ +static int _std_log(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _math_calculate_fun_real(s, l, arg, log, _exit, result); + +_exit: + mb_check(mb_push_value(s, l, arg)); + + return result; +} + +/* Get the ASCII code of a character */ +static int _std_asc(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + char* arg = 0; + int_t val = 0; +#ifdef MB_ENABLE_UNICODE + size_t sz = 0; +#endif /* MB_ENABLE_UNICODE */ + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_string(s, l, &arg)); + + mb_check(mb_attempt_close_bracket(s, l)); + + if(arg[0] == _ZERO_CHAR) { + result = MB_FUNC_ERR; + + goto _exit; + } +#ifdef MB_ENABLE_UNICODE + sz = (size_t)mb_uu_ischar(arg); + if(sizeof(int_t) < sz) { + sz = sizeof(int_t); + _handle_error_on_obj(s, SE_RN_OVERFLOW, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + } + memcpy(&val, arg, sz); +#else /* MB_ENABLE_UNICODE */ + val = (int_t)arg[0]; +#endif /* MB_ENABLE_UNICODE */ + mb_check(mb_push_int(s, l, val)); + +_exit: + return result; +} + +/* Get the character of an ASCII code */ +static int _std_chr(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + int_t arg = 0; + char* chr = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_int(s, l, &arg)); + + mb_check(mb_attempt_close_bracket(s, l)); + + chr = (char*)mb_malloc(sizeof(arg) + 1); + memset(chr, 0, sizeof(arg) + 1); + memcpy(chr, &arg, sizeof(arg)); + mb_check(mb_push_string(s, l, chr)); + + return result; +} + +/* Get a number of characters from the left of a string */ +static int _std_left(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + char* arg = 0; + int_t count = 0; + char* sub = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_string(s, l, &arg)); + mb_check(mb_pop_int(s, l, &count)); + + mb_check(mb_attempt_close_bracket(s, l)); + + if(count <= 0) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + +#ifdef MB_ENABLE_UNICODE + switch(mb_uu_substr(arg, 0, (int)count, &sub)) { + case 0: + _handle_error_on_obj(s, SE_RN_INVALID_STRING, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + case -1: + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_UNICODE */ + sub = (char*)mb_malloc(count + 1); + memcpy(sub, arg, count); + sub[count] = _ZERO_CHAR; +#endif /* MB_ENABLE_UNICODE */ + mb_check(mb_push_string(s, l, sub)); + +_exit: + return result; +} + +/* Get a number of characters from a specific position of a string */ +static int _std_mid(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + char* arg = 0; + int_t start = 0; + int_t count = 0; + char* sub = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_string(s, l, &arg)); + mb_check(mb_pop_int(s, l, &start)); + mb_check(mb_pop_int(s, l, &count)); + + mb_check(mb_attempt_close_bracket(s, l)); + + if(count <= 0 || start < 0 || start >= (int_t)mb_strlen(arg)) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + +#ifdef MB_ENABLE_UNICODE + switch(mb_uu_substr(arg, start, (int)count, &sub)) { + case 0: + _handle_error_on_obj(s, SE_RN_INVALID_STRING, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + case -1: + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_UNICODE */ + sub = (char*)mb_malloc(count + 1); + memcpy(sub, arg + start, count); + sub[count] = _ZERO_CHAR; +#endif /* MB_ENABLE_UNICODE */ + mb_check(mb_push_string(s, l, sub)); + +_exit: + return result; +} + +/* Get a number of characters from the right of a string */ +static int _std_right(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + char* arg = 0; + int_t count = 0; + char* sub = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_string(s, l, &arg)); + mb_check(mb_pop_int(s, l, &count)); + + mb_check(mb_attempt_close_bracket(s, l)); + + if(count <= 0) { + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + +#ifdef MB_ENABLE_UNICODE + switch(mb_uu_substr(arg, (int)(mb_uu_strlen(arg) - count), (int)count, &sub)) { + case 0: + _handle_error_on_obj(s, SE_RN_INVALID_STRING, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + case -1: + _handle_error_on_obj(s, SE_RN_INDEX_OUT_OF_BOUND, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } +#else /* MB_ENABLE_UNICODE */ + sub = (char*)mb_malloc(count + 1); + memcpy(sub, arg + (mb_strlen(arg) - count), count); + sub[count] = _ZERO_CHAR; +#endif /* MB_ENABLE_UNICODE */ + mb_check(mb_push_string(s, l, sub)); + +_exit: + return result; +} + +/* Get the string format of a number */ +static int _std_str(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + _dynamic_buffer_t buf; + size_t lbuf = 32; + + mb_assert(s && l); + + mb_make_nil(arg); + + _INIT_BUF(buf); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + switch(arg.type) { + case MB_DT_INT: + lbuf = 32; /* Enough for even 64bit integer */ + _RESIZE_CHAR_BUF(buf, lbuf); + if((size_t)sprintf(_CHAR_BUF_PTR(buf), MB_INT_FMT, arg.value.integer) >= lbuf) { + mb_assert(0 && "Buffer overflow."); + } + + break; + case MB_DT_REAL: + lbuf = 1 /* - */ + (DBL_MAX_10_EXP + 1) /* 308 + 1 digits */ + 1 /* . */ + 6 /* precision */ + 1 /* \0 */; + _RESIZE_CHAR_BUF(buf, lbuf); +#ifdef MB_MANUAL_REAL_FORMATTING + _real_to_str(arg.value.float_point, _CHAR_BUF_PTR(buf), lbuf, 5); +#else /* MB_MANUAL_REAL_FORMATTING */ + mb_realtostr(arg.value.float_point, _CHAR_BUF_PTR(buf), lbuf); +#endif /* MB_MANUAL_REAL_FORMATTING */ + + break; + case MB_DT_STRING: { + char* ret = mb_strdup(arg.value.string, mb_strlen(arg.value.string) + 1); + mb_check(mb_push_string(s, l, ret)); + + goto _exit; + } + case MB_DT_TYPE: { + const char* sp = mb_get_type_string(arg.value.type); + char* ret = mb_strdup(sp, strlen(sp) + 1); + mb_check(mb_push_string(s, l, ret)); + + goto _exit; + } +#ifdef MB_ENABLE_CLASS + case MB_DT_CLASS: { + bool_t got_tostr = false; + _class_t* instance = (_class_t*)arg.value.instance; + _object_t val_obj; + _MAKE_NIL(&val_obj); + if((result = _format_class_to_string(s, l, instance, &val_obj, &got_tostr)) == MB_FUNC_OK) { + if(got_tostr) { + mb_check(mb_push_string(s, l, val_obj.data.string)); + } else { + const char* sp = mb_get_type_string(_internal_type_to_public_type(_DT_CLASS)); + char* ret = mb_strdup(sp, strlen(sp) + 1); + mb_check(mb_push_string(s, l, ret)); + } + + goto _exit; + } else { + goto _exit; + } + } +#endif /* MB_ENABLE_CLASS */ + default: + result = MB_FUNC_ERR; + + goto _exit; + } + mb_check(mb_push_string(s, l, _HEAP_CHAR_BUF(buf))); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* Get the number format of a string, or get the value of a dictionary iterator */ +static int _std_val(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + char* conv_suc = 0; + mb_value_t arg; +#ifdef MB_ENABLE_COLLECTION_LIB + _object_t ocoi; +#endif /* MB_ENABLE_COLLECTION_LIB */ + mb_value_t ret; + mb_meta_status_e os = MB_MS_NONE; +#ifdef MB_ENABLE_COLLECTION_LIB + _ls_node_t* ast = 0; +#endif /* MB_ENABLE_COLLECTION_LIB */ + + mb_assert(s && l); + + mb_make_nil(arg); + mb_make_nil(ret); + + mb_check(mb_attempt_open_bracket(s, l)); + +#ifdef MB_ENABLE_COLLECTION_LIB + ast = (_ls_node_t*)*l; + if(ast && _IS_FUNC(ast->data, _coll_iterator)) { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } +#endif /* MB_ENABLE_COLLECTION_LIB */ + mb_check(mb_pop_value(s, l, &arg)); + os = _try_overridden(s, l, &arg, _STD_ID_VAL, MB_MF_FUNC); + if((os & MB_MS_DONE) == MB_MS_NONE) { + switch(arg.type) { + case MB_DT_STRING: + ret.value.integer = (int_t)mb_strtol(arg.value.string, &conv_suc, 0); + if(*conv_suc == _ZERO_CHAR) { + ret.type = MB_DT_INT; + mb_check(mb_push_value(s, l, ret)); + + goto _exit; + } + ret.value.float_point = (real_t)mb_strtod(arg.value.string, &conv_suc); + if(*conv_suc == _ZERO_CHAR) { + ret.type = MB_DT_REAL; + mb_check(mb_push_value(s, l, ret)); + + goto _exit; + } + result = MB_FUNC_ERR; + + break; +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST_IT: + _MAKE_NIL(&ocoi); + _public_value_to_internal_object(&arg, &ocoi); + _handle_error_on_obj(s, SE_RN_UNEXPECTED_TYPE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + case MB_DT_DICT_IT: + _MAKE_NIL(&ocoi); + _public_value_to_internal_object(&arg, &ocoi); + if(ocoi.data.dict_it && ocoi.data.dict_it->curr_node && ocoi.data.dict_it->curr_node != _INVALID_DICT_IT && ocoi.data.dict_it->curr_node->data) { + _internal_object_to_public_value((_object_t*)ocoi.data.dict_it->curr_node->data, &ret); + mb_check(mb_push_value(s, l, ret)); + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ + default: + _assign_public_value(s, &arg, 0, true); + _handle_error_on_obj(s, SE_RN_UNEXPECTED_TYPE, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } else { + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + mb_check(mb_push_value(s, l, ret)); + } + } + +_exit: + mb_check(mb_attempt_close_bracket(s, l)); + + return result; +} + +/* Get the length of a string or an array, or element count of a collection or a variable argument list */ +static int _std_len(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + mb_value_t arg; + _array_t* arr = 0; +#ifdef MB_ENABLE_COLLECTION_LIB + _list_t* lst = 0; + _dict_t* dct = 0; +#endif /* MB_ENABLE_COLLECTION_LIB */ + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + ast = (_ls_node_t*)*l; + if(ast) obj = (_object_t*)ast->data; + if(obj && _IS_FUNC(obj, _core_args)) { + ast = ast->next; + *l = ast; + _mb_check_mark_exit(mb_push_int(s, l, s->var_args ? (int_t)_ls_count(s->var_args) : 0), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + goto _exit; + } + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + os = _try_overridden(s, l, &arg, _STD_ID_LEN, MB_MF_FUNC); + if((os & MB_MS_DONE) == MB_MS_NONE) { + switch(arg.type) { + case MB_DT_STRING: +#ifdef MB_ENABLE_UNICODE + _mb_check_mark_exit(mb_push_int(s, l, (int_t)mb_uu_strlen(arg.value.string)), result, _exit); +#else /* MB_ENABLE_UNICODE */ + _mb_check_mark_exit(mb_push_int(s, l, (int_t)mb_strlen(arg.value.string)), result, _exit); +#endif /* MB_ENABLE_UNICODE */ + + break; + case MB_DT_ARRAY: + arr = (_array_t*)arg.value.array; + _mb_check_mark_exit(mb_push_int(s, l, (int_t)arr->count), result, _exit); + + break; +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: + lst = (_list_t*)arg.value.list; + _mb_check_mark_exit(mb_push_int(s, l, (int_t)lst->count), result, _exit); + _assign_public_value(s, &arg, 0, true); + + break; + case MB_DT_DICT: + dct = (_dict_t*)arg.value.dict; + _mb_check_mark_exit(mb_push_int(s, l, (int_t)_ht_count(dct->dict)), result, _exit); + _assign_public_value(s, &arg, 0, true); + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ + default: + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + + break; + } + } else { + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_int(s, l, 0), result, _exit); + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + +_exit: + _assign_public_value(s, &arg, 0, true); + + return result; +} + +/* GET statement */ +static int _std_get(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t ov; + mb_value_t arg; + _object_t obj; +#ifdef MB_ENABLE_COLLECTION_LIB + int_t index = 0; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + char* field = 0; + _ls_node_t* fnode = 0; + _object_t* fobj = 0; +#endif /* MB_ENABLE_CLASS */ + mb_value_t ret; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(ov); + mb_make_nil(arg); + mb_make_nil(ret); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &ov), result, _exit); + os = _try_overridden(s, l, &ov, _STD_ID_GET, MB_MF_FUNC); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _MAKE_NIL(&obj); + switch(ov.type) { +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: + _public_value_to_internal_object(&ov, &obj); + _mb_check_mark_exit(mb_pop_int(s, l, &index), result, _exit); + if(!_at_list(obj.data.list, index, &ret)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&ov, &obj); + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + if(!_find_dict(obj.data.dict, &arg, &ret)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + case MB_DT_LIST_IT: + _public_value_to_internal_object(&ov, &obj); + if(obj.data.list_it && !obj.data.list_it->list->range_begin && obj.data.list_it->curr.node && obj.data.list_it->curr.node->data) { + _internal_object_to_public_value((_object_t*)obj.data.list_it->curr.node->data, &ret); + } else if(obj.data.list_it && obj.data.list_it->list->range_begin) { + mb_make_int(ret, obj.data.list_it->curr.ranging); + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; + case MB_DT_DICT_IT: + _public_value_to_internal_object(&ov, &obj); + if(obj.data.dict_it && obj.data.dict_it->curr_node && obj.data.dict_it->curr_node != _INVALID_DICT_IT && obj.data.dict_it->curr_node->extra) { + _internal_object_to_public_value((_object_t*)obj.data.dict_it->curr_node->extra, &ret); + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case MB_DT_CLASS: + _public_value_to_internal_object(&ov, &obj); + _mb_check_mark_exit(mb_pop_string(s, l, &field), result, _exit); + field = mb_strupr(field); + fnode = _search_identifier_in_class(s, obj.data.instance, field, 0, 0); + if(fnode && fnode->data) { + fobj = (_object_t*)fnode->data; + _internal_object_to_public_value(fobj, &ret); + } + + break; +#endif /* MB_ENABLE_CLASS */ + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_OR_ITERATOR_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _exit); + } + +_exit: + _assign_public_value(s, &ov, 0, true); + + return result; +} + +/* SET statement */ +static int _std_set(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t ov; + mb_value_t key; + mb_value_t val; + _object_t obj; +#ifdef MB_ENABLE_COLLECTION_LIB + _object_t* oval = 0; + int_t idx = 0; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + char* field = 0; + _ls_node_t* fnode = 0; + _object_t* fobj = 0; + mb_value_t nv; +#endif /* MB_ENABLE_CLASS */ + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(ov); + mb_make_nil(key); + mb_make_nil(val); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &ov), result, _exit); + os = _try_overridden(s, l, &ov, _STD_ID_SET, MB_MF_FUNC); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _MAKE_NIL(&obj); + switch(ov.type) { +#ifdef MB_ENABLE_COLLECTION_LIB + case MB_DT_LIST: + _public_value_to_internal_object(&ov, &obj); + while(mb_has_arg(s, l)) { + mb_make_nil(val); + _mb_check_mark_exit(mb_pop_int(s, l, &idx), result, _exit); + _mb_check_mark_exit(mb_pop_value(s, l, &val), result, _exit); + if(!_set_list(obj.data.list, idx, &val, &oval)) { + if(oval) + _destroy_object(oval, 0); + + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&ov, &obj); + while(mb_has_arg(s, l)) { + mb_make_nil(key); + mb_make_nil(val); + _mb_check_mark_exit(mb_pop_value(s, l, &key), result, _exit); + _mb_check_mark_exit(mb_pop_value(s, l, &val), result, _exit); + if(!_set_dict(obj.data.dict, &key, &val, 0, 0)) { + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + + break; +#endif /* MB_ENABLE_COLLECTION_LIB */ +#ifdef MB_ENABLE_CLASS + case MB_DT_CLASS: + mb_make_nil(nv); + + _public_value_to_internal_object(&ov, &obj); + _mb_check_mark_exit(mb_pop_string(s, l, &field), result, _exit); + _mb_check_mark_exit(mb_pop_value(s, l, &nv), result, _exit); + field = mb_strupr(field); + fnode = _search_identifier_in_class(s, obj.data.instance, field, 0, 0); + if(fnode && _IS_VAR(fnode->data)) { + _object_t* nobj = 0; + fobj = (_object_t*)fnode->data; + _destroy_object(fobj->data.variable->data, 0); + _create_internal_object_from_public_value(&nv, &nobj); + fobj->data.variable->data = nobj; + } else { + _handle_error_on_obj(s, SE_RN_VAR_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + break; +#endif /* MB_ENABLE_CLASS */ + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, ov), result, _exit); + } + +_exit: + _assign_public_value(s, &ov, 0, true); + + return result; +} + +/* PRINT statement */ +static int _std_print(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + _object_t val_obj; + _object_t* val_ptr = 0; + bool_t pathed_str = false; + + mb_assert(s && l); + + ++s->no_eat_comma_mark; + ast = (_ls_node_t*)*l; + if(!ast || !ast->next || !ast->next->data) { + _handle_error_on_obj(s, SE_RN_SYNTAX_ERROR, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + + obj = (_object_t*)ast->data; + do { + pathed_str = false; + val_ptr = &val_obj; + _MAKE_NIL(val_ptr); + switch(obj->type) { + case _DT_VAR: + if(obj->data.variable->data->type == _DT_ROUTINE) { + obj = obj->data.variable->data; + val_ptr = _eval_var_in_print(s, &val_ptr, &ast, obj); + + goto _print; + } +#ifdef MB_ENABLE_CLASS + if(obj->data.variable->pathing) { + _ls_node_t* pathed = _search_identifier_in_scope_chain(s, 0, obj->data.variable->name, _PU(obj->data.variable->pathing), 0, 0); + if(pathed && pathed->data) { + if(obj != (_object_t*)pathed->data) { + obj = (_object_t*)pathed->data; + val_ptr = _eval_var_in_print(s, &val_ptr, &ast, obj); + if(val_ptr->type == _DT_STRING) + pathed_str = true; + } + } + + goto _print; + } +#endif /* MB_ENABLE_CLASS */ + /* Fall through */ + case _DT_ARRAY: /* Fall through */ + case _DT_NIL: /* Fall through */ + case _DT_INT: /* Fall through */ + case _DT_REAL: /* Fall through */ + case _DT_STRING: /* Fall through */ + case _DT_TYPE: /* Fall through */ +#ifdef MB_ENABLE_CLASS + case _DT_CLASS: /* Fall through */ +#endif /* MB_ENABLE_CLASS */ + case _DT_FUNC: /* Fall through */ + case _DT_ROUTINE: + result = _calc_expression(s, &ast, &val_ptr); + if(val_ptr->type == _DT_ROUTINE) { +#ifdef MB_ENABLE_LAMBDA + if(val_ptr->data.routine->type != MB_RT_LAMBDA) + val_ptr->is_ref = true; +#else /* MB_ENABLE_LAMBDA */ + val_ptr->is_ref = true; +#endif /* MB_ENABLE_LAMBDA */ + } + _REF(val_ptr) + _UNREF(val_ptr) +_print: + if(val_ptr->type == _DT_NIL) { + _get_printer(s)(s, MB_NIL); + } else if(val_ptr->type == _DT_INT) { + _get_printer(s)(s, MB_INT_FMT, val_ptr->data.integer); + } else if(val_ptr->type == _DT_REAL) { +#ifdef MB_MANUAL_REAL_FORMATTING + _dynamic_buffer_t buf; + size_t lbuf = 32; + _INIT_BUF(buf); + _RESIZE_CHAR_BUF(buf, lbuf); + _real_to_str(val_ptr->data.float_point, _CHAR_BUF_PTR(buf), lbuf, 5); + _get_printer(s)(s, "%s", _CHAR_BUF_PTR(buf)); + _DISPOSE_BUF(buf); +#else /* MB_MANUAL_REAL_FORMATTING */ + _get_printer(s)(s, MB_REAL_FMT, val_ptr->data.float_point); +#endif /* MB_MANUAL_REAL_FORMATTING */ + } else if(val_ptr->type == _DT_STRING) { + _print_string(s, val_ptr); + if(!val_ptr->is_ref && val_ptr->data.string && !pathed_str) { + safe_free(val_ptr->data.string); + } +#ifdef MB_ENABLE_USERTYPE_REF + } else if(val_ptr->type == _DT_USERTYPE_REF) { + if(val_ptr->data.usertype_ref->fmt) { + _dynamic_buffer_t buf; + size_t lbuf = 0; + _INIT_BUF(buf); + while((lbuf = (size_t)val_ptr->data.usertype_ref->fmt(s, val_ptr->data.usertype_ref->usertype, _CHAR_BUF_PTR(buf), (unsigned)_CHARS_OF_BUF(buf))) > _CHARS_OF_BUF(buf)) { + _RESIZE_CHAR_BUF(buf, lbuf); + } + _get_printer(s)(s, "%s", _CHAR_BUF_PTR(buf)); + _DISPOSE_BUF(buf); + } else { + _get_printer(s)(s, mb_get_type_string(_internal_type_to_public_type(val_ptr->type))); + } +#endif /* MB_ENABLE_USERTYPE_REF */ + } else if(val_ptr->type == _DT_TYPE) { + _get_printer(s)(s, mb_get_type_string(val_ptr->data.type)); +#ifdef MB_ENABLE_CLASS + } else if(val_ptr->type == _DT_CLASS) { + bool_t got_tostr = false; + _class_t* instance = val_ptr->data.instance; + val_ptr = &val_obj; + _MAKE_NIL(val_ptr); + if((result = _format_class_to_string(s, (void**)&ast, instance, val_ptr, &got_tostr)) == MB_FUNC_OK) { + if(got_tostr) { + obj = val_ptr; + + goto _print; + } else { + _get_printer(s)(s, mb_get_type_string(_internal_type_to_public_type(_DT_CLASS))); + } + } else { + goto _exit; + } +#endif /* MB_ENABLE_CLASS */ + } else { + _get_printer(s)(s, mb_get_type_string(_internal_type_to_public_type(val_ptr->type))); + } + if(result != MB_FUNC_OK) + goto _exit; + /* Fall through */ + case _DT_SEP: + if(!ast) + break; + obj = (_object_t*)ast->data; +#if _COMMA_AS_NEWLINE + if(obj->data.separator == ',') { +#else /* _COMMA_AS_NEWLINE */ + if(obj->data.separator == ';') { +#endif /* _COMMA_AS_NEWLINE */ + _get_printer(s)(s, "\n"); + } + + break; + default: + _handle_error_on_obj(s, SE_CM_NOT_SUPPORTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + + break; + } + + if(!ast) + break; + obj = (_object_t*)ast->data; + if(_is_print_terminal(s, obj)) + break; + if(_IS_SEP(obj, ',') || _IS_SEP(obj, ';')) { + ast = ast->next; + obj = (_object_t*)ast->data; + } else { + _handle_error_on_obj(s, SE_RN_COMMA_OR_SEMICOLON_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + } while(ast && !_IS_SEP(obj, ':') && !_IS_FUNC(obj, _core_close_bracket) && (obj->type == _DT_SEP || !_is_expression_terminal(s, obj))); + +_exit: + --s->no_eat_comma_mark; + + *l = ast; + if(result != MB_FUNC_OK) + _get_printer(s)(s, "\n"); + + return result; +} + +/* INPUT statement */ +static int _std_input(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + _ls_node_t* ast = 0; + _object_t* obj = 0; + char line[_INPUT_MAX_LENGTH]; + char* conv_suc = 0; + const char* pmt = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_func_begin(s, l)); + + mb_check(mb_attempt_func_end(s, l)); + + ast = (_ls_node_t*)*l; + obj = ast ? (_object_t*)ast->data : 0; + + if(!obj || _IS_EOS(obj)) { +#ifdef MB_CP_VC + getch(); +#else /* MB_CP_VC */ + _get_inputer(s)(s, pmt, line, sizeof(line)); +#endif /* MB_CP_VC */ + + goto _exit; + } + if(obj->type == _DT_STRING) { + pmt = obj->data.string; +#if MB_PRINT_INPUT_PROMPT + _print_string(s, obj); +#endif /* MB_PRINT_INPUT_PROMPT */ + ast = ast->next; + obj = (_object_t*)ast->data; + if(!_IS_SEP(obj, ',')) { + _handle_error_on_obj(s, SE_RN_COMMA_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + ast = ast->next; + obj = (_object_t*)ast->data; + } + if(!_IS_VAR(obj)) { + _handle_error_on_obj(s, SE_RN_VAR_EXPECTED, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + if(obj->data.variable->data->type == _DT_INT || obj->data.variable->data->type == _DT_REAL) { + _get_inputer(s)(s, pmt, line, sizeof(line)); + obj->data.variable->data->type = _DT_INT; + obj->data.variable->data->data.integer = (int_t)mb_strtol(line, &conv_suc, 0); + if(*conv_suc == _ZERO_CHAR) { +#if MB_PRINT_INPUT_CONTENT + _get_printer(s)(s, MB_INT_FMT "\n", obj->data.variable->data->data.integer); +#endif /* MB_PRINT_INPUT_CONTENT */ + } else { + obj->data.variable->data->type = _DT_REAL; + obj->data.variable->data->data.float_point = (real_t)mb_strtod(line, &conv_suc); + if(*conv_suc == _ZERO_CHAR) { +#if MB_PRINT_INPUT_CONTENT + _get_printer(s)(s, MB_REAL_FMT "\n", obj->data.variable->data->data.float_point); +#endif /* MB_PRINT_INPUT_CONTENT */ + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + } + ast = ast->next; + } else if(obj->data.variable->data->type == _DT_STRING) { + size_t len = 0; + if(obj->data.variable->data->data.string && !obj->data.variable->data->is_ref) { + safe_free(obj->data.variable->data->data.string); + } + len = (size_t)_get_inputer(s)(s, pmt, line, sizeof(line)); +#if defined MB_CP_VC && defined MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING + do { + _dynamic_buffer_t buf; + _dynamic_buffer_t wbuf; + _INIT_BUF(buf); + _INIT_BUF(wbuf); + while((len = (size_t)mb_bytes_to_wchar_ansi(line, &_WCHAR_BUF_PTR(wbuf), _WCHARS_OF_BUF(wbuf))) > _WCHARS_OF_BUF(wbuf)) { + _RESIZE_WCHAR_BUF(wbuf, len); + } + while((len = mb_wchar_to_bytes(_WCHAR_BUF_PTR(wbuf), &_CHAR_BUF_PTR(buf), _CHARS_OF_BUF(buf))) > _CHARS_OF_BUF(buf)) { + _RESIZE_CHAR_BUF(buf, len); + } +#if MB_PRINT_INPUT_CONTENT + _get_printer(s)(s, "%ls\n", _WCHAR_BUF_PTR(wbuf)); +#endif /* MB_PRINT_INPUT_CONTENT */ + _DISPOSE_BUF(wbuf); + obj->data.variable->data->data.string = _HEAP_CHAR_BUF(buf); + obj->data.variable->data->is_ref = false; + } while(0); +#else /* MB_CP_VC && MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING */ + obj->data.variable->data->data.string = mb_memdup(line, (unsigned)(len + 1)); +#if MB_PRINT_INPUT_CONTENT + _get_printer(s)(s, "%s\n", obj->data.variable->data->data.string); +#endif /* MB_PRINT_INPUT_CONTENT */ +#endif /* MB_CP_VC && MB_ENABLE_UNICODE && MB_UNICODE_NEED_CONVERTING*/ + ast = ast->next; + } else { + _handle_error_on_obj(s, SE_RN_INVALID_ID_USAGE, s->source_file, DON(ast), MB_FUNC_ERR, _exit, result); + } + +_exit: + *l = ast; + + return result; +} + +/** Collection lib */ + +#ifdef MB_ENABLE_COLLECTION_LIB +/* LIST statement */ +static int _coll_list(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + _list_t* coll = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + coll = _create_list(s); + + if(mb_has_arg(s, l)) { + _ls_node_t* ast = 0; + _object_t* obj = 0; + mb_make_nil(arg); + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _error); + ast = (_ls_node_t*)*l; + if(ast) obj = (_object_t*)ast->data; + if(arg.type == MB_DT_INT && obj && _IS_FUNC(obj, _core_to)) { + /* Push a range of integer */ + int_t begin = arg.value.integer; + int_t end = 0; + int_t step = 0; + ast = ast->next; + _mb_check_mark_exit(mb_pop_int(s, (void**)&ast, &end), result, _error); + step = sgn(end - begin); + coll->range_begin = (int_t*)mb_malloc(sizeof(int_t)); + *coll->range_begin = begin; + coll->count = end - begin + step; + if(!coll->count) coll->count = 1; + *l = ast; + } else { + /* Push arguments */ + if(!_push_list(coll, &arg, 0)) { + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON2(l), MB_FUNC_ERR, _error, result); + } + while(mb_has_arg(s, l)) { + mb_make_nil(arg); + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _error); + if(!_push_list(coll, &arg, 0)) { + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON2(l), MB_FUNC_ERR, _error, result); + } + } + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _error); + + arg.type = MB_DT_LIST; + arg.value.list = coll; + _mb_check_mark_exit(mb_push_value(s, l, arg), result, _error); + + while(0) { +_error: + mb_make_nil(arg); + mb_push_value(s, l, arg); + _destroy_list(coll); + } + + return result; +} + +/* DICT statement */ +static int _coll_dict(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t arg; + mb_value_t val; + _dict_t* coll = 0; + + mb_assert(s && l); + + mb_check(mb_attempt_open_bracket(s, l)); + + coll = _create_dict(s); + + while(mb_has_arg(s, l)) { + mb_make_nil(arg); + mb_make_nil(val); + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _error); + _mb_check_mark_exit(mb_pop_value(s, l, &val), result, _error); + if(!_set_dict(coll, &arg, &val, 0, 0)) { + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON2(l), MB_FUNC_ERR, _error, result); + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _error); + + arg.type = MB_DT_DICT; + arg.value.dict = coll; + _mb_check_mark_exit(mb_push_value(s, l, arg), result, _error); + + while(0) { +_error: + mb_make_nil(arg); + mb_push_value(s, l, arg); + _destroy_dict(coll); + } + + return result; +} + +/* PUSH statement */ +static int _coll_push(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + mb_value_t arg; + _object_t olst; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_PUSH, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + if(coll.type != MB_DT_LIST) { + _handle_error_on_obj(s, SE_RN_LIST_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + _MAKE_NIL(&olst); + _public_value_to_internal_object(&coll, &olst); + + while(mb_has_arg(s, l)) { + mb_make_nil(arg); + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + if(!_push_list(olst.data.list, &arg, 0)) { + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, coll), result, _exit); + } + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* POP statement */ +static int _coll_pop(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + mb_value_t val; + _object_t olst; + _object_t ocoll; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(val); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_value(s, l, &coll)); + os = _try_overridden(s, l, &coll, _COLL_ID_POP, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + if(coll.type != MB_DT_LIST) { + _assign_public_value(s, &coll, 0, true); + _handle_error_on_obj(s, SE_RN_LIST_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + _MAKE_NIL(&olst); + _public_value_to_internal_object(&coll, &olst); + if(_pop_list(olst.data.list, &val, s)) { + mb_check(mb_push_value(s, l, val)); + _MAKE_NIL(&ocoll); + _public_value_to_internal_object(&val, &ocoll); + _UNREF(&ocoll) + + _assign_public_value(s, &coll, 0, true); + } else { + mb_check(mb_push_value(s, l, val)); + + _assign_public_value(s, &coll, 0, true); + + _handle_error_on_obj(s, SE_RN_EMPTY_COLLECTION, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + } + } else { + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + mb_check(mb_push_value(s, l, val)); + } + } + + mb_check(mb_attempt_close_bracket(s, l)); + +_exit: + return result; +} + +/* BACK statement */ +static int _coll_back(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + mb_value_t val; + _object_t olst; + _object_t* oval = 0; + _ls_node_t* node = 0; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(val); + + mb_check(mb_attempt_open_bracket(s, l)); + + mb_check(mb_pop_value(s, l, &coll)); + os = _try_overridden(s, l, &coll, _COLL_ID_BACK, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + if(coll.type != MB_DT_LIST) { + _assign_public_value(s, &coll, 0, true); + _handle_error_on_obj(s, SE_RN_LIST_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + _MAKE_NIL(&olst); + _public_value_to_internal_object(&coll, &olst); + node = _ls_back(olst.data.list->list); + oval = node ? (_object_t*)node->data : 0; + if(oval) { + _internal_object_to_public_value(oval, &val); + + mb_check(mb_push_value(s, l, val)); + + _assign_public_value(s, &coll, 0, true); + } else { + mb_check(mb_push_value(s, l, val)); + + _assign_public_value(s, &coll, 0, true); + + _handle_error_on_obj(s, SE_RN_EMPTY_COLLECTION, s->source_file, DON2(l), MB_FUNC_WARNING, _exit, result); + } + } else { + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + mb_check(mb_push_value(s, l, val)); + } + } + + mb_check(mb_attempt_close_bracket(s, l)); + +_exit: + return result; +} + +/* INSERT statement */ +static int _coll_insert(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + int_t idx = 0; + mb_value_t arg; + _object_t olst; + _object_t* oval = 0; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(arg); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_INSERT, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _mb_check_mark_exit(mb_pop_int(s, l, &idx), result, _exit); + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + if(coll.type != MB_DT_LIST) { + _handle_error_on_obj(s, SE_RN_LIST_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + _MAKE_NIL(&olst); + _public_value_to_internal_object(&coll, &olst); + + if(!_insert_list(olst.data.list, idx, &arg, &oval)) { + if(oval) + _destroy_object(oval, 0); + + _handle_error_on_obj(s, SE_RN_INVALID_EXPRESSION, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + + _mb_check_mark_exit(mb_push_value(s, l, coll), result, _exit); + } else { + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, coll), result, _exit); + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* SORT statement */ +static int _coll_sort(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + _object_t olst; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_SORT, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + if(coll.type != MB_DT_LIST) { + _handle_error_on_obj(s, SE_RN_LIST_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + _MAKE_NIL(&olst); + _public_value_to_internal_object(&coll, &olst); + + _sort_list(olst.data.list); + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, coll), result, _exit); + } + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* EXISTS statement */ +static int _coll_exists(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + mb_value_t arg; + _object_t ocoll; + mb_value_t ret; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(arg); + mb_make_nil(ret); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_EXISTS, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _mb_check_mark_exit(mb_pop_value(s, l, &arg), result, _exit); + + _MAKE_NIL(&ocoll); + switch(coll.type) { + case MB_DT_LIST: + _public_value_to_internal_object(&coll, &ocoll); + mb_make_bool(ret, _find_list(ocoll.data.list, &arg, 0)); + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + mb_make_bool(ret, _find_dict(ocoll.data.dict, &arg, 0)); + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _exit); + } else { + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, coll), result, _exit); + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* INDEX_OF statement */ +static int _coll_index_of(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + int idx = 0; + mb_value_t coll; + _object_t ocoll; + mb_value_t val; + mb_value_t ret; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(val); + mb_make_nil(ret); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_INDEX_OF, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + ret.type = MB_DT_UNKNOWN; + _mb_check_mark_exit(mb_pop_value(s, l, &val), result, _exit); + _MAKE_NIL(&ocoll); + switch(coll.type) { + case MB_DT_LIST: + _public_value_to_internal_object(&coll, &ocoll); + if(_find_list(ocoll.data.list, &val, &idx)) { + mb_make_int(ret, (int_t)idx); + } + + break; + default: + _handle_error_on_obj(s, SE_RN_LIST_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _exit); + } + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* REMOVE statement */ +static int _coll_remove(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + int_t idx = 0; + mb_value_t key; + _object_t ocoll; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(key); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_REMOVE, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _MAKE_NIL(&ocoll); + switch(coll.type) { + case MB_DT_LIST: + _public_value_to_internal_object(&coll, &ocoll); + while(mb_has_arg(s, l)) { + _mb_check_mark_exit(mb_pop_int(s, l, &idx), result, _exit); + + if(!_remove_at_list(ocoll.data.list, idx)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + while(mb_has_arg(s, l)) { + _mb_check_mark_exit(mb_pop_value(s, l, &key), result, _exit); + + if(!_remove_dict(ocoll.data.dict, &key)) { + _handle_error_on_obj(s, SE_RN_CANNOT_FIND_WITH_THE_SPECIFIC_INDEX, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + } + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, coll), result, _exit); + } + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* CLEAR statement */ +static int _coll_clear(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + _object_t ocoll; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_CLEAR, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _MAKE_NIL(&ocoll); + switch(coll.type) { + case MB_DT_LIST: + _public_value_to_internal_object(&coll, &ocoll); + _clear_list(ocoll.data.list); + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + _clear_dict(ocoll.data.dict); + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, coll), result, _exit); + } + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* CLONE statement */ +static int _coll_clone(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + _object_t ocoll; + _object_t otgt; + mb_value_t ret; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(ret); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _MAKE_NIL(&ocoll); + _MAKE_NIL(&otgt); + switch(coll.type) { +#ifdef MB_ENABLE_USERTYPE_REF + case MB_DT_USERTYPE_REF: + _public_value_to_internal_object(&coll, &ocoll); + _clone_usertype_ref(ocoll.data.usertype_ref, &otgt); + _internal_object_to_public_value(&otgt, &ret); + + break; +#endif /* MB_ENABLE_USERTYPE_REF */ + case MB_DT_LIST: + _public_value_to_internal_object(&coll, &ocoll); + _clone_object(s, &ocoll, &otgt, false, true); + ret.type = MB_DT_LIST; + ret.value.list = otgt.data.list; + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + _clone_object(s, &ocoll, &otgt, false, true); + ret.type = MB_DT_DICT; + ret.value.dict = otgt.data.dict; + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _exit); + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* TO_ARRAY statement */ +static int _coll_to_array(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + _object_t ocoll; + _array_t* array = 0; + _array_helper_t helper; + mb_value_t ret; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(ret); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + _MAKE_NIL(&ocoll); + switch(coll.type) { + case MB_DT_LIST: + _public_value_to_internal_object(&coll, &ocoll); + array = _create_array(s, mb_strdup("", 1), _DT_REAL); + array->count = ocoll.data.list->count; + array->dimension_count = 1; + array->dimensions[0] = ocoll.data.list->count; + _init_array(array); + helper.s = s; + helper.array = array; + helper.index = 0; + _LS_FOREACH(ocoll.data.list->list, _do_nothing_on_object, _copy_list_to_array, &helper); + ret.type = MB_DT_ARRAY; + ret.value.array = array; + + break; + default: + _handle_error_on_obj(s, SE_RN_LIST_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _exit); + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* ITERATOR statement */ +static int _coll_iterator(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t coll; + _object_t ocoll; + _list_it_t* lit = 0; + _dict_it_t* dit = 0; + mb_value_t ret; + mb_meta_status_e os = MB_MS_NONE; + + mb_assert(s && l); + + mb_make_nil(coll); + mb_make_nil(ret); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + _mb_check_mark_exit(mb_pop_value(s, l, &coll), result, _exit); + os = _try_overridden(s, l, &coll, _COLL_ID_ITERATOR, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _MAKE_NIL(&ocoll); + switch(coll.type) { + case MB_DT_LIST: + _public_value_to_internal_object(&coll, &ocoll); + lit = _create_list_it(ocoll.data.list, false); + ret.type = MB_DT_LIST_IT; + ret.value.list_it = lit; + + break; + case MB_DT_DICT: + _public_value_to_internal_object(&coll, &ocoll); + dit = _create_dict_it(ocoll.data.dict, false); + ret.type = MB_DT_DICT_IT; + ret.value.dict_it = dit; + + break; + default: + _handle_error_on_obj(s, SE_RN_COLLECTION_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _exit); + } + +_exit: + _assign_public_value(s, &coll, 0, true); + + return result; +} + +/* MOVE_NEXT statement */ +static int _coll_move_next(mb_interpreter_t* s, void** l) { + int result = MB_FUNC_OK; + mb_value_t it; + _object_t oit; + mb_value_t ret; + mb_meta_status_e os = MB_MS_NONE; + _ls_node_t* ast = 0; + + mb_assert(s && l); + + mb_make_nil(it); + mb_make_nil(ret); + + _mb_check_mark_exit(mb_attempt_open_bracket(s, l), result, _exit); + + ast = (_ls_node_t*)*l; + if(ast && _IS_FUNC(ast->data, _coll_iterator)) { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + _mb_check_mark_exit(mb_pop_value(s, l, &it), result, _exit); + os = _try_overridden(s, l, &it, _COLL_ID_MOVE_NEXT, MB_MF_COLL); + if((os & MB_MS_DONE) == MB_MS_NONE) { + _MAKE_NIL(&oit); + switch(it.type) { + case MB_DT_LIST_IT: + _public_value_to_internal_object(&it, &oit); + oit.data.list_it = _move_list_it_next(oit.data.list_it); + if(_invalid_list_it(oit.data.list_it)) { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } else if(oit.data.list_it) { + mb_make_bool(ret, true); + } else { + mb_make_nil(ret); + } + + break; + case MB_DT_DICT_IT: + _public_value_to_internal_object(&it, &oit); + if(oit.data.dict_it && oit.data.dict_it->curr_node == _INVALID_DICT_IT && _invalid_dict_it(oit.data.dict_it)) { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } + oit.data.dict_it = _move_dict_it_next(oit.data.dict_it); + if(_invalid_dict_it(oit.data.dict_it)) { + _handle_error_on_obj(s, SE_RN_INVALID_ITERATOR, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + } else if(oit.data.dict_it) { + mb_make_bool(ret, true); + } else { + mb_make_nil(ret); + } + + break; + default: + _assign_public_value(s, &it, 0, true); + _handle_error_on_obj(s, SE_RN_ITERABLE_EXPECTED, s->source_file, DON2(l), MB_FUNC_ERR, _exit, result); + + break; + } + } + + _mb_check_mark_exit(mb_attempt_close_bracket(s, l), result, _exit); + + if((os & MB_MS_RETURNED) == MB_MS_NONE) { + _mb_check_mark_exit(mb_push_value(s, l, ret), result, _exit); + } + +_exit: + return result; +} +#endif /* MB_ENABLE_COLLECTION_LIB */ + +/* ========================================================} */ + +#ifdef MB_COMPACT_MODE +# pragma pack() +#endif /* MB_COMPACT_MODE */ + +#ifdef MB_CP_BORLANDC +# pragma warn .8004 +# pragma warn .8008 +# pragma warn .8012 +#endif /* MB_CP_BORLANDC */ + +#ifdef MB_CP_CLANG +# pragma clang diagnostic pop +#endif /* MB_CP_CLANG */ + +#ifdef MB_CP_VC +# pragma warning(pop) +#endif /* MB_CP_VC */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */