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/Database.js +1 -146
- package/Parser.js +1 -762
- package/README.md +157 -12
- package/botql.browser.js +481 -1067
- package/botql.js +3 -785
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
67
|
-
this.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(
|
|
74
|
-
this.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(
|
|
80
|
-
throw
|
|
33
|
+
error(e) {
|
|
34
|
+
throw Error(`syntax error: ${e}`);
|
|
81
35
|
}
|
|
82
|
-
peekChar(
|
|
83
|
-
return this.source[this.pos +
|
|
36
|
+
peekChar(e = 0) {
|
|
37
|
+
return this.source[this.pos + e];
|
|
84
38
|
}
|
|
85
39
|
tokenize() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
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(
|
|
46
|
+
if (/\s/.test(e)) {
|
|
94
47
|
this.pos++;
|
|
95
48
|
continue;
|
|
96
49
|
}
|
|
97
|
-
if (
|
|
98
|
-
|
|
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 (
|
|
102
|
-
this.tokens.push(this.readString(
|
|
54
|
+
if ('"' === e || "'" === e) {
|
|
55
|
+
this.tokens.push(this.readString(e));
|
|
103
56
|
continue;
|
|
104
57
|
}
|
|
105
|
-
if (/[0-9]/.test(
|
|
58
|
+
if (/[0-9]/.test(e)) {
|
|
106
59
|
this.tokens.push(this.readNumber());
|
|
107
60
|
continue;
|
|
108
61
|
}
|
|
109
|
-
if ("{}(),.=+*;".includes(
|
|
110
|
-
this.tokens.push(new Token(TokenType.SYMBOL,
|
|
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(
|
|
115
|
-
|
|
116
|
-
this.tokens.push(
|
|
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: "${
|
|
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(
|
|
125
|
-
|
|
75
|
+
readString(e) {
|
|
76
|
+
let t = this.line;
|
|
126
77
|
this.pos++;
|
|
127
|
-
let
|
|
128
|
-
|
|
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
|
-
|
|
136
|
-
value += this.peekChar();
|
|
137
|
-
this.pos++;
|
|
84
|
+
"\n" === this.peekChar() && this.line++, s += this.peekChar(), this.pos++;
|
|
138
85
|
}
|
|
139
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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(
|
|
170
|
-
this.tokens = new Tokenizer(
|
|
171
|
-
this.pos = 0;
|
|
101
|
+
constructor(e) {
|
|
102
|
+
this.tokens = new Tokenizer(e).tokenize(), this.pos = 0;
|
|
172
103
|
}
|
|
173
|
-
error(
|
|
174
|
-
|
|
175
|
-
throw
|
|
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(
|
|
181
|
-
|
|
182
|
-
|
|
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(...
|
|
187
|
-
return this.at(TokenType.KEYWORD) &&
|
|
115
|
+
atKeyword(...e) {
|
|
116
|
+
return this.at(TokenType.KEYWORD) && e.includes(this.current().value);
|
|
188
117
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
return tok;
|
|
123
|
+
let e = this.current();
|
|
124
|
+
return e.type !== TokenType.EOF && this.pos++, e;
|
|
200
125
|
}
|
|
201
|
-
expect(
|
|
202
|
-
|
|
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(
|
|
208
|
-
return this.expect(TokenType.KEYWORD,
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
294
|
-
|
|
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
|
-
|
|
314
|
-
if (!
|
|
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
|
-
|
|
187
|
+
let t = this.parseFileName();
|
|
320
188
|
this.expect(TokenType.SYMBOL, ",");
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
348
|
-
return { type: "ConnectResponse", alias };
|
|
203
|
+
let e = this.expect(TokenType.IDENT).value;
|
|
204
|
+
return { type: "ConnectResponse", alias: e };
|
|
349
205
|
}
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
371
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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.
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
478
|
-
|
|
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
|
|
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
|
-
|
|
281
|
+
let t = this.parseFileName();
|
|
500
282
|
this.expect(TokenType.SYMBOL, ",");
|
|
501
|
-
|
|
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
|
-
|
|
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
|
-
|
|
515
|
-
|
|
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.
|
|
522
|
-
|
|
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.
|
|
536
|
-
|
|
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.
|
|
543
|
-
|
|
544
|
-
let
|
|
545
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
322
|
+
let e = this.expect(TokenType.IDENT).value;
|
|
569
323
|
this.expectKeyword("SET");
|
|
570
|
-
|
|
324
|
+
let t = this.expect(TokenType.IDENT).value;
|
|
571
325
|
this.expect(TokenType.SYMBOL, "=");
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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.
|
|
595
|
-
|
|
596
|
-
let
|
|
597
|
-
|
|
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
|
|
619
|
-
|
|
340
|
+
let e = this.parsePrimary();
|
|
341
|
+
for (; this.at(TokenType.SYMBOL, "+"); ) {
|
|
620
342
|
this.advance();
|
|
621
|
-
|
|
622
|
-
|
|
343
|
+
let t = this.parsePrimary();
|
|
344
|
+
e = { type: "Binary", op: "+", left: e, right: t };
|
|
623
345
|
}
|
|
624
|
-
return
|
|
346
|
+
return e;
|
|
625
347
|
}
|
|
626
|
-
// Igualdade, usada em WHERE (ex: id = LAST_INSERT_ID())
|
|
627
348
|
parseComparison() {
|
|
628
|
-
|
|
349
|
+
let e = this.parseExpression();
|
|
629
350
|
if (this.at(TokenType.SYMBOL, "=")) {
|
|
630
351
|
this.advance();
|
|
631
|
-
|
|
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
|
|
355
|
+
return e;
|
|
635
356
|
}
|
|
636
357
|
parsePrimary() {
|
|
637
|
-
|
|
638
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
651
|
-
|
|
364
|
+
let s = this.parseExpressionList();
|
|
365
|
+
t = { type: "Call", callee: t.name, args: s };
|
|
652
366
|
}
|
|
653
|
-
|
|
367
|
+
for (; this.at(TokenType.SYMBOL, "."); ) {
|
|
654
368
|
this.advance();
|
|
655
|
-
|
|
656
|
-
|
|
369
|
+
let r = this.expect(TokenType.IDENT).value;
|
|
370
|
+
t = { type: "Member", object: t, property: r };
|
|
657
371
|
}
|
|
658
|
-
return
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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(
|
|
717
|
-
|
|
718
|
-
|
|
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(
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
757
|
-
this.driver.prepare(
|
|
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(
|
|
761
|
-
return this.driver.prepare(`SELECT * FROM ${
|
|
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(
|
|
1360
|
-
this.db =
|
|
1361
|
-
|
|
1362
|
-
this.
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
this.
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
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 (
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
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
|
-
|
|
1497
|
-
if (this._importedFiles.has(
|
|
1498
|
-
if (!this.fileSystem.exists(
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
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 [...
|
|
1117
|
+
return [...i2, ...n];
|
|
1508
1118
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
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 (
|
|
1525
|
-
|
|
1526
|
-
await this.run(
|
|
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(
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
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
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
if (
|
|
1564
|
-
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
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 (
|
|
1591
|
-
|
|
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(
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
evalCondition(
|
|
1599
|
-
if (typeof
|
|
1600
|
-
|
|
1601
|
-
if (
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
return
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
if (
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
this.
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
let p =
|
|
1721
|
-
|
|
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
|
-
|
|
1735
|
-
|
|
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
|
|
1230
|
+
return s2;
|
|
1741
1231
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
switch (stmt.type) {
|
|
1232
|
+
async execStatement(e2, t2) {
|
|
1233
|
+
switch (e2.type) {
|
|
1745
1234
|
case "Reply": {
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
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
|
-
|
|
1780
|
-
|
|
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
|
-
|
|
1254
|
+
case "ParseSignal":
|
|
1255
|
+
t2.vars.SIGNAL = await this.signalParser(t2.rawSignal);
|
|
1787
1256
|
break;
|
|
1788
|
-
|
|
1789
|
-
|
|
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
|
-
|
|
1796
|
-
this.execInsert(stmt, ctx);
|
|
1260
|
+
case "Insert":
|
|
1261
|
+
this.execInsert(e2, t2);
|
|
1797
1262
|
break;
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
this.execUpdate(stmt, ctx);
|
|
1263
|
+
case "Update":
|
|
1264
|
+
this.execUpdate(e2, t2);
|
|
1801
1265
|
break;
|
|
1802
|
-
}
|
|
1803
1266
|
default:
|
|
1804
|
-
throw
|
|
1267
|
+
throw Error(`runtime error: unsupported statement inside event: ${e2.type}`);
|
|
1805
1268
|
}
|
|
1806
1269
|
}
|
|
1807
|
-
execInsert(
|
|
1808
|
-
let
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
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
|
-
|
|
1862
|
-
if (
|
|
1863
|
-
|
|
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
|
-
|
|
1871
|
-
|
|
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(
|
|
1875
|
-
switch (
|
|
1296
|
+
evalExpr(e2, t2) {
|
|
1297
|
+
switch (e2.type) {
|
|
1876
1298
|
case "Literal":
|
|
1877
|
-
return
|
|
1878
|
-
case "Identifier":
|
|
1879
|
-
if (
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
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
|
-
|
|
1892
|
-
if (
|
|
1893
|
-
return
|
|
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 (
|
|
1897
|
-
|
|
1898
|
-
if (!
|
|
1899
|
-
|
|
1900
|
-
return
|
|
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
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
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 (
|
|
1910
|
-
throw
|
|
1323
|
+
if ("=" === e2.op) return r === a;
|
|
1324
|
+
throw Error(`runtime error: unsupported operator: "${e2.op}"`);
|
|
1911
1325
|
}
|
|
1912
1326
|
default:
|
|
1913
|
-
throw
|
|
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();
|