-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcapi.cpp
More file actions
423 lines (384 loc) · 16.3 KB
/
Copy pathcapi.cpp
File metadata and controls
423 lines (384 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
// C API gövdesi — capi.h'daki sözleşmenin uygulaması.
//
// Buradaki asıl iş ömür yönetimidir. Native CLI'da (main.cpp komutRun) Source,
// AST ve Interpreter yığında yaşar ve fonksiyon bitince yok olur. Gömme'de bu
// çalışmaz: FnObj, AST düğümlerine HAM işaretçi tutuyor (value.hpp FnObj::body).
// rs_call, rs_eval'den çok sonra çağrılacağı için AST'nin o ana kadar yaşaması
// şart. Bu yüzden rs_vm hepsinin sahibidir ve üye SIRASI önemlidir.
#include "capi.h"
#include <cstdlib>
#include <exception>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "diag.hpp"
#include "interp.hpp"
#include "lexer.hpp"
#include "parser.hpp"
#include "resolver.hpp"
#include "source.hpp"
// Üyeler bildirim sırasının TERSİNE yıkılır. Aşağıdaki sıra şunu garanti eder:
// interp -> prog -> diag -> src. Değiştirme; interp'in ortamları AST'ye,
// Diagnostics de Source'a bakıyor.
struct rs_vm {
std::unique_ptr<rs::Source> src;
std::unique_ptr<rs::Diagnostics> diag;
rs::Program prog;
std::unique_ptr<rs::Interpreter> interp;
std::vector<std::pair<std::string, std::string>> kayitli;
rs_host_fn host = nullptr;
void* user = nullptr;
std::string sonHata;
bool yuklendi = false;
// 0 = yorumlayıcının varsayılanı. Host eval'den önce de sonra da
// ayarlayabilsin diye burada bekletiliyor (interp eval'de doğuyor).
int maxDerinlik = 0;
// Dize kanalı — yalnızca host geri çağrısı sürerken anlamlı.
// aktifArg, yorumlayıcının yığınındaki argüman vektörünü gösterir;
// sahibi biz değiliz, çağrı bitince mutlaka nullptr olmalı.
const std::vector<rs::Interpreter::HostArg>* aktifArg = nullptr;
std::string donenDize;
bool donenDizeVar = false;
// Ters yön: son rs_call'ın sonucu dize ise burada tutulur (rs_result_str).
// Geri çağrıdan bağımsız — sahibi VM, ömrü bir sonraki rs_call'a kadar.
std::string sonucDize;
bool sonucDizeVar = false;
};
namespace {
// Sonuç dize kanalını sıfırlar. İç içe rs_call'da her çıkış yolunda çağrılıyor:
// içteki çağrının bıraktığı dize, dıştaki çağrının sonucu sanılmasın.
void sonucKanaliniTemizle(rs_vm* vm) {
vm->sonucDize.clear();
vm->sonucDizeVar = false;
}
// Tanı çıktısını metne alır. Renk kodları kapatılır — bu tampon bir terminale
// değil host'a gidiyor.
std::string taniMetni(const rs::Diagnostics& d) {
const bool renkVardi = std::getenv("NO_COLOR") == nullptr;
#ifdef _WIN32
if (renkVardi) { _putenv_s("NO_COLOR", "1"); }
#else
if (renkVardi) { setenv("NO_COLOR", "1", 1); }
#endif
std::ostringstream oss;
d.print(oss);
d.printSummary(oss);
#ifdef _WIN32
if (renkVardi) { _putenv_s("NO_COLOR", ""); }
#else
if (renkVardi) { unsetenv("NO_COLOR"); }
#endif
return oss.str();
}
// Geri çağrı boyunca aktif argümanları yayınlar, çıkışta ÖNCEKİ hâli geri koyar.
// Host geri çağrısı (özellikle JS/WASM tarafı) istisna fırlatabilir; elle
// sıfırlamak o yolda dangling bir işaretçi bırakırdı.
//
// ⚠️ GERİ ÇAĞRI İÇİNDEN rs_call — İÇ İÇE ÇAĞRI (4 Ağu 2026'da teşhis edildi).
// Bu kapsam eskiden çıkışta aktifArg'ı NULL'lıyor, dize kanalını da yalnızca
// GİRİŞTE temizliyordu. İkisi de tek katmanlı bir dünya varsayıyordu; oysa host
// geri çağrısının içinden rs_call yeniden çağrılabiliyor ve gömen tarafta bu
// istisna değil KURAL: Minecraft köprüsünde mc.warpIsinla ilkeli oyuncuyu
// ışınlıyor, ışınlanma anında sunucu PlayerChangedWorldEvent'i basıyor, o olayın
// dinleyicisi de betiğin dunyaDegisti fonksiyonunu çağırıyor — hepsi DIŞ ilkel
// hâlâ dönmemişken.
//
// İki kusur birden çıkıyordu:
// 1. İç çağrının son ilkeli dize döndürmüşse donenDizeVar TRUE kalıyor ve dış
// ilkelin sayı sonucu köprüde DİZE sanılıyordu. std::move edilmiş tampon da
// boş olduğu için betiğe "" gidiyordu: /warp zindan her çağrıldığında
// "int() dönüştüremedi: \"\"" — kusur betikte aranıyordu, betikte değildi.
// 2. İç çağrının kapsamı çıkışta aktifArg'ı NULL'lıyordu; dış ilkel ondan sonra
// argümanlarını (rs_arg_str) okuyamaz hâle geliyordu.
// Çözüm ikisinde de aynı: kapsam bir YIĞIN gibi davransın — girişte önceki hâli
// saklasın, çıkışta aynen geri koysun.
struct ArgKapsami {
rs_vm* vm;
const std::vector<rs::Interpreter::HostArg>* oncekiArg;
std::string oncekiDize;
bool oncekiDizeVar;
ArgKapsami(rs_vm* v, const std::vector<rs::Interpreter::HostArg>& args)
: vm(v), oncekiArg(v->aktifArg), oncekiDize(std::move(v->donenDize)),
oncekiDizeVar(v->donenDizeVar) {
vm->aktifArg = &args;
vm->donenDize.clear();
vm->donenDizeVar = false;
}
~ArgKapsami() {
vm->aktifArg = oncekiArg;
vm->donenDize = std::move(oncekiDize);
vm->donenDizeVar = oncekiDizeVar;
}
ArgKapsami(const ArgKapsami&) = delete;
ArgKapsami& operator=(const ArgKapsami&) = delete;
};
// Çağrı boyunca yorumlayıcının tanı hedefini değiştirir, çıkışta MUTLAKA geri
// takar. Elle yazılmış geri takma satırı, callGlobal beklenmedik bir istisnayla
// çıktığında atlanıyordu: yığındaki geçici Diagnostics yok oluyor, yorumlayıcı
// ise onu göstermeye devam ediyordu — bir sonraki hata ölü belleğe yazılırdı.
struct TaniKapsami {
rs::Interpreter* interp;
rs::Diagnostics* onceki;
TaniKapsami(rs::Interpreter* i, rs::Diagnostics& yeni, rs::Diagnostics& eski)
: interp(i), onceki(&eski) {
interp->setDiagnostics(yeni);
}
~TaniKapsami() { interp->setDiagnostics(*onceki); }
TaniKapsami(const TaniKapsami&) = delete;
TaniKapsami& operator=(const TaniKapsami&) = delete;
};
} // namespace
extern "C" {
rs_vm* rs_new(void) {
return new (std::nothrow) rs_vm{};
}
void rs_free(rs_vm* vm) {
delete vm;
}
void rs_set_host(rs_vm* vm, rs_host_fn cb, void* user) {
if (vm == nullptr) {
return;
}
vm->host = cb;
vm->user = user;
}
void rs_set_max_depth(rs_vm* vm, int n) {
if (vm == nullptr || n <= 0) {
return;
}
vm->maxDerinlik = n;
if (vm->interp != nullptr) {
vm->interp->setMaxDepth(n);
}
}
int rs_register(rs_vm* vm, const char* modul, const char* fn) {
if (vm == nullptr || modul == nullptr || fn == nullptr) {
return 1;
}
if (vm->yuklendi) {
vm->sonHata = "rs_register, rs_eval'den SONRA çağrıldı; "
"'include' satırları eval sırasında çözülüyor";
return 1;
}
vm->kayitli.emplace_back(modul, fn);
return 0;
}
int rs_eval(rs_vm* vm, const char* kaynak, const char* ad) {
if (vm == nullptr || kaynak == nullptr) {
return 1;
}
if (vm->yuklendi) {
vm->sonHata = "bu VM'de zaten bir betik yüklü — bir VM = bir betik "
"(birden fazla betik için rs_new ile ayrı VM aç)";
return 1;
}
vm->sonHata.clear();
vm->src = std::make_unique<rs::Source>(ad != nullptr ? ad : "<gomulu>", kaynak);
vm->diag = std::make_unique<rs::Diagnostics>(*vm->src);
rs::Lexer lexer(*vm->src, *vm->diag);
rs::Parser parser(*vm->src, lexer.tokenize(), *vm->diag);
vm->prog = parser.parseProgram();
if (vm->diag->hasErrors()) {
vm->sonHata = taniMetni(*vm->diag);
return 1;
}
// Resolver ayrı bir Diagnostics kullanır: uyarıları (host isimleri) betiğin
// kendi çıktısına karıştırmayalım, ama HATALARI yine de bildirelim.
rs::Diagnostics cozTani(*vm->src);
rs::Resolver resolver(*vm->src, cozTani);
resolver.resolve(vm->prog);
if (cozTani.hasErrors()) {
vm->sonHata = taniMetni(cozTani);
return 1;
}
vm->interp = std::make_unique<rs::Interpreter>(*vm->src, *vm->diag);
if (vm->maxDerinlik > 0) {
vm->interp->setMaxDepth(vm->maxDerinlik);
}
// Köprü, run'dan ÖNCE kurulmalı: 'include game' üst düzey bir deyim ve
// run sırasında çözülüyor.
if (vm->host != nullptr) {
rs_host_fn cb = vm->host;
void* user = vm->user;
// vm'i yakalamak güvenli: lambda interp'in içinde yaşıyor, interp ise
// rs_vm'in SON üyesi — yani vm'den önce yıkılıyor.
vm->interp->setHostBridge(
vm->kayitli,
[vm, cb, user](const std::string& modul, const std::string& fn,
const std::vector<rs::Interpreter::HostArg>& args)
-> rs::Interpreter::HostSonuc {
std::vector<double> sayilar;
sayilar.reserve(args.size());
for (const auto& a : args) {
sayilar.push_back(a.sayi);
}
const ArgKapsami kapsam(vm, args);
rs::Interpreter::HostSonuc s;
// İstisna DOĞDUĞU yerde durdurulur. rs_host_fn 'extern "C"';
// oradan çıkan bir C++ istisnasının yorumlayıcının karelerinden
// geçip rs_call'ın dışına ulaşması tanımsız davranıştı. Üstelik
// yıkıcıların çalışmadığı host'larda (tarayıcıda JS istisnası)
// her kaçış derinlik sayacından bir kare yiyebilir.
//
// ÖLÇÜM NOTU (28 Tem 2026): native'de kaçış başına sızıntı
// ÖLÇÜLMEDİ — 5.000 kaçıştan sonra ulaşılabilen derinlik 798'de
// sabit kaldı, çünkü burada yığın çözülürken yıkıcılar çalışıyor
// ve sayaç geri veriliyor. Sızıntı senaryosu yalnızca yıkıcıların
// atlandığı host'lar için TEORİK; wasm'da ölçülmedi. Düzeltmenin
// ölçülmüş gerekçesi sızıntı değil, kaçışın kendisi: eski ağaçta
// 5.000 çağrının 5.000'i rs_call'ın dışına çıkıyordu (extern "C"
// sınırını aşan istisna = tanımsız davranış), yenisinde sıfırı.
try {
const double d = cb(modul.c_str(), fn.c_str(),
sayilar.empty() ? nullptr : sayilar.data(),
static_cast<int>(sayilar.size()), user);
if (vm->donenDizeVar) {
s.dizeMi = true;
s.dize = std::move(vm->donenDize);
} else {
s.sayi = d;
}
} catch (const std::exception& ex) {
s.hataMi = true;
s.hata = "host fonksiyonu '" + modul + "." + fn +
"' istisna fırlattı: " + ex.what();
} catch (...) {
s.hataMi = true;
s.hata = "host fonksiyonu '" + modul + "." + fn +
"' C++ olmayan bir istisna fırlattı";
}
return s;
});
}
vm->yuklendi = true;
// Üst düzey deyimler de host çağırabilir; rs_call'daki son savunmanın eşi.
try {
if (!vm->interp->run(vm->prog)) {
vm->sonHata = taniMetni(*vm->diag);
return 1;
}
} catch (const std::exception& ex) {
vm->sonHata = std::string("gömme sınırını aşan istisna: ") + ex.what();
return 1;
} catch (...) {
vm->sonHata = "gömme sınırını aşan bilinmeyen istisna";
return 1;
}
return 0;
}
int rs_call(rs_vm* vm, const char* fn, const double* args, int argc, double* out) {
if (vm == nullptr || fn == nullptr) {
return 1;
}
if (!vm->yuklendi || vm->interp == nullptr) {
vm->sonHata = "önce rs_eval ile bir betik yükle";
return 1;
}
if (argc < 0 || (argc > 0 && args == nullptr)) {
vm->sonHata = "argüman sayısı ile dizi uyuşmuyor";
return 1;
}
vm->sonHata.clear();
// Dize kanalı çağrı başına kurulur; başarısız ya da sayı dönen bir çağrıdan
// sonra bir öncekinin metni okunmamalı. ⚠️ Bu temizlik TEK BAŞINA yetmiyor —
// çağrının İÇİNDEN yapılan bir rs_call kendi dizesini buraya yazıyor, o yüzden
// her çıkış yolunda (başarı, başarısızlık, istisna) yeniden temizleniyor.
sonucKanaliniTemizle(vm);
std::vector<rs::Value> degerler;
degerler.reserve(static_cast<std::size_t>(argc));
for (int i = 0; i < argc; ++i) {
degerler.emplace_back(args[i]);
}
// Çağrının hataları çağrı başına temiz bir Diagnostics'e yazılsın; aksi hâlde
// eval'den kalan uyarılar her rs_call'da tekrar basılır.
rs::Diagnostics cagriTani(*vm->src);
rs::Value sonuc{rs::Nil{}};
bool tamam = false;
{
const TaniKapsami tani(vm->interp.get(), cagriTani, *vm->diag);
// Son savunma: buraya bir istisna ulaşırsa host'a değil sonHata'ya gider.
// Host geri çağrılarınınki köprüde yakalanıyor (bkz. rs_eval); bu blok
// yorumlayıcının kendi beklenmedik istisnaları için.
try {
tamam = vm->interp->callGlobal(fn, degerler, sonuc);
} catch (const std::exception& ex) {
vm->sonHata = std::string("gömme sınırını aşan istisna: ") + ex.what();
sonucKanaliniTemizle(vm);
return 1;
} catch (...) {
vm->sonHata = "gömme sınırını aşan bilinmeyen istisna";
sonucKanaliniTemizle(vm);
return 1;
}
}
if (!tamam) {
sonucKanaliniTemizle(vm);
vm->sonHata = taniMetni(cagriTani);
// BOŞ TANI YASAK. callGlobal'ın üç başarısızlık yolu da diag'a yazıyor, ama
// ölçüldü: gömen taraf (Minecraft köprüsü) BOŞ mesajlı bir hata alabiliyor ve o
// durumda hatanın VAR OLDUĞU görülüyor, NE olduğu hiçbir yerde yazmıyor --
// /warp zindan her çağrıldığında tam olarak bu oluyordu ve kusur iki gün
// teşhissiz kaldı. Boş bir dize döndürmek, host'a "hata var ama söylemeyeceğim"
// demek; hangi tanının kaybolduğunu bilmesek bile en azından FONKSİYON ADINI ve
// çağrı tarafında kalan tanıyı verebiliriz.
if (vm->sonHata.empty()) {
vm->sonHata = taniMetni(*vm->diag);
}
if (vm->sonHata.empty()) {
vm->sonHata = std::string("'") + fn
+ "' çağrısı başarısız oldu ama yorumlayıcı tanı üretmedi"
" (bu bir RaidenScript kusuru; betiği değil dili işaret ediyor)";
}
return 1;
}
// SONUÇ KANALI BURADA DA TEMİZLENİYOR, yalnızca girişte değil. Giriş temizliği
// tek katmanlı bir dünyada yeterliydi; iç içe çağrıda değil: bu çağrının içinden
// (bir host ilkelinin içinden) yapılan rs_call kendi sonuç dizesini yazıp
// bırakıyor ve dıştaki çağrı sayı döndürdüğünde host, rs_result_str ile İÇTEKİ
// çağrının dizesini okuyordu. Yukarıdaki ArgKapsami'nin düzelttiği kusurun
// kardeşi (aynı sebep: VM'de tek katman varsayan paylaşılan kanal).
sonucKanaliniTemizle(vm);
if (const auto* sp = std::get_if<rs::Str>(&sonuc)) {
if (*sp != nullptr) {
vm->sonucDize = **sp;
vm->sonucDizeVar = true;
}
}
if (out != nullptr) {
*out = rs::toDouble(sonuc);
}
return 0;
}
const char* rs_last_error(rs_vm* vm) {
if (vm == nullptr) {
return "";
}
return vm->sonHata.c_str();
}
const char* rs_arg_str(rs_vm* vm, int i) {
if (vm == nullptr || vm->aktifArg == nullptr || i < 0) {
return nullptr;
}
if (static_cast<std::size_t>(i) >= vm->aktifArg->size()) {
return nullptr;
}
const std::string* p = (*vm->aktifArg)[static_cast<std::size_t>(i)].dize;
return p != nullptr ? p->c_str() : nullptr;
}
const char* rs_result_str(rs_vm* vm) {
if (vm == nullptr || !vm->sonucDizeVar) {
return nullptr;
}
return vm->sonucDize.c_str();
}
void rs_return_str(rs_vm* vm, const char* s) {
if (vm == nullptr || vm->aktifArg == nullptr || s == nullptr) {
return;
}
vm->donenDize = s;
vm->donenDizeVar = true;
}
} // extern "C"