botql 1.0.2 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/botql.browser.js CHANGED
@@ -19,643 +19,357 @@ var BotQL = (() => {
19
19
  var require_Parser = __commonJS({
20
20
  "Parser.js"(exports, module) {
21
21
  "use strict";
22
- var TokenType = {
23
- KEYWORD: "KEYWORD",
24
- STRING: "STRING",
25
- NUMBER: "NUMBER",
26
- IDENT: "IDENT",
27
- SYMBOL: "SYMBOL",
28
- EOF: "EOF"
29
- };
30
- var KEYWORDS = /* @__PURE__ */ new Set([
31
- "CREATE",
32
- "BOT",
33
- "PLATFORM",
34
- "CONNECT",
35
- "TABLE",
36
- "PREVENT",
37
- "DEFAULT",
38
- "ON",
39
- "START",
40
- "MESSAGE",
41
- "FROM",
42
- "WHEN",
43
- "CONTAINS",
44
- "OR",
45
- "OTHERWISE",
46
- "KEYWORDS",
47
- "THINK",
48
- "WAITING",
49
- "REPLY",
50
- "TO",
51
- "FORWARD",
52
- "PARSE",
53
- "SEND",
54
- "INSERT",
55
- "INTO",
56
- "VALUES",
57
- "UPDATE",
58
- "SET",
59
- "WHERE",
60
- "RUN",
61
- "IMPORT",
62
- "AS",
63
- "RESPONSE"
64
- ]);
22
+ var TokenType = { KEYWORD: "KEYWORD", STRING: "STRING", NUMBER: "NUMBER", IDENT: "IDENT", SYMBOL: "SYMBOL", EOF: "EOF" };
23
+ var KEYWORDS = /* @__PURE__ */ new Set(["CREATE", "BOT", "PLATFORM", "CONNECT", "TABLE", "PREVENT", "DEFAULT", "ON", "START", "MESSAGE", "FROM", "WHEN", "CONTAINS", "OR", "OTHERWISE", "KEYWORDS", "THINK", "WAITING", "REPLY", "TO", "FORWARD", "PARSE", "SEND", "INSERT", "INTO", "VALUES", "UPDATE", "SET", "WHERE", "RUN", "IMPORT", "AS", "RESPONSE"]);
65
24
  var Token = class {
66
- constructor(type, value, line) {
67
- this.type = type;
68
- this.value = value;
69
- this.line = line;
25
+ constructor(e, t, s) {
26
+ this.type = e, this.value = t, this.line = s;
70
27
  }
71
28
  };
72
29
  var Tokenizer = class {
73
- constructor(source) {
74
- this.source = source;
75
- this.pos = 0;
76
- this.line = 1;
77
- this.tokens = [];
30
+ constructor(e) {
31
+ this.source = e, this.pos = 0, this.line = 1, this.tokens = [];
78
32
  }
79
- error(msg) {
80
- throw new Error(`syntax error: ${msg}`);
33
+ error(e) {
34
+ throw Error(`syntax error: ${e}`);
81
35
  }
82
- peekChar(offset = 0) {
83
- return this.source[this.pos + offset];
36
+ peekChar(e = 0) {
37
+ return this.source[this.pos + e];
84
38
  }
85
39
  tokenize() {
86
- while (this.pos < this.source.length) {
87
- const c = this.peekChar();
88
- if (c === "\n") {
89
- this.line++;
90
- this.pos++;
40
+ for (; this.pos < this.source.length; ) {
41
+ let e = this.peekChar();
42
+ if ("\n" === e) {
43
+ this.line++, this.pos++;
91
44
  continue;
92
45
  }
93
- if (/\s/.test(c)) {
46
+ if (/\s/.test(e)) {
94
47
  this.pos++;
95
48
  continue;
96
49
  }
97
- if (c === "-" && this.peekChar(1) === "-") {
98
- while (this.pos < this.source.length && this.peekChar() !== "\n") this.pos++;
50
+ if ("-" === e && "-" === this.peekChar(1)) {
51
+ for (; this.pos < this.source.length && "\n" !== this.peekChar(); ) this.pos++;
99
52
  continue;
100
53
  }
101
- if (c === '"' || c === "'") {
102
- this.tokens.push(this.readString(c));
54
+ if ('"' === e || "'" === e) {
55
+ this.tokens.push(this.readString(e));
103
56
  continue;
104
57
  }
105
- if (/[0-9]/.test(c)) {
58
+ if (/[0-9]/.test(e)) {
106
59
  this.tokens.push(this.readNumber());
107
60
  continue;
108
61
  }
109
- if ("{}(),.=+*;".includes(c)) {
110
- this.tokens.push(new Token(TokenType.SYMBOL, c, this.line));
111
- this.pos++;
62
+ if ("{}(),.=+*;".includes(e)) {
63
+ this.tokens.push(new Token(TokenType.SYMBOL, e, this.line)), this.pos++;
112
64
  continue;
113
65
  }
114
- if (/[A-Za-z_]/.test(c)) {
115
- const word = this.readWord();
116
- this.tokens.push(word);
66
+ if (/[A-Za-z_]/.test(e)) {
67
+ let t = this.readWord();
68
+ this.tokens.push(t);
117
69
  continue;
118
70
  }
119
- this.error(`unexpected character: "${c}"`);
71
+ this.error(`unexpected character: "${e}"`);
120
72
  }
121
- this.tokens.push(new Token(TokenType.EOF, null, this.line));
122
- return this.tokens;
73
+ return this.tokens.push(new Token(TokenType.EOF, null, this.line)), this.tokens;
123
74
  }
124
- readString(quote) {
125
- const startLine = this.line;
75
+ readString(e) {
76
+ let t = this.line;
126
77
  this.pos++;
127
- let value = "";
128
- while (this.pos < this.source.length && this.peekChar() !== quote) {
129
- if (this.peekChar() === "\\") {
130
- this.pos++;
131
- value += this.peekChar();
132
- this.pos++;
78
+ let s = "";
79
+ for (; this.pos < this.source.length && this.peekChar() !== e; ) {
80
+ if ("\\" === this.peekChar()) {
81
+ this.pos++, s += this.peekChar(), this.pos++;
133
82
  continue;
134
83
  }
135
- if (this.peekChar() === "\n") this.line++;
136
- value += this.peekChar();
137
- this.pos++;
84
+ "\n" === this.peekChar() && this.line++, s += this.peekChar(), this.pos++;
138
85
  }
139
- if (this.peekChar() !== quote) {
140
- this.error("unterminated string");
141
- }
142
- this.pos++;
143
- return new Token(TokenType.STRING, value, startLine);
86
+ return this.peekChar() !== e && this.error("unterminated string"), this.pos++, new Token(TokenType.STRING, s, t);
144
87
  }
145
88
  readNumber() {
146
- const startLine = this.line;
147
- let value = "";
148
- while (this.pos < this.source.length && /[0-9.]/.test(this.peekChar())) {
149
- value += this.peekChar();
150
- this.pos++;
151
- }
152
- return new Token(TokenType.NUMBER, Number(value), startLine);
89
+ let e = this.line, t = "";
90
+ for (; this.pos < this.source.length && /[0-9.]/.test(this.peekChar()); ) t += this.peekChar(), this.pos++;
91
+ return new Token(TokenType.NUMBER, Number(t), e);
153
92
  }
154
93
  readWord() {
155
- const startLine = this.line;
156
- let value = "";
157
- while (this.pos < this.source.length && /[A-Za-z0-9_]/.test(this.peekChar())) {
158
- value += this.peekChar();
159
- this.pos++;
160
- }
161
- const upper = value.toUpperCase();
162
- if (KEYWORDS.has(upper) && value === upper) {
163
- return new Token(TokenType.KEYWORD, upper, startLine);
164
- }
165
- return new Token(TokenType.IDENT, value, startLine);
94
+ let e = this.line, t = "";
95
+ for (; this.pos < this.source.length && /[A-Za-z0-9_]/.test(this.peekChar()); ) t += this.peekChar(), this.pos++;
96
+ let s = t.toUpperCase();
97
+ return KEYWORDS.has(s) && t === s ? new Token(TokenType.KEYWORD, s, e) : new Token(TokenType.IDENT, t, e);
166
98
  }
167
99
  };
168
100
  var Parser = class {
169
- constructor(source) {
170
- this.tokens = new Tokenizer(source).tokenize();
171
- this.pos = 0;
101
+ constructor(e) {
102
+ this.tokens = new Tokenizer(e).tokenize(), this.pos = 0;
172
103
  }
173
- error(msg) {
174
- const tok = this.current();
175
- throw new Error(`syntax error: ${msg}, near "${tok ? tok.value : "EOF"}"`);
104
+ error(e) {
105
+ let t = this.current();
106
+ throw Error(`syntax error: ${e}, near "${t ? t.value : "EOF"}"`);
176
107
  }
177
108
  current() {
178
109
  return this.tokens[this.pos];
179
110
  }
180
- at(type, value) {
181
- const tok = this.current();
182
- if (tok.type !== type) return false;
183
- if (value !== void 0 && tok.value !== value) return false;
184
- return true;
111
+ at(e, t) {
112
+ let s = this.current();
113
+ return s.type === e && (void 0 === t || s.value === t);
185
114
  }
186
- atKeyword(...values) {
187
- return this.at(TokenType.KEYWORD) && values.includes(this.current().value);
115
+ atKeyword(...e) {
116
+ return this.at(TokenType.KEYWORD) && e.includes(this.current().value);
188
117
  }
189
- // Casa por valor, independente do token ser KEYWORD ou IDENT.
190
- // Necessário para palavras como "SIGNAL", que tanto introduz um
191
- // evento (ON SIGNAL FROM) como é usada como identificador (SIGNAL.PAIR).
192
- atWord(...values) {
193
- const tok = this.current();
194
- return (tok.type === TokenType.KEYWORD || tok.type === TokenType.IDENT) && values.includes(tok.value);
118
+ atWord(...e) {
119
+ let t = this.current();
120
+ return (t.type === TokenType.KEYWORD || t.type === TokenType.IDENT) && e.includes(t.value);
195
121
  }
196
122
  advance() {
197
- const tok = this.current();
198
- if (tok.type !== TokenType.EOF) this.pos++;
199
- return tok;
123
+ let e = this.current();
124
+ return e.type !== TokenType.EOF && this.pos++, e;
200
125
  }
201
- expect(type, value) {
202
- if (!this.at(type, value)) {
203
- this.error(`expected ${value || type}`);
204
- }
205
- return this.advance();
126
+ expect(e, t) {
127
+ return this.at(e, t) || this.error(`expected ${t || e}`), this.advance();
206
128
  }
207
- expectKeyword(value) {
208
- return this.expect(TokenType.KEYWORD, value);
129
+ expectKeyword(e) {
130
+ return this.expect(TokenType.KEYWORD, e);
209
131
  }
210
132
  isAtEnd() {
211
133
  return this.at(TokenType.EOF);
212
134
  }
213
- // ---- Programa ----
214
135
  parseProgram() {
215
- const statements = [];
216
- while (!this.isAtEnd()) {
217
- statements.push(this.parseStatement());
218
- }
219
- return { type: "Program", body: statements };
136
+ let e = [];
137
+ for (; !this.isAtEnd(); ) e.push(this.parseStatement());
138
+ return { type: "Program", body: e };
220
139
  }
221
- // ---- Bloco { ... } ou statement único ----
222
- // Regra da linguagem: bloco com mais de uma ação usa chaves,
223
- // uma única ação não precisa.
224
140
  parseBody() {
225
141
  if (this.at(TokenType.SYMBOL, "{")) {
226
142
  this.advance();
227
- const statements = [];
228
- while (!this.at(TokenType.SYMBOL, "}")) {
229
- if (this.isAtEnd()) this.error('unclosed block "{"');
230
- statements.push(this.parseStatement());
231
- }
232
- this.advance();
233
- return statements;
143
+ let e = [];
144
+ for (; !this.at(TokenType.SYMBOL, "}"); ) this.isAtEnd() && this.error('unclosed block "{"'), e.push(this.parseStatement());
145
+ return this.advance(), e;
234
146
  }
235
147
  return [this.parseStatement()];
236
148
  }
237
- // ---- Dispatch de statement ----
238
149
  parseStatement() {
239
- if (this.atKeyword("CREATE")) return this.parseCreate();
240
- if (this.atKeyword("PLATFORM")) return this.parsePlatform();
241
- if (this.atKeyword("CONNECT")) return this.parseConnect();
242
- if (this.atKeyword("ON")) return this.parseOn();
243
- if (this.atKeyword("WHEN")) return this.parseWhen();
244
- if (this.atKeyword("OTHERWISE")) return this.parseOtherwise();
245
- if (this.atKeyword("REPLY")) return this.parseReply();
246
- if (this.atKeyword("THINK")) return this.parseThink();
247
- if (this.atKeyword("FORWARD")) return this.parseForward();
248
- if (this.atKeyword("PARSE")) return this.parseParseSignal();
249
- if (this.atKeyword("SEND")) return this.parseSend();
250
- if (this.atKeyword("INSERT")) return this.parseInsert();
251
- if (this.atKeyword("UPDATE")) return this.parseUpdate();
252
- if (this.atKeyword("RUN")) return this.parseRunBot();
253
- if (this.atKeyword("IMPORT")) return this.parseImport();
254
- this.error("unexpected statement");
255
- }
256
- // ---- CREATE BOT / CREATE TABLE ----
150
+ return this.atKeyword("CREATE") ? this.parseCreate() : this.atKeyword("PLATFORM") ? this.parsePlatform() : this.atKeyword("CONNECT") ? this.parseConnect() : this.atKeyword("ON") ? this.parseOn() : this.atKeyword("WHEN") ? this.parseWhen() : this.atKeyword("OTHERWISE") ? this.parseOtherwise() : this.atKeyword("REPLY") ? this.parseReply() : this.atKeyword("THINK") ? this.parseThink() : this.atKeyword("FORWARD") ? this.parseForward() : this.atKeyword("PARSE") ? this.parseParseSignal() : this.atKeyword("SEND") ? this.parseSend() : this.atKeyword("INSERT") ? this.parseInsert() : this.atKeyword("UPDATE") ? this.parseUpdate() : this.atKeyword("RUN") ? this.parseRunBot() : this.atKeyword("IMPORT") ? this.parseImport() : void this.error("unexpected statement");
151
+ }
257
152
  parseCreate() {
258
- this.expectKeyword("CREATE");
259
- if (this.atKeyword("BOT")) {
153
+ if (this.expectKeyword("CREATE"), this.atKeyword("BOT")) {
260
154
  this.advance();
261
- const name = this.expect(TokenType.STRING).value;
262
- return { type: "CreateBot", name };
155
+ let e = this.expect(TokenType.STRING).value;
156
+ return { type: "CreateBot", name: e };
263
157
  }
264
158
  if (this.atKeyword("TABLE")) {
265
159
  this.advance();
266
- const name = this.expect(TokenType.IDENT).value;
267
- const columns = this.parseColumnList();
268
- const preventDefault = this.tryParsePreventDefault();
269
- const defaultMessage = this.tryParseDefaultMessage();
270
- return { type: "CreateTable", name, columns, preventDefault, defaultMessage };
160
+ let t = this.expect(TokenType.IDENT).value, s = this.parseColumnList(), r = this.tryParsePreventDefault(), i = this.tryParseDefaultMessage();
161
+ return { type: "CreateTable", name: t, columns: s, preventDefault: r, defaultMessage: i };
271
162
  }
272
163
  this.error("expected BOT or TABLE after CREATE");
273
164
  }
274
165
  parseColumnList() {
275
166
  this.expect(TokenType.SYMBOL, "(");
276
- const columns = [];
277
- while (!this.at(TokenType.SYMBOL, ")")) {
278
- const colName = this.expect(TokenType.IDENT).value;
279
- const constraints = [];
280
- let colType = null;
281
- while (this.at(TokenType.IDENT) && !this.at(TokenType.SYMBOL, ",") && !this.at(TokenType.SYMBOL, ")")) {
282
- const word = this.advance().value;
283
- if (!colType) colType = word;
284
- else constraints.push(word);
167
+ let e = [];
168
+ for (; !this.at(TokenType.SYMBOL, ")"); ) {
169
+ let t = this.expect(TokenType.IDENT).value, s = [], r = null;
170
+ for (; this.at(TokenType.IDENT) && !this.at(TokenType.SYMBOL, ",") && !this.at(TokenType.SYMBOL, ")"); ) {
171
+ let i = this.advance().value;
172
+ r ? s.push(i) : r = i;
285
173
  }
286
- columns.push({ name: colName, columnType: colType, constraints });
287
- if (this.at(TokenType.SYMBOL, ",")) this.advance();
174
+ e.push({ name: t, columnType: r, constraints: s }), this.at(TokenType.SYMBOL, ",") && this.advance();
288
175
  }
289
- this.expect(TokenType.SYMBOL, ")");
290
- return columns;
176
+ return this.expect(TokenType.SYMBOL, ")"), e;
291
177
  }
292
178
  tryParsePreventDefault() {
293
- if (this.atKeyword("PREVENT")) {
294
- this.advance();
295
- this.expectKeyword("DEFAULT");
296
- return true;
297
- }
298
- return false;
299
- }
300
- // ---- DEFAULT MESSAGE "texto" ----
301
- // ---- DEFAULT MESSAGE (ficheiro.txt, N) ----
302
- //
303
- // Modificador do CREATE TABLE, ao lado de PREVENT DEFAULT: saudação
304
- // automática enviada só na primeira mensagem de cada client/sender
305
- // (ver botql.js, receiveMessage). Aceita texto direto ou a mesma forma
306
- // de ficheiro indexado do REPLY — por isso reaproveita isReplyFileForm
307
- // para o lookahead. "DEFAULT" aqui não é o mesmo "DEFAULT" de "PREVENT
308
- // DEFAULT" — só é reconhecido como início de DEFAULT MESSAGE quando NÃO
309
- // vem logo a seguir a PREVENT (tryParsePreventDefault já consumiu esse
310
- // caso antes de chegarmos aqui).
179
+ return !!this.atKeyword("PREVENT") && (this.advance(), this.expectKeyword("DEFAULT"), true);
180
+ }
311
181
  tryParseDefaultMessage() {
312
182
  if (!this.atKeyword("DEFAULT")) return null;
313
- const next = this.tokens[this.pos + 1];
314
- if (!next || next.type !== TokenType.KEYWORD || next.value !== "MESSAGE") return null;
315
- this.advance();
316
- this.advance();
317
- if (this.isReplyFileForm()) {
183
+ let e = this.tokens[this.pos + 1];
184
+ if (!e || e.type !== TokenType.KEYWORD || "MESSAGE" !== e.value) return null;
185
+ if (this.advance(), this.advance(), this.isReplyFileForm()) {
318
186
  this.expect(TokenType.SYMBOL, "(");
319
- const file = this.parseFileName();
187
+ let t = this.parseFileName();
320
188
  this.expect(TokenType.SYMBOL, ",");
321
- const index = this.parseExpression();
322
- this.expect(TokenType.SYMBOL, ")");
323
- return { file, index };
189
+ let s = this.parseExpression();
190
+ return this.expect(TokenType.SYMBOL, ")"), { file: t, index: s };
324
191
  }
325
- const value = this.parseExpression();
326
- return { value };
192
+ let r = this.parseExpression();
193
+ return { value: r };
327
194
  }
328
- // ---- PLATFORM ----
329
195
  parsePlatform() {
330
196
  this.expectKeyword("PLATFORM");
331
- const value = this.advance().value;
332
- return { type: "Platform", value };
333
- }
334
- // ---- CONNECT SERVIÇO TIPO "credencial" ----
335
- // ---- CONNECT RESPONSE alias ----
336
- //
337
- // A segunda forma liga o bot a uma IA usando um valor já importado (o
338
- // alias de um IMPORT {..., N} AS alias) — só estabelece a ligação,
339
- // não dispara nada sozinho (quem dispara é Response()/Response(alias),
340
- // dentro de uma ação). RESPONSE é keyword fixa aqui, por isso da para
341
- // distinguir logo no primeiro token, sem lookahead: a forma normal
342
- // nunca começa por essa palavra.
197
+ let e = this.advance().value;
198
+ return { type: "Platform", value: e };
199
+ }
343
200
  parseConnect() {
344
- this.expectKeyword("CONNECT");
345
- if (this.atKeyword("RESPONSE")) {
201
+ if (this.expectKeyword("CONNECT"), this.atKeyword("RESPONSE")) {
346
202
  this.advance();
347
- const alias = this.expect(TokenType.IDENT).value;
348
- return { type: "ConnectResponse", alias };
203
+ let e = this.expect(TokenType.IDENT).value;
204
+ return { type: "ConnectResponse", alias: e };
349
205
  }
350
- const service = this.advance().value;
351
- const kind = this.advance().value;
352
- const credential = this.expect(TokenType.STRING).value;
353
- return { type: "Connect", service, kind, credential };
206
+ let t = this.advance().value, s = this.advance().value, r = this.expect(TokenType.STRING).value;
207
+ return { type: "Connect", service: t, kind: s, credential: r };
354
208
  }
355
- // ---- ON START | ON MESSAGE | ON SIGNAL FROM "..." ----
356
209
  parseOn() {
357
- this.expectKeyword("ON");
358
- if (this.atKeyword("START")) {
210
+ if (this.expectKeyword("ON"), this.atKeyword("START")) {
359
211
  this.advance();
360
- const body = this.parseBody();
361
- return { type: "On", event: "START", body };
212
+ let e = this.parseBody();
213
+ return { type: "On", event: "START", body: e };
362
214
  }
363
215
  if (this.atKeyword("MESSAGE")) {
364
216
  this.advance();
365
- const body = this.parseBody();
366
- return { type: "On", event: "MESSAGE", body };
217
+ let t = this.parseBody();
218
+ return { type: "On", event: "MESSAGE", body: t };
367
219
  }
368
220
  if (this.atWord("SIGNAL")) {
369
- this.advance();
370
- this.expectKeyword("FROM");
371
- const source = this.expect(TokenType.STRING).value;
372
- const body = this.parseBody();
373
- return { type: "On", event: "SIGNAL", source, body };
221
+ this.advance(), this.expectKeyword("FROM");
222
+ let s = this.expect(TokenType.STRING).value, r = this.parseBody();
223
+ return { type: "On", event: "SIGNAL", source: s, body: r };
374
224
  }
375
225
  this.error("unexpected event after ON");
376
226
  }
377
- // ---- WHEN CONTAINS "..." [OR CONTAINS "..."]* ----
378
227
  parseWhen() {
379
228
  this.expectKeyword("WHEN");
380
- const conditions = [this.parseCondition()];
381
- while (this.atKeyword("OR")) {
382
- this.advance();
383
- conditions.push(this.parseCondition());
384
- }
385
- const body = this.parseBody();
386
- return { type: "When", conditions, body };
229
+ let e = [this.parseCondition()];
230
+ for (; this.atKeyword("OR"); ) this.advance(), e.push(this.parseCondition());
231
+ let t = this.parseBody();
232
+ return { type: "When", conditions: e, body: t };
387
233
  }
388
234
  parseCondition() {
389
- this.expectKeyword("CONTAINS");
390
- if (this.at(TokenType.SYMBOL, "(")) {
391
- const values = this.parseStringList();
392
- return { op: "CONTAINS_ANY", values };
235
+ if (this.expectKeyword("CONTAINS"), this.at(TokenType.SYMBOL, "(")) {
236
+ let e = this.parseStringList();
237
+ return { op: "CONTAINS_ANY", values: e };
393
238
  }
394
239
  if (this.atKeyword("KEYWORDS")) {
395
- this.advance();
396
- this.expect(TokenType.SYMBOL, "(");
397
- const path = this.parseFileName();
398
- this.expect(TokenType.SYMBOL, ")");
399
- return { op: "CONTAINS_KEYWORDS_FILE", path };
240
+ this.advance(), this.expect(TokenType.SYMBOL, "(");
241
+ let t = this.parseFileName();
242
+ return this.expect(TokenType.SYMBOL, ")"), { op: "CONTAINS_KEYWORDS_FILE", path: t };
400
243
  }
401
- const value = this.expect(TokenType.STRING).value;
402
- return { op: "CONTAINS", value };
244
+ let s = this.expect(TokenType.STRING).value;
245
+ return { op: "CONTAINS", value: s };
403
246
  }
404
- // Lista de strings entre parênteses: ("a", "b", "c")
405
247
  parseStringList() {
406
248
  this.expect(TokenType.SYMBOL, "(");
407
- const values = [];
408
- while (!this.at(TokenType.SYMBOL, ")")) {
409
- values.push(this.expect(TokenType.STRING).value);
410
- if (this.at(TokenType.SYMBOL, ",")) this.advance();
411
- }
412
- this.expect(TokenType.SYMBOL, ")");
413
- return values;
249
+ let e = [];
250
+ for (; !this.at(TokenType.SYMBOL, ")"); ) e.push(this.expect(TokenType.STRING).value), this.at(TokenType.SYMBOL, ",") && this.advance();
251
+ return this.expect(TokenType.SYMBOL, ")"), e;
414
252
  }
415
- // Nome de ficheiro sem aspas, ex: saudacoes.txt ou lista_bot.sql.
416
- // O tokenizer separa "saudacoes" (IDENT) e ".txt" em IDENT + SYMBOL('.')
417
- // + IDENT, por isso remontamos aqui em vez de pedir um STRING.
418
253
  parseFileName() {
419
- let name = this.expect(TokenType.IDENT).value;
420
- while (this.at(TokenType.SYMBOL, ".")) {
421
- this.advance();
422
- name += "." + this.expect(TokenType.IDENT).value;
423
- }
424
- return name;
254
+ let e = this.expect(TokenType.IDENT).value;
255
+ for (; this.at(TokenType.SYMBOL, "."); ) this.advance(), e += "." + this.expect(TokenType.IDENT).value;
256
+ return e;
425
257
  }
426
- // ---- OTHERWISE ----
427
258
  parseOtherwise() {
428
259
  this.expectKeyword("OTHERWISE");
429
- const body = this.parseBody();
430
- return { type: "Otherwise", body };
431
- }
432
- // ---- REPLY [TO alvo] expressão ----
433
- // ---- REPLY [TO alvo] (ficheiro.txt, N) ----
434
- //
435
- // A segunda forma referencia uma entrada numerada de um ficheiro de
436
- // respostas (uma linha "N- texto" por entrada). Distingue-se da
437
- // expressão comum porque começa por "(" seguido de um nome de
438
- // ficheiro (IDENT + "." + IDENT), e não por uma STRING/NUMBER/IDENT
439
- // isolado — daí o lookahead antes de decidir qual ramo seguir.
440
- // ---- THINK(ficheiro.txt) ----
441
- // ---- THINK(ficheiro.txt) OR REPLY (fallback.txt, N) ----
442
- // ---- THINK(ficheiro.txt) OR REPLY "texto fixo" ----
443
- //
444
- // Diferente de REPLY: THINK não responde com um texto já mapeado, faz
445
- // retrieval local sobre um ficheiro de conhecimento (ver RAG.js) e só
446
- // responde se achar um bloco com confiança suficiente. Antes de
447
- // procurar, sinaliza onThinking (para a plataforma poder mostrar
448
- // "Pensando..." enquanto isso, já que a busca — em ficheiros grandes —
449
- // não é instantânea como um REPLY comum).
450
- //
451
- // O "OR REPLY ..." é o fallback: só corre se THINK não encontrar nada
452
- // com confiança suficiente. Fica preso ao mesmo statement (não é um
453
- // segundo statement solto no corpo do WHEN) porque só faz sentido
454
- // junto — um REPLY fallback sem THINK antes seria só um REPLY normal.
260
+ let e = this.parseBody();
261
+ return { type: "Otherwise", body: e };
262
+ }
455
263
  parseThink() {
456
- this.expectKeyword("THINK");
457
- this.expect(TokenType.SYMBOL, "(");
458
- const file = this.parseFileName();
264
+ this.expectKeyword("THINK"), this.expect(TokenType.SYMBOL, "(");
265
+ let e = this.parseFileName();
459
266
  this.expect(TokenType.SYMBOL, ")");
460
- const waiting = this.tryParseWaiting();
461
- let fallback = null;
462
- if (this.atKeyword("OR")) {
463
- this.advance();
464
- fallback = this.parseReply();
465
- }
466
- return { type: "Think", file, waiting, fallback };
467
- }
468
- // ---- WAITING() | WAITING(ficheiro.txt) | WAITING(ficheiro.txt, N) ----
469
- //
470
- // Modificador do THINK, sempre logo a seguir a ele, antes do OR (ver
471
- // parseThink acima). Os dois argumentos são opcionais e independentes:
472
- // sem nenhum, o interpretador usa texto e tempo mínimo por omissão
473
- // ("Pensando...", 3 segundos) — ver botql.js, resolveWaitingConfig.
267
+ let t = this.tryParseWaiting(), s = null;
268
+ return this.atKeyword("OR") && (this.advance(), s = this.parseReply()), { type: "Think", file: e, waiting: t, fallback: s };
269
+ }
474
270
  tryParseWaiting() {
475
271
  if (!this.atKeyword("WAITING")) return null;
476
- this.advance();
477
- this.expect(TokenType.SYMBOL, "(");
478
- let file = null;
479
- let seconds = null;
480
- if (!this.at(TokenType.SYMBOL, ")")) {
481
- file = this.parseFileName();
482
- if (this.at(TokenType.SYMBOL, ",")) {
483
- this.advance();
484
- seconds = this.parseExpression();
485
- }
486
- }
487
- this.expect(TokenType.SYMBOL, ")");
488
- return { file, seconds };
272
+ this.advance(), this.expect(TokenType.SYMBOL, "(");
273
+ let e = null, t = null;
274
+ return !this.at(TokenType.SYMBOL, ")") && (e = this.parseFileName(), this.at(TokenType.SYMBOL, ",") && (this.advance(), t = this.parseExpression())), this.expect(TokenType.SYMBOL, ")"), { file: e, seconds: t };
489
275
  }
490
276
  parseReply() {
491
277
  this.expectKeyword("REPLY");
492
- let target = null;
493
- if (this.atKeyword("TO")) {
494
- this.advance();
495
- target = this.advance().value;
496
- }
497
- if (this.isReplyFileForm()) {
278
+ let e = null;
279
+ if (this.atKeyword("TO") && (this.advance(), e = this.advance().value), this.isReplyFileForm()) {
498
280
  this.expect(TokenType.SYMBOL, "(");
499
- const file = this.parseFileName();
281
+ let t = this.parseFileName();
500
282
  this.expect(TokenType.SYMBOL, ",");
501
- const index = this.parseExpression();
502
- this.expect(TokenType.SYMBOL, ")");
503
- return { type: "Reply", target, file, index };
283
+ let s = this.parseExpression();
284
+ return this.expect(TokenType.SYMBOL, ")"), { type: "Reply", target: e, file: t, index: s };
504
285
  }
505
- const value = this.parseExpression();
506
- return { type: "Reply", target, value };
286
+ let r = this.parseExpression();
287
+ return { type: "Reply", target: e, value: r };
507
288
  }
508
- // Lookahead: "(" IDENT "." IDENT "," ... — só a forma de ficheiro
509
- // tem "." logo a seguir ao primeiro identificador dentro dos
510
- // parênteses. Uma chamada de função normal, ex: (NOW()), nunca
511
- // bate aqui.
512
289
  isReplyFileForm() {
513
290
  if (!this.at(TokenType.SYMBOL, "(")) return false;
514
- const next = this.tokens[this.pos + 1];
515
- const afterNext = this.tokens[this.pos + 2];
516
- return !!next && next.type === TokenType.IDENT && !!afterNext && afterNext.type === TokenType.SYMBOL && afterNext.value === ".";
291
+ let e = this.tokens[this.pos + 1], t = this.tokens[this.pos + 2];
292
+ return !!e && e.type === TokenType.IDENT && !!t && t.type === TokenType.SYMBOL && "." === t.value;
517
293
  }
518
- // ---- FORWARD TO "contacto" ----
519
294
  parseForward() {
520
- this.expectKeyword("FORWARD");
521
- this.expectKeyword("TO");
522
- const target = this.parseExpression();
523
- return { type: "ForwardTo", target };
295
+ this.expectKeyword("FORWARD"), this.expectKeyword("TO");
296
+ let e = this.parseExpression();
297
+ return { type: "ForwardTo", target: e };
524
298
  }
525
- // ---- PARSE SIGNAL ----
526
299
  parseParseSignal() {
527
- this.expectKeyword("PARSE");
528
- if (!this.atWord("SIGNAL")) this.error("expected SIGNAL after PARSE");
529
- this.advance();
530
- return { type: "ParseSignal" };
300
+ return this.expectKeyword("PARSE"), this.atWord("SIGNAL") || this.error("expected SIGNAL after PARSE"), this.advance(), { type: "ParseSignal" };
531
301
  }
532
- // ---- SEND TO destino ----
533
302
  parseSend() {
534
- this.expectKeyword("SEND");
535
- this.expectKeyword("TO");
536
- const target = this.advance().value;
537
- return { type: "SendTo", target };
303
+ this.expectKeyword("SEND"), this.expectKeyword("TO");
304
+ let e = this.advance().value;
305
+ return { type: "SendTo", target: e };
538
306
  }
539
- // ---- INSERT INTO tabela [(cols)] [VALUES (vals)] ----
540
307
  parseInsert() {
541
- this.expectKeyword("INSERT");
542
- this.expectKeyword("INTO");
543
- const table = this.expect(TokenType.IDENT).value;
544
- let columns = null;
545
- if (this.at(TokenType.SYMBOL, "(")) {
546
- columns = this.parseExpressionList();
547
- }
548
- let values = null;
549
- if (this.atKeyword("VALUES")) {
550
- this.advance();
551
- values = this.parseExpressionList();
552
- }
553
- return { type: "Insert", table, columns, values };
308
+ this.expectKeyword("INSERT"), this.expectKeyword("INTO");
309
+ let e = this.expect(TokenType.IDENT).value, t = null;
310
+ this.at(TokenType.SYMBOL, "(") && (t = this.parseExpressionList());
311
+ let s = null;
312
+ return this.atKeyword("VALUES") && (this.advance(), s = this.parseExpressionList()), { type: "Insert", table: e, columns: t, values: s };
554
313
  }
555
314
  parseExpressionList() {
556
315
  this.expect(TokenType.SYMBOL, "(");
557
- const items = [];
558
- while (!this.at(TokenType.SYMBOL, ")")) {
559
- items.push(this.parseExpression());
560
- if (this.at(TokenType.SYMBOL, ",")) this.advance();
561
- }
562
- this.expect(TokenType.SYMBOL, ")");
563
- return items;
316
+ let e = [];
317
+ for (; !this.at(TokenType.SYMBOL, ")"); ) e.push(this.parseExpression()), this.at(TokenType.SYMBOL, ",") && this.advance();
318
+ return this.expect(TokenType.SYMBOL, ")"), e;
564
319
  }
565
- // ---- UPDATE tabela SET col = expr WHERE expr ----
566
320
  parseUpdate() {
567
321
  this.expectKeyword("UPDATE");
568
- const table = this.expect(TokenType.IDENT).value;
322
+ let e = this.expect(TokenType.IDENT).value;
569
323
  this.expectKeyword("SET");
570
- const column = this.expect(TokenType.IDENT).value;
324
+ let t = this.expect(TokenType.IDENT).value;
571
325
  this.expect(TokenType.SYMBOL, "=");
572
- const value = this.parseExpression();
573
- let where = null;
574
- if (this.atKeyword("WHERE")) {
575
- this.advance();
576
- where = this.parseComparison();
577
- }
578
- return { type: "Update", table, set: { column, value }, where };
579
- }
580
- // ---- IMPORT {ficheiro.sql} ----
581
- // ---- IMPORT {ficheiro.txt, N} ----
582
- // ---- IMPORT {ficheiro.txt, N} AS alias ----
583
- //
584
- // A segunda forma importa só a entrada N de um ficheiro de valores
585
- // (o mesmo formato "N- texto" usado por REPLY (ficheiro, N)), em vez
586
- // do ficheiro inteiro. O índice é opcional, tal como em REPLY.
587
- //
588
- // AS alias só faz sentido junto com o índice (dá nome ao valor lido,
589
- // para CONNECT RESPONSE/Response(...) e qualquer outra expressão
590
- // referenciarem por esse nome em vez do nome generico "env"). Sem AS,
591
- // mantém-se o comportamento original: o valor cai em env.NOME_FICHEIRO.
326
+ let s = this.parseExpression(), r = null;
327
+ return this.atKeyword("WHERE") && (this.advance(), r = this.parseComparison()), { type: "Update", table: e, set: { column: t, value: s }, where: r };
328
+ }
592
329
  parseImport() {
593
- this.expectKeyword("IMPORT");
594
- this.expect(TokenType.SYMBOL, "{");
595
- const path = this.parseFileName();
596
- let index = null;
597
- if (this.at(TokenType.SYMBOL, ",")) {
598
- this.advance();
599
- index = this.parseExpression();
600
- }
601
- this.expect(TokenType.SYMBOL, "}");
602
- let alias = null;
603
- if (this.atKeyword("AS")) {
604
- this.advance();
605
- alias = this.expect(TokenType.IDENT).value;
606
- }
607
- return { type: "Import", path, index, alias };
330
+ this.expectKeyword("IMPORT"), this.expect(TokenType.SYMBOL, "{");
331
+ let e = this.parseFileName(), t = null;
332
+ this.at(TokenType.SYMBOL, ",") && (this.advance(), t = this.parseExpression()), this.expect(TokenType.SYMBOL, "}");
333
+ let s = null;
334
+ return this.atKeyword("AS") && (this.advance(), s = this.expect(TokenType.IDENT).value), { type: "Import", path: e, index: t, alias: s };
608
335
  }
609
- // ---- RUN BOT ----
610
336
  parseRunBot() {
611
- this.expectKeyword("RUN");
612
- this.expectKeyword("BOT");
613
- return { type: "RunBot" };
337
+ return this.expectKeyword("RUN"), this.expectKeyword("BOT"), { type: "RunBot" };
614
338
  }
615
- // ---- Expressões: literais, identificadores, chamadas, acesso a
616
- // propriedade (obj.prop) e concatenação com "+" ----
617
339
  parseExpression() {
618
- let node = this.parsePrimary();
619
- while (this.at(TokenType.SYMBOL, "+")) {
340
+ let e = this.parsePrimary();
341
+ for (; this.at(TokenType.SYMBOL, "+"); ) {
620
342
  this.advance();
621
- const right = this.parsePrimary();
622
- node = { type: "Binary", op: "+", left: node, right };
343
+ let t = this.parsePrimary();
344
+ e = { type: "Binary", op: "+", left: e, right: t };
623
345
  }
624
- return node;
346
+ return e;
625
347
  }
626
- // Igualdade, usada em WHERE (ex: id = LAST_INSERT_ID())
627
348
  parseComparison() {
628
- const left = this.parseExpression();
349
+ let e = this.parseExpression();
629
350
  if (this.at(TokenType.SYMBOL, "=")) {
630
351
  this.advance();
631
- const right = this.parseExpression();
632
- return { type: "Binary", op: "=", left, right };
352
+ let t = this.parseExpression();
353
+ return { type: "Binary", op: "=", left: e, right: t };
633
354
  }
634
- return left;
355
+ return e;
635
356
  }
636
357
  parsePrimary() {
637
- const tok = this.current();
638
- if (tok.type === TokenType.STRING) {
358
+ let e = this.current();
359
+ if (e.type === TokenType.STRING || e.type === TokenType.NUMBER) return this.advance(), { type: "Literal", value: e.value };
360
+ if (e.type === TokenType.IDENT) {
639
361
  this.advance();
640
- return { type: "Literal", value: tok.value };
641
- }
642
- if (tok.type === TokenType.NUMBER) {
643
- this.advance();
644
- return { type: "Literal", value: tok.value };
645
- }
646
- if (tok.type === TokenType.IDENT) {
647
- this.advance();
648
- let node = { type: "Identifier", name: tok.value };
362
+ let t = { type: "Identifier", name: e.value };
649
363
  if (this.at(TokenType.SYMBOL, "(")) {
650
- const args = this.parseExpressionList();
651
- node = { type: "Call", callee: node.name, args };
364
+ let s = this.parseExpressionList();
365
+ t = { type: "Call", callee: t.name, args: s };
652
366
  }
653
- while (this.at(TokenType.SYMBOL, ".")) {
367
+ for (; this.at(TokenType.SYMBOL, "."); ) {
654
368
  this.advance();
655
- const prop = this.expect(TokenType.IDENT).value;
656
- node = { type: "Member", object: node, property: prop };
369
+ let r = this.expect(TokenType.IDENT).value;
370
+ t = { type: "Member", object: t, property: r };
657
371
  }
658
- return node;
372
+ return t;
659
373
  }
660
374
  this.error("invalid expression");
661
375
  }
@@ -668,97 +382,72 @@ var BotQL = (() => {
668
382
  var require_Database = __commonJS({
669
383
  "Database.js"(exports, module) {
670
384
  "use strict";
671
- var CONTEXT_SCHEMA = [
672
- { name: "id", columnType: "INT", constraints: ["PRIMARY", "KEY", "AUTO_INCREMENT"] },
673
- { name: "client", columnType: "TEXT", constraints: [] },
674
- { name: "message", columnType: "TEXT", constraints: [] },
675
- { name: "reply", columnType: "TEXT", constraints: [] },
676
- { name: "created_at", columnType: "DATETIME", constraints: [] }
677
- ];
385
+ var CONTEXT_SCHEMA = [{ name: "id", columnType: "INT", constraints: ["PRIMARY", "KEY", "AUTO_INCREMENT"] }, { name: "client", columnType: "TEXT", constraints: [] }, { name: "message", columnType: "TEXT", constraints: [] }, { name: "reply", columnType: "TEXT", constraints: [] }, { name: "created_at", columnType: "DATETIME", constraints: [] }];
678
386
  var MemoryDatabase = class {
679
387
  constructor() {
680
- this.tables = /* @__PURE__ */ new Map();
681
- this.autoIncrement = /* @__PURE__ */ new Map();
682
- }
683
- createTable(name, columns, preventDefault) {
684
- const resolvedColumns = name === "Context" && columns.length === 0 ? CONTEXT_SCHEMA : columns;
685
- if (this.tables.has(name)) {
686
- if (preventDefault) return;
687
- throw new Error(`runtime error: table "${name}" already exists`);
688
- }
689
- this.tables.set(name, { columns: resolvedColumns, rows: [] });
690
- this.autoIncrement.set(name, 0);
691
- }
692
- insert(table, columnNames, values) {
693
- const t = this.tables.get(table);
694
- if (!t) throw new Error(`runtime error: table "${table}" does not exist`);
695
- const nextId = this.autoIncrement.get(table) + 1;
696
- this.autoIncrement.set(table, nextId);
697
- const row = { id: nextId };
698
- columnNames.forEach((col, i) => {
699
- row[col] = values[i];
700
- });
701
- t.rows.push(row);
702
- return nextId;
703
- }
704
- update(table, column, value, whereColumn, whereValue) {
705
- const t = this.tables.get(table);
706
- if (!t) throw new Error(`runtime error: table "${table}" does not exist`);
707
- const row = whereColumn ? t.rows.find((r) => r[whereColumn] === whereValue) : t.rows[t.rows.length - 1];
708
- if (row) row[column] = value;
709
- return row || null;
710
- }
711
- getRows(table) {
712
- const t = this.tables.get(table);
388
+ this.tables = /* @__PURE__ */ new Map(), this.autoIncrement = /* @__PURE__ */ new Map();
389
+ }
390
+ createTable(e, t, r) {
391
+ let s = "Context" === e && 0 === t.length ? CONTEXT_SCHEMA : t;
392
+ if (this.tables.has(e)) {
393
+ if (r) return;
394
+ throw Error(`runtime error: table "${e}" already exists`);
395
+ }
396
+ this.tables.set(e, { columns: s, rows: [] }), this.autoIncrement.set(e, 0);
397
+ }
398
+ insert(e, t, r) {
399
+ let s = this.tables.get(e);
400
+ if (!s) throw Error(`runtime error: table "${e}" does not exist`);
401
+ let n = this.autoIncrement.get(e) + 1;
402
+ this.autoIncrement.set(e, n);
403
+ let i = { id: n };
404
+ return t.forEach((e2, t2) => {
405
+ i[e2] = r[t2];
406
+ }), s.rows.push(i), n;
407
+ }
408
+ update(e, t, r, s, n) {
409
+ let i = this.tables.get(e);
410
+ if (!i) throw Error(`runtime error: table "${e}" does not exist`);
411
+ let a = s ? i.rows.find((e2) => e2[s] === n) : i.rows[i.rows.length - 1];
412
+ return a && (a[t] = r), a || null;
413
+ }
414
+ getRows(e) {
415
+ let t = this.tables.get(e);
713
416
  return t ? t.rows.slice() : [];
714
417
  }
715
418
  };
716
- function mapColumnType(columnType) {
717
- const type = (columnType || "").toUpperCase();
718
- if (type === "INT" || type === "INTEGER") return "INTEGER";
719
- if (type === "DATETIME" || type === "DATE") return "TEXT";
720
- return "TEXT";
419
+ function mapColumnType(e) {
420
+ let t = (e || "").toUpperCase();
421
+ return "INT" === t || "INTEGER" === t ? "INTEGER" : "TEXT";
721
422
  }
722
- function buildColumnDef(col) {
723
- const type = mapColumnType(col.columnType);
724
- const isPrimary = col.constraints.includes("PRIMARY") && col.constraints.includes("KEY");
725
- const isAutoIncrement = col.constraints.includes("AUTO_INCREMENT");
726
- let def = `${col.name} ${type}`;
727
- if (isPrimary) def += " PRIMARY KEY";
728
- if (isPrimary && isAutoIncrement) def += " AUTOINCREMENT";
729
- return def;
423
+ function buildColumnDef(e) {
424
+ let t = mapColumnType(e.columnType), r = e.constraints.includes("PRIMARY") && e.constraints.includes("KEY"), s = e.constraints.includes("AUTO_INCREMENT"), n = `${e.name} ${t}`;
425
+ return r && (n += " PRIMARY KEY"), r && s && (n += " AUTOINCREMENT"), n;
730
426
  }
731
427
  var SQLiteDatabase = class {
732
- /**
733
- * @param {string} filePath Caminho do ficheiro .sqlite, ou ":memory:" para um banco temporário.
734
- */
735
- constructor(filePath = ":memory:") {
736
- const { DatabaseSync } = __require("node:sqlite");
737
- this.driver = new DatabaseSync(filePath);
738
- }
739
- createTable(name, columns, preventDefault) {
740
- const resolvedColumns = name === "Context" && columns.length === 0 ? CONTEXT_SCHEMA : columns;
741
- const columnDefs = resolvedColumns.map(buildColumnDef).join(", ");
742
- const ifNotExists = preventDefault ? "IF NOT EXISTS " : "";
743
- this.driver.exec(`CREATE TABLE ${ifNotExists}${name} (${columnDefs})`);
744
- }
745
- insert(table, columnNames, values) {
746
- const placeholders = columnNames.map(() => "?").join(", ");
747
- const sql = `INSERT INTO ${table} (${columnNames.join(", ")}) VALUES (${placeholders})`;
748
- const info = this.driver.prepare(sql).run(...values);
749
- return Number(info.lastInsertRowid);
750
- }
751
- update(table, column, value, whereColumn, whereValue) {
752
- if (whereColumn) {
753
- const sql = `UPDATE ${table} SET ${column} = ? WHERE ${whereColumn} = ?`;
754
- this.driver.prepare(sql).run(value, whereValue);
428
+ constructor(e = ":memory:") {
429
+ let { DatabaseSync: t } = __require("node:sqlite");
430
+ this.driver = new t(e);
431
+ }
432
+ createTable(e, t, r) {
433
+ let s = "Context" === e && 0 === t.length ? CONTEXT_SCHEMA : t, n = s.map(buildColumnDef).join(", ");
434
+ this.driver.exec(`CREATE TABLE ${r ? "IF NOT EXISTS " : ""}${e} (${n})`);
435
+ }
436
+ insert(e, t, r) {
437
+ let s = t.map(() => "?").join(", "), n = `INSERT INTO ${e} (${t.join(", ")}) VALUES (${s})`, i = this.driver.prepare(n).run(...r);
438
+ return Number(i.lastInsertRowid);
439
+ }
440
+ update(e, t, r, s, n) {
441
+ if (s) {
442
+ let i = `UPDATE ${e} SET ${t} = ? WHERE ${s} = ?`;
443
+ this.driver.prepare(i).run(r, n);
755
444
  } else {
756
- const sql = `UPDATE ${table} SET ${column} = ? WHERE id = (SELECT MAX(id) FROM ${table})`;
757
- this.driver.prepare(sql).run(value);
445
+ let a = `UPDATE ${e} SET ${t} = ? WHERE id = (SELECT MAX(id) FROM ${e})`;
446
+ this.driver.prepare(a).run(r);
758
447
  }
759
448
  }
760
- getRows(table) {
761
- return this.driver.prepare(`SELECT * FROM ${table}`).all();
449
+ getRows(e) {
450
+ return this.driver.prepare(`SELECT * FROM ${e}`).all();
762
451
  }
763
452
  close() {
764
453
  this.driver.close();
@@ -1349,572 +1038,297 @@ var BotQL = (() => {
1349
1038
  // botql.js
1350
1039
  var require_botql = __commonJS({
1351
1040
  "botql.js"(exports, module) {
1352
- var { Parser } = require_Parser();
1353
- var { MemoryDatabase } = require_Database();
1354
- var { createDefaultFileSystem } = require_FileSystem();
1355
- var { KnowledgeCache } = require_RAG();
1041
+ var { Parser: e } = require_Parser();
1042
+ var { MemoryDatabase: t } = require_Database();
1043
+ var { createDefaultFileSystem: s } = require_FileSystem();
1044
+ var { KnowledgeCache: i } = require_RAG();
1356
1045
  var WAITING_TEXTO_PADRAO = "Pensando...";
1357
- var WAITING_SEGUNDOS_PADRAO = 3;
1358
1046
  var BotQLInterpreter = class _BotQLInterpreter {
1359
- constructor(options = {}) {
1360
- this.db = options.db || new MemoryDatabase();
1361
- this.onReply = options.onReply || null;
1362
- this.onForward = options.onForward || null;
1363
- this.onSend = options.onSend || null;
1364
- this.signalParser = options.signalParser || ((raw) => raw);
1365
- this.onThinking = options.onThinking || null;
1366
- this.botName = null;
1367
- this.platform = null;
1368
- this.connections = [];
1369
- this.handlers = { START: [], MESSAGE: [], SIGNAL: [] };
1370
- this.running = false;
1371
- this.responseConnections = /* @__PURE__ */ new Set();
1372
- this.onResponse = options.onResponse || null;
1373
- this.defaultMessageConfig = null;
1374
- this.nativeFuncs = /* @__PURE__ */ new Map();
1375
- this.registerFunction("NOW", () => (/* @__PURE__ */ new Date()).toISOString());
1376
- this.fileSystem = options.fileSystem || createDefaultFileSystem();
1377
- const defaultBasePath = typeof process !== "undefined" && process.cwd ? process.cwd() : "";
1378
- this._basePathStack = [options.basePath || defaultBasePath];
1379
- this._importedFiles = /* @__PURE__ */ new Set();
1380
- this._rootBasePath = options.basePath || defaultBasePath;
1381
- this._keywordsFileCache = /* @__PURE__ */ new Map();
1382
- this._replyFileCache = /* @__PURE__ */ new Map();
1383
- this.envValues = /* @__PURE__ */ new Map();
1384
- this.knowledgeCache = new KnowledgeCache(this.fileSystem);
1385
- }
1386
- static fromSource(source, options = {}) {
1387
- const ast = new Parser(source).parseProgram();
1388
- const interpreter = new _BotQLInterpreter(options);
1389
- interpreter.load(ast);
1390
- return interpreter;
1391
- }
1392
- // Lê um ficheiro .sql do disco (ou de outro fileSystem injetado) e usa
1393
- // a sua pasta como base para resolver IMPORTs relativos dentro dele.
1394
- static fromFile(filePath, options = {}) {
1395
- const fileSystem = options.fileSystem || createDefaultFileSystem();
1396
- const resolved = fileSystem.resolve("", filePath);
1397
- const source = fileSystem.readFile(resolved);
1398
- return _BotQLInterpreter.fromSource(source, {
1399
- ...options,
1400
- fileSystem,
1401
- basePath: fileSystem.dirname(resolved)
1402
- });
1403
- }
1404
- registerFunction(name, fn) {
1405
- this.nativeFuncs.set(name, fn);
1406
- }
1407
- // ---- Carregamento da AST: statements de topo, fora de eventos ----
1408
- //
1409
- // IMPORT é sempre resolvido antes de qualquer outro comando, independente
1410
- // de onde aparece no ficheiro (tal como o SOURCE do MySQL ou o \i do
1411
- // psql) — por isso o corpo é primeiro "achatado": todo o conteúdo vindo
1412
- // de ficheiros importados entra ANTES dos statements do próprio ficheiro,
1413
- // recursivamente, mesmo que o IMPORT apareça no meio ou no fim do texto.
1414
- load(ast, basePath = this._basePathStack[this._basePathStack.length - 1]) {
1415
- const flatBody = this._flattenImports(ast.body, basePath);
1416
- for (const node of flatBody) {
1417
- switch (node.type) {
1418
- case "CreateBot":
1419
- this.botName = node.name;
1420
- break;
1421
- case "Platform":
1422
- this.platform = node.value;
1423
- break;
1424
- case "Connect":
1425
- this.connections.push({
1426
- service: node.service,
1427
- kind: node.kind,
1428
- credential: node.credential
1429
- });
1430
- break;
1431
- case "ConnectResponse":
1432
- if (!this.envValues.has(node.alias)) {
1433
- throw new Error(`runtime error: CONNECT RESPONSE failed, alias "${node.alias}" was not imported (use IMPORT {..., N} AS ${node.alias})`);
1434
- }
1435
- this.responseConnections.add(node.alias);
1436
- break;
1437
- case "CreateTable":
1438
- this.db.createTable(node.name, node.columns, node.preventDefault);
1439
- if (node.defaultMessage) {
1440
- const senderColumn = node.name === "Context" ? "client" : (node.columns.find((c) => c.name === "client" || c.name === "sender") || {}).name;
1441
- if (!senderColumn) {
1442
- throw new Error(`runtime error: DEFAULT MESSAGE on table "${node.name}" requires a "client" or "sender" column`);
1443
- }
1444
- this.defaultMessageConfig = { table: node.name, senderColumn, ...node.defaultMessage };
1445
- }
1446
- break;
1447
- case "On":
1448
- this.handlers[node.event].push(node);
1449
- break;
1450
- case "RunBot":
1451
- this.running = true;
1452
- break;
1453
- default:
1454
- throw new Error(`runtime error: unsupported top-level statement: ${node.type}`);
1455
- }
1047
+ constructor(e2 = {}) {
1048
+ this.db = e2.db || new t(), this.onReply = e2.onReply || null, this.onForward = e2.onForward || null, this.onSend = e2.onSend || null, this.signalParser = e2.signalParser || ((e3) => e3), this.onThinking = e2.onThinking || null, this.botName = null, this.platform = null, this.connections = [], this.handlers = { START: [], MESSAGE: [], SIGNAL: [] }, this.running = false, this.responseConnections = /* @__PURE__ */ new Set(), this.onResponse = e2.onResponse || null, this.defaultMessageConfig = null, this.nativeFuncs = /* @__PURE__ */ new Map(), this.registerFunction("NOW", () => (/* @__PURE__ */ new Date()).toISOString()), this.fileSystem = e2.fileSystem || s();
1049
+ let n = "undefined" != typeof process && process.cwd ? process.cwd() : "";
1050
+ this._basePathStack = [e2.basePath || n], this._importedFiles = /* @__PURE__ */ new Set(), this._rootBasePath = e2.basePath || n, this._keywordsFileCache = /* @__PURE__ */ new Map(), this._replyFileCache = /* @__PURE__ */ new Map(), this.envValues = /* @__PURE__ */ new Map(), this.knowledgeCache = new i(this.fileSystem);
1051
+ }
1052
+ static fromSource(t2, s2 = {}) {
1053
+ let i2 = new e(t2).parseProgram(), n = new _BotQLInterpreter(s2);
1054
+ return n.load(i2), n;
1055
+ }
1056
+ static fromFile(e2, t2 = {}) {
1057
+ let i2 = t2.fileSystem || s(), n = i2.resolve("", e2), r = i2.readFile(n);
1058
+ return _BotQLInterpreter.fromSource(r, { ...t2, fileSystem: i2, basePath: i2.dirname(n) });
1059
+ }
1060
+ registerFunction(e2, t2) {
1061
+ this.nativeFuncs.set(e2, t2);
1062
+ }
1063
+ load(e2, t2 = this._basePathStack[this._basePathStack.length - 1]) {
1064
+ let s2 = this._flattenImports(e2.body, t2);
1065
+ for (let i2 of s2) switch (i2.type) {
1066
+ case "CreateBot":
1067
+ this.botName = i2.name;
1068
+ break;
1069
+ case "Platform":
1070
+ this.platform = i2.value;
1071
+ break;
1072
+ case "Connect":
1073
+ this.connections.push({ service: i2.service, kind: i2.kind, credential: i2.credential });
1074
+ break;
1075
+ case "ConnectResponse":
1076
+ if (!this.envValues.has(i2.alias)) throw Error(`runtime error: CONNECT RESPONSE failed, alias "${i2.alias}" was not imported (use IMPORT {..., N} AS ${i2.alias})`);
1077
+ this.responseConnections.add(i2.alias);
1078
+ break;
1079
+ case "CreateTable":
1080
+ if (this.db.createTable(i2.name, i2.columns, i2.preventDefault), i2.defaultMessage) {
1081
+ let n = "Context" === i2.name ? "client" : (i2.columns.find((e3) => "client" === e3.name || "sender" === e3.name) || {}).name;
1082
+ if (!n) throw Error(`runtime error: DEFAULT MESSAGE on table "${i2.name}" requires a "client" or "sender" column`);
1083
+ this.defaultMessageConfig = { table: i2.name, senderColumn: n, ...i2.defaultMessage };
1084
+ }
1085
+ break;
1086
+ case "On":
1087
+ this.handlers[i2.event].push(i2);
1088
+ break;
1089
+ case "RunBot":
1090
+ this.running = true;
1091
+ break;
1092
+ default:
1093
+ throw Error(`runtime error: unsupported top-level statement: ${i2.type}`);
1456
1094
  }
1457
1095
  }
1458
- // Devolve um array de statements sem nenhum nó 'Import': o conteúdo de
1459
- // cada ficheiro importado é lido, parseado e achatado recursivamente
1460
- // primeiro (imports dentro de imports também respeitam a regra), e só
1461
- // depois vêm os statements que pertencem a este próprio ficheiro.
1462
- //
1463
- // IMPORT {ficheiro.sql} (sem índice) — comportamento original: lê o
1464
- // ficheiro inteiro como código BotQL e junta ao programa.
1465
- //
1466
- // IMPORT {ficheiro.txt, N} (com índice) — não é código: lê só a
1467
- // entrada N do ficheiro (mesmo formato "N- valor" do REPLY indexado)
1468
- // e guarda em this.envValues. Com AS alias, a chave é o alias (fica
1469
- // acessível diretamente pelo nome, ex: K, CONNECT RESPONSE K); sem
1470
- // AS, mantém-se o comportamento original — chave é o nome do
1471
- // ficheiro sem extensão, só acessível via env.NOME_FICHEIRO. Um
1472
- // ficheiro sem AS não pode ser usado por CONNECT RESPONSE (exige
1473
- // alias explícito, para não colidir com outros IMPORTs do mesmo
1474
- // ficheiro em índices diferentes).
1475
- // Não produz nenhum statement — é resolvido e removido do AST aqui
1476
- // mesmo, antes de correr qualquer evento.
1477
- _flattenImports(body, basePath) {
1478
- const fromImports = [];
1479
- const ownStatements = [];
1480
- for (const node of body) {
1481
- if (node.type !== "Import") {
1482
- ownStatements.push(node);
1096
+ _flattenImports(t2, s2) {
1097
+ let i2 = [], n = [];
1098
+ for (let r of t2) {
1099
+ if ("Import" !== r.type) {
1100
+ n.push(r);
1483
1101
  continue;
1484
1102
  }
1485
- if (node.index !== null && node.index !== void 0) {
1486
- const index = this.evalExpr(node.index, this.createContext());
1487
- const entries = this._loadIndexedFile(node.path, basePath);
1488
- const value = entries.get(Number(index));
1489
- if (value === void 0) {
1490
- throw new Error(`runtime error: IMPORT failed, entry ${index} not found in "${node.path}"`);
1491
- }
1492
- const key = node.alias || node.path.replace(/\.[^.]+$/, "");
1493
- this.envValues.set(key, value);
1103
+ if (null !== r.index && void 0 !== r.index) {
1104
+ let a = this.evalExpr(r.index, this.createContext()), l = this._loadIndexedFile(r.path, s2), o = l.get(Number(a));
1105
+ if (void 0 === o) throw Error(`runtime error: IMPORT failed, entry ${a} not found in "${r.path}"`);
1106
+ let h = r.alias || r.path.replace(/\.[^.]+$/, "");
1107
+ this.envValues.set(h, o);
1494
1108
  continue;
1495
1109
  }
1496
- const fullPath = this.fileSystem.resolve(basePath, node.path);
1497
- if (this._importedFiles.has(fullPath)) continue;
1498
- if (!this.fileSystem.exists(fullPath)) {
1499
- throw new Error(`runtime error: IMPORT failed, file not found: "${fullPath}"`);
1500
- }
1501
- this._importedFiles.add(fullPath);
1502
- const source = this.fileSystem.readFile(fullPath);
1503
- const importedAst = new Parser(source).parseProgram();
1504
- const nestedFlat = this._flattenImports(importedAst.body, this.fileSystem.dirname(fullPath));
1505
- fromImports.push(...nestedFlat);
1110
+ let u = this.fileSystem.resolve(s2, r.path);
1111
+ if (this._importedFiles.has(u)) continue;
1112
+ if (!this.fileSystem.exists(u)) throw Error(`runtime error: IMPORT failed, file not found: "${u}"`);
1113
+ this._importedFiles.add(u);
1114
+ let c = this.fileSystem.readFile(u), f = new e(c).parseProgram(), d = this._flattenImports(f.body, this.fileSystem.dirname(u));
1115
+ i2.push(...d);
1506
1116
  }
1507
- return [...fromImports, ...ownStatements];
1117
+ return [...i2, ...n];
1508
1118
  }
1509
- // ---- Contexto de execução de um evento ----
1510
- createContext(base = {}) {
1511
- return {
1512
- vars: {
1513
- client: base.client || null,
1514
- message: base.message || null,
1515
- lastMsg: null,
1516
- SIGNAL: null
1517
- },
1518
- rawSignal: base.rawSignal || null,
1519
- lastInsertId: null
1520
- };
1119
+ createContext(e2 = {}) {
1120
+ return { vars: { client: e2.client || null, message: e2.message || null, lastMsg: null, SIGNAL: null }, rawSignal: e2.rawSignal || null, lastInsertId: null };
1521
1121
  }
1522
- // ---- Ciclo de vida do bot ----
1523
1122
  async start() {
1524
- for (const handler of this.handlers.START) {
1525
- const ctx = this.createContext();
1526
- await this.run(handler.body, ctx);
1123
+ for (let e2 of this.handlers.START) {
1124
+ let t2 = this.createContext();
1125
+ await this.run(e2.body, t2);
1527
1126
  }
1528
1127
  }
1529
- async receiveMessage(client, message) {
1530
- const ctx = this.createContext({ client, message });
1531
- const cumprimentou = await this.greetIfNew(client);
1532
- if (cumprimentou) return ctx;
1533
- for (const handler of this.handlers.MESSAGE) {
1534
- await this.run(handler.body, ctx);
1535
- }
1536
- return ctx;
1537
- }
1538
- // Mostra a DEFAULT MESSAGE para este client, se ainda não for
1539
- // conhecido — sem precisar de nenhuma mensagem recebida. Usado por
1540
- // receiveMessage() (caso real, reativo) e também pode ser chamado
1541
- // diretamente pelo editor/preview ao abrir o chat, já que aí não há a
1542
- // restrição das plataformas reais de só poder responder depois do
1543
- // utilizador escrever primeiro. Devolve true se cumprimentou (e por
1544
- // isso nada mais deve correr nesse turno), false caso contrário —
1545
- // incluindo quando não há DEFAULT MESSAGE nenhuma configurada.
1546
- async greetIfNew(client) {
1547
- if (!this.defaultMessageConfig) return false;
1548
- const { table, senderColumn } = this.defaultMessageConfig;
1549
- const jaConhecido = this.db.getRows(table).some((row) => row[senderColumn] === client);
1550
- if (jaConhecido) return false;
1551
- const ctx = this.createContext({ client });
1552
- const text = this.resolveDefaultMessageText(this.defaultMessageConfig, ctx);
1553
- ctx.vars.lastMsg = text;
1554
- this.db.insert(table, [senderColumn], [client]);
1555
- if (this.onReply) {
1556
- await this.onReply({ target: null, text, client });
1557
- }
1558
- return true;
1128
+ async receiveMessage(e2, t2) {
1129
+ let s2 = this.createContext({ client: e2, message: t2 }), i2 = await this.greetIfNew(e2);
1130
+ if (i2) return s2;
1131
+ for (let n of this.handlers.MESSAGE) await this.run(n.body, s2);
1132
+ return s2;
1559
1133
  }
1560
- // Resolve o texto do DEFAULT MESSAGE: mesma dualidade texto-direto vs
1561
- // ficheiro indexado que o REPLY já tem (ver resolveReplyFromFile).
1562
- resolveDefaultMessageText(config, ctx) {
1563
- if (config.file) {
1564
- return this.resolveReplyFromFile(config.file, config.index, ctx);
1565
- }
1566
- return String(this.evalExpr(config.value, ctx));
1567
- }
1568
- async receiveSignal(source, rawSignal) {
1569
- const ctx = this.createContext({ rawSignal });
1570
- for (const handler of this.handlers.SIGNAL) {
1571
- if (handler.source !== source) continue;
1572
- await this.run(handler.body, ctx);
1573
- }
1574
- return ctx;
1575
- }
1576
- // ---- Execução de uma lista de statements (corpo de um bloco) ----
1577
- async run(statements, ctx) {
1578
- let groupMatched = false;
1579
- for (let i = 0; i < statements.length; i++) {
1580
- const stmt = statements[i];
1581
- if (stmt.type === "When") {
1582
- if (i === 0 || statements[i - 1].type !== "When") groupMatched = false;
1583
- const isMatch = stmt.conditions.some((cond) => this.evalCondition(cond, ctx));
1584
- if (isMatch) {
1585
- await this.run(stmt.body, ctx);
1586
- groupMatched = true;
1587
- }
1134
+ async greetIfNew(e2) {
1135
+ if (!this.defaultMessageConfig) return false;
1136
+ let { table: t2, senderColumn: s2 } = this.defaultMessageConfig, i2 = this.db.getRows(t2).some((t3) => t3[s2] === e2);
1137
+ if (i2) return false;
1138
+ let n = this.createContext({ client: e2 }), r = this.resolveDefaultMessageText(this.defaultMessageConfig, n);
1139
+ return n.vars.lastMsg = r, this.db.insert(t2, [s2], [e2]), this.onReply && await this.onReply({ target: null, text: r, client: e2 }), true;
1140
+ }
1141
+ resolveDefaultMessageText(e2, t2) {
1142
+ return e2.file ? this.resolveReplyFromFile(e2.file, e2.index, t2) : String(this.evalExpr(e2.value, t2));
1143
+ }
1144
+ async receiveSignal(e2, t2) {
1145
+ let s2 = this.createContext({ rawSignal: t2 });
1146
+ for (let i2 of this.handlers.SIGNAL) i2.source === e2 && await this.run(i2.body, s2);
1147
+ return s2;
1148
+ }
1149
+ async run(e2, t2) {
1150
+ let s2 = false;
1151
+ for (let i2 = 0; i2 < e2.length; i2++) {
1152
+ let n = e2[i2];
1153
+ if ("When" === n.type) {
1154
+ (0 === i2 || "When" !== e2[i2 - 1].type) && (s2 = false);
1155
+ let r = n.conditions.some((e3) => this.evalCondition(e3, t2));
1156
+ r && (await this.run(n.body, t2), s2 = true);
1588
1157
  continue;
1589
1158
  }
1590
- if (stmt.type === "Otherwise") {
1591
- if (!groupMatched) await this.run(stmt.body, ctx);
1592
- groupMatched = false;
1159
+ if ("Otherwise" === n.type) {
1160
+ s2 || await this.run(n.body, t2), s2 = false;
1593
1161
  continue;
1594
1162
  }
1595
- await this.execStatement(stmt, ctx);
1596
- }
1597
- }
1598
- evalCondition(cond, ctx) {
1599
- if (typeof ctx.vars.message !== "string") return false;
1600
- const message = ctx.vars.message.toLowerCase();
1601
- if (cond.op === "CONTAINS") {
1602
- return message.includes(cond.value.toLowerCase());
1603
- }
1604
- if (cond.op === "CONTAINS_ANY") {
1605
- return cond.values.some((v) => message.includes(v.toLowerCase()));
1606
- }
1607
- if (cond.op === "CONTAINS_KEYWORDS_FILE") {
1608
- const words = this._loadKeywordsFile(cond.path);
1609
- return words.some((v) => message.includes(v.toLowerCase()));
1610
- }
1611
- throw new Error(`runtime error: unsupported condition: ${cond.op}`);
1612
- }
1613
- // um ficheiro de keywords: uma palavra/frase por linha, nada mais.
1614
- // Sem comentários nem qualquer outra sintaxe misturada de propósito —
1615
- // um ficheiro só serve para um fim, evita ambiguidade sobre o que é
1616
- // keyword e o que não é. Resultado fica em cache — chamado a cada
1617
- // mensagem recebida, não pode reler disco/rede sempre.
1618
- _loadKeywordsFile(path) {
1619
- if (this._keywordsFileCache.has(path)) {
1620
- return this._keywordsFileCache.get(path);
1621
- }
1622
- const fullPath = this.fileSystem.resolve(this._rootBasePath, path);
1623
- if (!this.fileSystem.exists(fullPath)) {
1624
- throw new Error(`runtime error: KEYWORDS failed, file not found: "${fullPath}"`);
1625
- }
1626
- const raw = this.fileSystem.readFile(fullPath);
1627
- const words = raw.split("\n").map((linha) => linha.trim()).filter((linha) => linha.length > 0);
1628
- this._keywordsFileCache.set(path, words);
1629
- return words;
1630
- }
1631
- // Resolve o texto e o tempo mínimo do WAITING(...) de um THINK. Sem
1632
- // WAITING nenhum no .sql (stmt.waiting === null), ou com WAITING()
1633
- // vazio, usa os valores por omissão. O ficheiro do WAITING é lido tal
1634
- // e qual (texto/HTML livre) — ao contrário do REPLY/DEFAULT MESSAGE
1635
- // indexados, não é o formato "N- valor".
1636
- resolveWaitingConfig(waiting, ctx) {
1637
- if (!waiting) return { text: WAITING_TEXTO_PADRAO, seconds: WAITING_SEGUNDOS_PADRAO };
1638
- let text = WAITING_TEXTO_PADRAO;
1639
- if (waiting.file) {
1640
- const fullPath = this.fileSystem.resolve(this._rootBasePath, waiting.file);
1641
- if (!this.fileSystem.exists(fullPath)) {
1642
- throw new Error(`runtime error: WAITING failed, file not found: "${fullPath}"`);
1643
- }
1644
- text = this.fileSystem.readFile(fullPath).trim();
1645
- }
1646
- let seconds = WAITING_SEGUNDOS_PADRAO;
1647
- if (waiting.seconds) {
1648
- seconds = Number(this.evalExpr(waiting.seconds, ctx));
1649
- }
1650
- return { text, seconds };
1651
- }
1652
- // Resolve REPLY (ficheiro.txt, indice): lê o índice (número ou
1653
- // expressão que resolve a número) e devolve o texto da entrada
1654
- // correspondente do ficheiro de respostas.
1655
- resolveReplyFromFile(file, indexNode, ctx) {
1656
- const index = this.evalExpr(indexNode, ctx);
1657
- const entries = this._loadIndexedFile(file, this._rootBasePath);
1658
- const text = entries.get(Number(index));
1659
- if (text === void 0) {
1660
- throw new Error(`runtime error: REPLY failed, entry ${index} not found in "${file}"`);
1661
- }
1662
- return text;
1663
- }
1664
- // Lê um ficheiro de entradas numeradas. Duas formas, na mesma
1665
- // entrada N, nunca misturadas:
1666
- //
1667
- // N- texto — uma linha só (forma original, continua igual)
1668
- // N-{ ... } — bloco delimitado por chaves, pode ter
1669
- // varias linhas e HTML/Markdown/CSS por
1670
- // dentro. So fecha na "}" que corresponde a
1671
- // "{" que abriu — chaves internas (ex: um
1672
- // style="{color:red}" dentro do HTML) contam
1673
- // para o aninhamento e NAO fecham a entrada
1674
- // cedo. Rigoroso: uma "{" sem a "}"
1675
- // correspondente antes do fim do ficheiro e'
1676
- // erro, nunca silenciosamente ignorado.
1677
- //
1678
- // Resultado e' um Map indice -> valor, em cache — usado tanto por
1679
- // REPLY (ficheiro, N) como por IMPORT {ficheiro, N}.
1680
- _loadIndexedFile(path, basePath) {
1681
- if (this._replyFileCache.has(path)) {
1682
- return this._replyFileCache.get(path);
1683
- }
1684
- const fullPath = this.fileSystem.resolve(basePath, path);
1685
- if (!this.fileSystem.exists(fullPath)) {
1686
- throw new Error(`runtime error: file not found: "${fullPath}"`);
1687
- }
1688
- const raw = this.fileSystem.readFile(fullPath);
1689
- const entries = this._parseIndexedEntries(raw, path);
1690
- this._replyFileCache.set(path, entries);
1691
- return entries;
1692
- }
1693
- // Parser char-by-char do formato de entradas numeradas. Percorre o
1694
- // ficheiro procurando "N-" no inicio de uma linha (ignorando espacos);
1695
- // o que vem a seguir decide a forma:
1696
- // "{" logo a seguir -> bloco: conta chaves ate a correspondente
1697
- // fechar, valor e' o conteudo entre elas (sem as chaves),
1698
- // aparado nas pontas.
1699
- // qualquer outra coisa -> forma de uma linha: valor e' o resto da
1700
- // linha, aparado.
1701
- _parseIndexedEntries(raw, path) {
1702
- const entries = /* @__PURE__ */ new Map();
1703
- const len = raw.length;
1704
- let i = 0;
1705
- while (i < len) {
1706
- const lineStart = i;
1707
- let j = lineStart;
1708
- while (j < len && (raw[j] === " " || raw[j] === " ")) j++;
1709
- const numMatch = /^\d+/.exec(raw.slice(j));
1710
- if (!numMatch || raw[j + numMatch[0].length] !== "-") {
1711
- const nl2 = raw.indexOf("\n", lineStart);
1712
- i = nl2 === -1 ? len : nl2 + 1;
1163
+ await this.execStatement(n, t2);
1164
+ }
1165
+ }
1166
+ evalCondition(e2, t2) {
1167
+ if ("string" != typeof t2.vars.message) return false;
1168
+ let s2 = t2.vars.message.toLowerCase();
1169
+ if ("CONTAINS" === e2.op) return s2.includes(e2.value.toLowerCase());
1170
+ if ("CONTAINS_ANY" === e2.op) return e2.values.some((e3) => s2.includes(e3.toLowerCase()));
1171
+ if ("CONTAINS_KEYWORDS_FILE" === e2.op) {
1172
+ let i2 = this._loadKeywordsFile(e2.path);
1173
+ return i2.some((e3) => s2.includes(e3.toLowerCase()));
1174
+ }
1175
+ throw Error(`runtime error: unsupported condition: ${e2.op}`);
1176
+ }
1177
+ _loadKeywordsFile(e2) {
1178
+ if (this._keywordsFileCache.has(e2)) return this._keywordsFileCache.get(e2);
1179
+ let t2 = this.fileSystem.resolve(this._rootBasePath, e2);
1180
+ if (!this.fileSystem.exists(t2)) throw Error(`runtime error: KEYWORDS failed, file not found: "${t2}"`);
1181
+ let s2 = this.fileSystem.readFile(t2), i2 = s2.split("\n").map((e3) => e3.trim()).filter((e3) => e3.length > 0);
1182
+ return this._keywordsFileCache.set(e2, i2), i2;
1183
+ }
1184
+ resolveWaitingConfig(e2, t2) {
1185
+ if (!e2) return { text: WAITING_TEXTO_PADRAO, seconds: 3 };
1186
+ let s2 = WAITING_TEXTO_PADRAO;
1187
+ if (e2.file) {
1188
+ let i2 = this.fileSystem.resolve(this._rootBasePath, e2.file);
1189
+ if (!this.fileSystem.exists(i2)) throw Error(`runtime error: WAITING failed, file not found: "${i2}"`);
1190
+ s2 = this.fileSystem.readFile(i2).trim();
1191
+ }
1192
+ let n = 3;
1193
+ return e2.seconds && (n = Number(this.evalExpr(e2.seconds, t2))), { text: s2, seconds: n };
1194
+ }
1195
+ resolveReplyFromFile(e2, t2, s2) {
1196
+ let i2 = this.evalExpr(t2, s2), n = this._loadIndexedFile(e2, this._rootBasePath), r = n.get(Number(i2));
1197
+ if (void 0 === r) throw Error(`runtime error: REPLY failed, entry ${i2} not found in "${e2}"`);
1198
+ return r;
1199
+ }
1200
+ _loadIndexedFile(e2, t2) {
1201
+ if (this._replyFileCache.has(e2)) return this._replyFileCache.get(e2);
1202
+ let s2 = this.fileSystem.resolve(t2, e2);
1203
+ if (!this.fileSystem.exists(s2)) throw Error(`runtime error: file not found: "${s2}"`);
1204
+ let i2 = this.fileSystem.readFile(s2), n = this._parseIndexedEntries(i2, e2);
1205
+ return this._replyFileCache.set(e2, n), n;
1206
+ }
1207
+ _parseIndexedEntries(e2, t2) {
1208
+ let s2 = /* @__PURE__ */ new Map(), i2 = e2.length, n = 0;
1209
+ for (; n < i2; ) {
1210
+ let r = n, a = r;
1211
+ for (; a < i2 && (" " === e2[a] || " " === e2[a]); ) a++;
1212
+ let l = /^\d+/.exec(e2.slice(a));
1213
+ if (!l || "-" !== e2[a + l[0].length]) {
1214
+ let o = e2.indexOf("\n", r);
1215
+ n = -1 === o ? i2 : o + 1;
1713
1216
  continue;
1714
1217
  }
1715
- const index = Number(numMatch[0]);
1716
- let k = j + numMatch[0].length + 1;
1717
- if (raw[k] === "{") {
1718
- let depth = 1;
1719
- const contentStart = k + 1;
1720
- let p = contentStart;
1721
- while (p < len && depth > 0) {
1722
- if (raw[p] === "{") depth++;
1723
- else if (raw[p] === "}") depth--;
1724
- p++;
1725
- }
1726
- if (depth !== 0) {
1727
- throw new Error(`runtime error: unclosed "{" for entry ${index} in "${path}"`);
1728
- }
1729
- const value2 = raw.slice(contentStart, p - 1).trim();
1730
- entries.set(index, value2);
1731
- i = p;
1218
+ let h = Number(l[0]), u = a + l[0].length + 1;
1219
+ if ("{" === e2[u]) {
1220
+ let c = 1, f = u + 1, d = f;
1221
+ for (; d < i2 && c > 0; ) "{" === e2[d] ? c++ : "}" === e2[d] && c--, d++;
1222
+ if (0 !== c) throw Error(`runtime error: unclosed "{" for entry ${h} in "${t2}"`);
1223
+ let p = e2.slice(f, d - 1).trim();
1224
+ s2.set(h, p), n = d;
1732
1225
  continue;
1733
1226
  }
1734
- const nl = raw.indexOf("\n", k);
1735
- const lineEnd = nl === -1 ? len : nl;
1736
- const value = raw.slice(k, lineEnd).trim();
1737
- entries.set(index, value);
1738
- i = nl === -1 ? len : nl + 1;
1227
+ let m = e2.indexOf("\n", u), w = -1 === m ? i2 : m, g = e2.slice(u, w).trim();
1228
+ s2.set(h, g), n = -1 === m ? i2 : m + 1;
1739
1229
  }
1740
- return entries;
1230
+ return s2;
1741
1231
  }
1742
- // ---- Execução de um statement de ação ----
1743
- async execStatement(stmt, ctx) {
1744
- switch (stmt.type) {
1232
+ async execStatement(e2, t2) {
1233
+ switch (e2.type) {
1745
1234
  case "Reply": {
1746
- const text = stmt.file ? this.resolveReplyFromFile(stmt.file, stmt.index, ctx) : String(await this.evalReplyValue(stmt.value, ctx));
1747
- ctx.vars.lastMsg = text;
1748
- if (this.onReply) {
1749
- await this.onReply({ target: stmt.target, text, client: ctx.vars.client });
1750
- }
1235
+ let s2 = e2.file ? this.resolveReplyFromFile(e2.file, e2.index, t2) : String(await this.evalReplyValue(e2.value, t2));
1236
+ t2.vars.lastMsg = s2, this.onReply && await this.onReply({ target: e2.target, text: s2, client: t2.vars.client });
1751
1237
  break;
1752
1238
  }
1753
1239
  case "Think": {
1754
- const waiting = this.resolveWaitingConfig(stmt.waiting, ctx);
1755
- const inicio = Date.now();
1756
- if (this.onThinking) {
1757
- await this.onThinking({ client: ctx.vars.client, text: waiting.text });
1758
- }
1759
- const fullPath = this.fileSystem.resolve(this._rootBasePath, stmt.file);
1760
- const index = this.knowledgeCache.get(fullPath);
1761
- const resultado = index.analyze(ctx.vars.message);
1762
- const decorrido = Date.now() - inicio;
1763
- const faltam = waiting.seconds * 1e3 - decorrido;
1764
- if (faltam > 0) {
1765
- await new Promise((resolve) => setTimeout(resolve, faltam));
1766
- }
1767
- if (resultado.decision === "RESPONDER") {
1768
- const text = resultado.texto;
1769
- ctx.vars.lastMsg = text;
1770
- if (this.onReply) {
1771
- await this.onReply({ target: null, text, client: ctx.vars.client });
1772
- }
1773
- } else if (stmt.fallback) {
1774
- await this.execStatement(stmt.fallback, ctx);
1775
- }
1240
+ let i2 = this.resolveWaitingConfig(e2.waiting, t2), n = Date.now();
1241
+ this.onThinking && await this.onThinking({ client: t2.vars.client, text: i2.text });
1242
+ let r = this.fileSystem.resolve(this._rootBasePath, e2.file), a = this.knowledgeCache.get(r), l = a.analyze(t2.vars.message), o = Date.now() - n, h = 1e3 * i2.seconds - o;
1243
+ if (h > 0 && await new Promise((e3) => setTimeout(e3, h)), "RESPONDER" === l.decision) {
1244
+ let u = l.texto;
1245
+ t2.vars.lastMsg = u, this.onReply && await this.onReply({ target: null, text: u, client: t2.vars.client });
1246
+ } else e2.fallback && await this.execStatement(e2.fallback, t2);
1776
1247
  break;
1777
1248
  }
1778
1249
  case "ForwardTo": {
1779
- const target = String(this.evalExpr(stmt.target, ctx));
1780
- if (this.onForward) {
1781
- await this.onForward({ target, client: ctx.vars.client });
1782
- }
1250
+ let c = String(this.evalExpr(e2.target, t2));
1251
+ this.onForward && await this.onForward({ target: c, client: t2.vars.client });
1783
1252
  break;
1784
1253
  }
1785
- case "ParseSignal": {
1786
- ctx.vars.SIGNAL = await this.signalParser(ctx.rawSignal);
1254
+ case "ParseSignal":
1255
+ t2.vars.SIGNAL = await this.signalParser(t2.rawSignal);
1787
1256
  break;
1788
- }
1789
- case "SendTo": {
1790
- if (this.onSend) {
1791
- await this.onSend({ target: stmt.target, signal: ctx.vars.SIGNAL });
1792
- }
1257
+ case "SendTo":
1258
+ this.onSend && await this.onSend({ target: e2.target, signal: t2.vars.SIGNAL });
1793
1259
  break;
1794
- }
1795
- case "Insert": {
1796
- this.execInsert(stmt, ctx);
1260
+ case "Insert":
1261
+ this.execInsert(e2, t2);
1797
1262
  break;
1798
- }
1799
- case "Update": {
1800
- this.execUpdate(stmt, ctx);
1263
+ case "Update":
1264
+ this.execUpdate(e2, t2);
1801
1265
  break;
1802
- }
1803
1266
  default:
1804
- throw new Error(`runtime error: unsupported statement inside event: ${stmt.type}`);
1267
+ throw Error(`runtime error: unsupported statement inside event: ${e2.type}`);
1805
1268
  }
1806
1269
  }
1807
- execInsert(stmt, ctx) {
1808
- let columnNames;
1809
- let values;
1810
- if (stmt.table === "Context" && (!stmt.columns || stmt.columns.length === 0) && !stmt.values) {
1811
- columnNames = ["client", "message", "created_at"];
1812
- values = [ctx.vars.client, ctx.vars.message, (/* @__PURE__ */ new Date()).toISOString()];
1813
- } else {
1814
- columnNames = (stmt.columns || []).map((c) => c.name);
1815
- values = (stmt.values || []).map((v) => this.evalExpr(v, ctx));
1816
- }
1817
- ctx.lastInsertId = this.db.insert(stmt.table, columnNames, values);
1818
- }
1819
- execUpdate(stmt, ctx) {
1820
- const value = this.evalExpr(stmt.set.value, ctx);
1821
- let whereColumn = null;
1822
- let whereValue = null;
1823
- if (stmt.where) {
1824
- whereColumn = stmt.where.left.name;
1825
- whereValue = this.evalExpr(stmt.where.right, ctx);
1826
- }
1827
- this.db.update(stmt.table, stmt.set.column, value, whereColumn, whereValue);
1828
- }
1829
- // ---- Avaliação de expressões ----
1830
- // Ponto de entrada usado só por REPLY <expr>: trata Response()/
1831
- // Response(alias) como caso especial assíncrono (chama this.onResponse,
1832
- // que fala de verdade com a IA ligada) e delega qualquer outra
1833
- // expressão ao evalExpr síncrono normal. Response() não pode ser
1834
- // composto dentro de + (ex: REPLY "x" + Response() não é suportado) —
1835
- // só faz sentido como o valor direto do REPLY, conforme a doc.
1836
- async evalReplyValue(node, ctx) {
1837
- if (node.type === "Call" && node.callee === "Response") {
1838
- return this.execResponse(node, ctx);
1839
- }
1840
- return this.evalExpr(node, ctx);
1841
- }
1842
- // Response() / Response(alias): dispara o ciclo com a IA ligada via
1843
- // CONNECT RESPONSE — envia a mensagem atual, espera, devolve o texto.
1844
- // Sem argumento só é válido com exatamente uma ligação (ambíguo com
1845
- // mais que uma); com argumento, o alias tem de corresponder a um
1846
- // CONNECT RESPONSE já feito.
1847
- async execResponse(node, ctx) {
1848
- if (!this.onResponse) {
1849
- throw new Error("runtime error: Response() failed, no AI connected (missing onResponse handler)");
1850
- }
1851
- let alias;
1852
- if (node.args.length === 0) {
1853
- if (this.responseConnections.size === 0) {
1854
- throw new Error("runtime error: Response() failed, no CONNECT RESPONSE found");
1855
- }
1856
- if (this.responseConnections.size > 1) {
1857
- throw new Error(`runtime error: Response() is ambiguous with multiple CONNECT RESPONSE (${[...this.responseConnections].join(", ")}); use Response(alias)`);
1858
- }
1859
- alias = [...this.responseConnections][0];
1270
+ execInsert(e2, t2) {
1271
+ let s2, i2;
1272
+ "Context" !== e2.table || e2.columns && 0 !== e2.columns.length || e2.values ? (s2 = (e2.columns || []).map((e3) => e3.name), i2 = (e2.values || []).map((e3) => this.evalExpr(e3, t2))) : (s2 = ["client", "message", "created_at"], i2 = [t2.vars.client, t2.vars.message, (/* @__PURE__ */ new Date()).toISOString()]), t2.lastInsertId = this.db.insert(e2.table, s2, i2);
1273
+ }
1274
+ execUpdate(e2, t2) {
1275
+ let s2 = this.evalExpr(e2.set.value, t2), i2 = null, n = null;
1276
+ e2.where && (i2 = e2.where.left.name, n = this.evalExpr(e2.where.right, t2)), this.db.update(e2.table, e2.set.column, s2, i2, n);
1277
+ }
1278
+ async evalReplyValue(e2, t2) {
1279
+ return "Call" === e2.type && "Response" === e2.callee ? this.execResponse(e2, t2) : this.evalExpr(e2, t2);
1280
+ }
1281
+ async execResponse(e2, t2) {
1282
+ if (!this.onResponse) throw Error("runtime error: Response() failed, no AI connected (missing onResponse handler)");
1283
+ let s2;
1284
+ if (0 === e2.args.length) {
1285
+ if (0 === this.responseConnections.size) throw Error("runtime error: Response() failed, no CONNECT RESPONSE found");
1286
+ if (this.responseConnections.size > 1) throw Error(`runtime error: Response() is ambiguous with multiple CONNECT RESPONSE (${[...this.responseConnections].join(", ")}); use Response(alias)`);
1287
+ s2 = [...this.responseConnections][0];
1860
1288
  } else {
1861
- const argNode = node.args[0];
1862
- if (argNode.type !== "Identifier") {
1863
- throw new Error("runtime error: Response(alias) expects an alias name, not a string or expression");
1864
- }
1865
- alias = argNode.name;
1866
- if (!this.responseConnections.has(alias)) {
1867
- throw new Error(`runtime error: Response(${alias}) failed, no CONNECT RESPONSE ${alias} found`);
1868
- }
1289
+ let i2 = e2.args[0];
1290
+ if ("Identifier" !== i2.type) throw Error("runtime error: Response(alias) expects an alias name, not a string or expression");
1291
+ if (s2 = i2.name, !this.responseConnections.has(s2)) throw Error(`runtime error: Response(${s2}) failed, no CONNECT RESPONSE ${s2} found`);
1869
1292
  }
1870
- const token = this.envValues.get(alias);
1871
- const text = await this.onResponse({ alias, token, message: ctx.vars.message, client: ctx.vars.client });
1872
- return text;
1293
+ let n = this.envValues.get(s2), r = await this.onResponse({ alias: s2, token: n, message: t2.vars.message, client: t2.vars.client });
1294
+ return r;
1873
1295
  }
1874
- evalExpr(node, ctx) {
1875
- switch (node.type) {
1296
+ evalExpr(e2, t2) {
1297
+ switch (e2.type) {
1876
1298
  case "Literal":
1877
- return node.value;
1878
- case "Identifier": {
1879
- if (node.name === "env") {
1880
- return Object.fromEntries(this.envValues);
1881
- }
1882
- if (Object.prototype.hasOwnProperty.call(ctx.vars, node.name)) {
1883
- return ctx.vars[node.name];
1884
- }
1885
- if (this.envValues.has(node.name)) {
1886
- return this.envValues.get(node.name);
1887
- }
1888
- throw new Error(`runtime error: unknown identifier: "${node.name}"`);
1889
- }
1299
+ return e2.value;
1300
+ case "Identifier":
1301
+ if ("env" === e2.name) return Object.fromEntries(this.envValues);
1302
+ if (Object.prototype.hasOwnProperty.call(t2.vars, e2.name)) return t2.vars[e2.name];
1303
+ if (this.envValues.has(e2.name)) return this.envValues.get(e2.name);
1304
+ throw Error(`runtime error: unknown identifier: "${e2.name}"`);
1890
1305
  case "Member": {
1891
- const obj = this.evalExpr(node.object, ctx);
1892
- if (obj === null || obj === void 0) return void 0;
1893
- return obj[node.property];
1306
+ let s2 = this.evalExpr(e2.object, t2);
1307
+ if (null == s2) return;
1308
+ return s2[e2.property];
1894
1309
  }
1895
1310
  case "Call": {
1896
- if (node.callee === "LAST_INSERT_ID") return ctx.lastInsertId;
1897
- const fn = this.nativeFuncs.get(node.callee);
1898
- if (!fn) throw new Error(`runtime error: unknown function: "${node.callee}"`);
1899
- const args = node.args.map((a) => this.evalExpr(a, ctx));
1900
- return fn(...args);
1311
+ if ("LAST_INSERT_ID" === e2.callee) return t2.lastInsertId;
1312
+ let i2 = this.nativeFuncs.get(e2.callee);
1313
+ if (!i2) throw Error(`runtime error: unknown function: "${e2.callee}"`);
1314
+ let n = e2.args.map((e3) => this.evalExpr(e3, t2));
1315
+ return i2(...n);
1901
1316
  }
1902
1317
  case "Binary": {
1903
- const left = this.evalExpr(node.left, ctx);
1904
- const right = this.evalExpr(node.right, ctx);
1905
- if (node.op === "+") {
1906
- if (typeof left === "number" && typeof right === "number") return left + right;
1907
- return String(left) + String(right);
1318
+ let r = this.evalExpr(e2.left, t2), a = this.evalExpr(e2.right, t2);
1319
+ if ("+" === e2.op) {
1320
+ if ("number" == typeof r && "number" == typeof a) return r + a;
1321
+ return String(r) + String(a);
1908
1322
  }
1909
- if (node.op === "=") return left === right;
1910
- throw new Error(`runtime error: unsupported operator: "${node.op}"`);
1323
+ if ("=" === e2.op) return r === a;
1324
+ throw Error(`runtime error: unsupported operator: "${e2.op}"`);
1911
1325
  }
1912
1326
  default:
1913
- throw new Error(`runtime error: unsupported expression: ${node.type}`);
1327
+ throw Error(`runtime error: unsupported expression: ${e2.type}`);
1914
1328
  }
1915
1329
  }
1916
1330
  };
1917
- module.exports = { BotQLInterpreter, MemoryDatabase };
1331
+ module.exports = { BotQLInterpreter, MemoryDatabase: t };
1918
1332
  }
1919
1333
  });
1920
1334
  return require_botql();