@yakuzaa/jade-compiler 0.1.0
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/dist/ast/nodes.d.ts +219 -0
- package/dist/ast/nodes.d.ts.map +1 -0
- package/dist/ast/nodes.js +2 -0
- package/dist/ast/nodes.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +108 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen/ir_generator.d.ts +42 -0
- package/dist/codegen/ir_generator.d.ts.map +1 -0
- package/dist/codegen/ir_generator.js +691 -0
- package/dist/codegen/ir_generator.js.map +1 -0
- package/dist/codegen/ir_nodes.d.ts +143 -0
- package/dist/codegen/ir_nodes.d.ts.map +1 -0
- package/dist/codegen/ir_nodes.js +3 -0
- package/dist/codegen/ir_nodes.js.map +1 -0
- package/dist/codegen/ir_printer.d.ts +10 -0
- package/dist/codegen/ir_printer.d.ts.map +1 -0
- package/dist/codegen/ir_printer.js +131 -0
- package/dist/codegen/ir_printer.js.map +1 -0
- package/dist/codegen/wasm_generator.d.ts +12 -0
- package/dist/codegen/wasm_generator.d.ts.map +1 -0
- package/dist/codegen/wasm_generator.js +48 -0
- package/dist/codegen/wasm_generator.js.map +1 -0
- package/dist/codegen/wat_generator.d.ts +29 -0
- package/dist/codegen/wat_generator.d.ts.map +1 -0
- package/dist/codegen/wat_generator.js +345 -0
- package/dist/codegen/wat_generator.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer/lexer.d.ts +31 -0
- package/dist/lexer/lexer.d.ts.map +1 -0
- package/dist/lexer/lexer.js +475 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/lexer/token.d.ts +8 -0
- package/dist/lexer/token.d.ts.map +1 -0
- package/dist/lexer/token.js +2 -0
- package/dist/lexer/token.js.map +1 -0
- package/dist/lexer/token_type.d.ts +80 -0
- package/dist/lexer/token_type.d.ts.map +1 -0
- package/dist/lexer/token_type.js +98 -0
- package/dist/lexer/token_type.js.map +1 -0
- package/dist/parser/parse_result.d.ts +12 -0
- package/dist/parser/parse_result.d.ts.map +1 -0
- package/dist/parser/parse_result.js +2 -0
- package/dist/parser/parse_result.js.map +1 -0
- package/dist/parser/parser.d.ts +57 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +1094 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/semantic/semantic_analyzer.d.ts +10 -0
- package/dist/semantic/semantic_analyzer.d.ts.map +1 -0
- package/dist/semantic/semantic_analyzer.js +16 -0
- package/dist/semantic/semantic_analyzer.js.map +1 -0
- package/dist/semantic/symbol_table.d.ts +33 -0
- package/dist/semantic/symbol_table.d.ts.map +1 -0
- package/dist/semantic/symbol_table.js +105 -0
- package/dist/semantic/symbol_table.js.map +1 -0
- package/dist/semantic/type_checker.d.ts +66 -0
- package/dist/semantic/type_checker.d.ts.map +1 -0
- package/dist/semantic/type_checker.js +963 -0
- package/dist/semantic/type_checker.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
import { TokenType } from '../lexer/token_type.js';
|
|
2
|
+
export class Parser {
|
|
3
|
+
tokens;
|
|
4
|
+
current = 0;
|
|
5
|
+
errors = [];
|
|
6
|
+
constructor(tokens) {
|
|
7
|
+
this.tokens = tokens;
|
|
8
|
+
}
|
|
9
|
+
parse() {
|
|
10
|
+
const declaracoes = [];
|
|
11
|
+
while (!this.isAtEnd()) {
|
|
12
|
+
try {
|
|
13
|
+
const declaracao = this.parseDeclaracao();
|
|
14
|
+
if (declaracao) {
|
|
15
|
+
declaracoes.push(declaracao);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
this.errors.push({
|
|
20
|
+
message: error.message || 'Erro de sintaxe',
|
|
21
|
+
line: this.peek().line,
|
|
22
|
+
column: this.peek().column
|
|
23
|
+
});
|
|
24
|
+
this.synchronize();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const program = {
|
|
28
|
+
kind: 'Programa',
|
|
29
|
+
line: 1,
|
|
30
|
+
column: 1,
|
|
31
|
+
declaracoes
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
program,
|
|
35
|
+
errors: this.errors,
|
|
36
|
+
success: this.errors.length === 0
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// ── Declarações ───────────────────────────────────────────
|
|
40
|
+
parseDeclaracao() {
|
|
41
|
+
try {
|
|
42
|
+
if (this.match(TokenType.MODULO))
|
|
43
|
+
return this.parseModulo();
|
|
44
|
+
if (this.match(TokenType.CLASSE))
|
|
45
|
+
return this.parseClasse();
|
|
46
|
+
if (this.match(TokenType.ENTIDADE))
|
|
47
|
+
return this.parseEntidade();
|
|
48
|
+
if (this.match(TokenType.SERVICO))
|
|
49
|
+
return this.parseServico();
|
|
50
|
+
if (this.match(TokenType.FUNCAO))
|
|
51
|
+
return this.parseFuncao();
|
|
52
|
+
if (this.match(TokenType.EVENTO))
|
|
53
|
+
return this.parseEvento();
|
|
54
|
+
if (this.match(TokenType.REGRA))
|
|
55
|
+
return this.parseRegra();
|
|
56
|
+
if (this.match(TokenType.INTERFACE))
|
|
57
|
+
return this.parseInterface();
|
|
58
|
+
if (this.match(TokenType.ENUM))
|
|
59
|
+
return this.parseEnum();
|
|
60
|
+
if (this.match(TokenType.IMPORTAR))
|
|
61
|
+
return this.parseImportacao();
|
|
62
|
+
if (this.match(TokenType.TELA))
|
|
63
|
+
return this.parseTela();
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
parseModulo() {
|
|
71
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do módulo");
|
|
72
|
+
const nome = nomeToken.value;
|
|
73
|
+
const declaracoes = [];
|
|
74
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
75
|
+
const declaracao = this.parseDeclaracao();
|
|
76
|
+
if (declaracao)
|
|
77
|
+
declaracoes.push(declaracao);
|
|
78
|
+
}
|
|
79
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar módulo");
|
|
80
|
+
return {
|
|
81
|
+
kind: 'Modulo',
|
|
82
|
+
line: nomeToken.line,
|
|
83
|
+
column: nomeToken.column,
|
|
84
|
+
nome,
|
|
85
|
+
declaracoes
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
parseClasse() {
|
|
89
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da classe");
|
|
90
|
+
const nome = nomeToken.value;
|
|
91
|
+
let superClasse;
|
|
92
|
+
const interfaces = [];
|
|
93
|
+
if (this.match(TokenType.EXTENDS)) {
|
|
94
|
+
superClasse = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da superclasse").value;
|
|
95
|
+
}
|
|
96
|
+
if (this.match(TokenType.IMPLEMENTS)) {
|
|
97
|
+
do {
|
|
98
|
+
interfaces.push(this.consume(TokenType.IDENTIFICADOR, "Esperado nome da interface").value);
|
|
99
|
+
} while (this.match(TokenType.VIRGULA));
|
|
100
|
+
}
|
|
101
|
+
const campos = [];
|
|
102
|
+
const metodos = [];
|
|
103
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
104
|
+
if (this.match(TokenType.FUNCAO)) {
|
|
105
|
+
metodos.push(this.parseFuncao());
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
campos.push(this.parseCampo());
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar classe");
|
|
112
|
+
return {
|
|
113
|
+
kind: 'Classe',
|
|
114
|
+
line: nomeToken.line,
|
|
115
|
+
column: nomeToken.column,
|
|
116
|
+
nome,
|
|
117
|
+
superClasse,
|
|
118
|
+
interfaces,
|
|
119
|
+
campos,
|
|
120
|
+
metodos
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
parseEntidade() {
|
|
124
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da entidade");
|
|
125
|
+
const nome = nomeToken.value;
|
|
126
|
+
const campos = [];
|
|
127
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
128
|
+
campos.push(this.parseCampo());
|
|
129
|
+
}
|
|
130
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar entidade");
|
|
131
|
+
return {
|
|
132
|
+
kind: 'Entidade',
|
|
133
|
+
line: nomeToken.line,
|
|
134
|
+
column: nomeToken.column,
|
|
135
|
+
nome,
|
|
136
|
+
campos
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
parseServico() {
|
|
140
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do serviço");
|
|
141
|
+
const nome = nomeToken.value;
|
|
142
|
+
const metodos = [];
|
|
143
|
+
const ouvintes = [];
|
|
144
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
145
|
+
if (this.match(TokenType.FUNCAO)) {
|
|
146
|
+
metodos.push(this.parseFuncao());
|
|
147
|
+
}
|
|
148
|
+
else if (this.match(TokenType.ESCUTAR)) {
|
|
149
|
+
ouvintes.push(this.parseOuvinte());
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
throw this.error("Esperado 'funcao' ou 'escutar' no serviço");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar serviço");
|
|
156
|
+
return {
|
|
157
|
+
kind: 'Servico',
|
|
158
|
+
line: nomeToken.line,
|
|
159
|
+
column: nomeToken.column,
|
|
160
|
+
nome,
|
|
161
|
+
metodos,
|
|
162
|
+
ouvintes
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
parseFuncao() {
|
|
166
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da função");
|
|
167
|
+
const nome = nomeToken.value;
|
|
168
|
+
this.consume(TokenType.ABRE_PAREN, "Esperado '('");
|
|
169
|
+
const parametros = [];
|
|
170
|
+
if (!this.check(TokenType.FECHA_PAREN)) {
|
|
171
|
+
do {
|
|
172
|
+
parametros.push(this.parseParametro());
|
|
173
|
+
} while (this.match(TokenType.VIRGULA));
|
|
174
|
+
}
|
|
175
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')'");
|
|
176
|
+
let tipoRetorno;
|
|
177
|
+
if (this.match(TokenType.SETA)) {
|
|
178
|
+
tipoRetorno = this.parseTipo();
|
|
179
|
+
}
|
|
180
|
+
const corpo = this.parseBloco();
|
|
181
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar função");
|
|
182
|
+
return {
|
|
183
|
+
kind: 'Funcao',
|
|
184
|
+
line: nomeToken.line,
|
|
185
|
+
column: nomeToken.column,
|
|
186
|
+
nome,
|
|
187
|
+
parametros,
|
|
188
|
+
tipoRetorno,
|
|
189
|
+
corpo
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
parseEvento() {
|
|
193
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do evento");
|
|
194
|
+
const nome = nomeToken.value;
|
|
195
|
+
const campos = [];
|
|
196
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
197
|
+
campos.push(this.parseCampo());
|
|
198
|
+
}
|
|
199
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar evento");
|
|
200
|
+
return {
|
|
201
|
+
kind: 'Evento',
|
|
202
|
+
line: nomeToken.line,
|
|
203
|
+
column: nomeToken.column,
|
|
204
|
+
nome,
|
|
205
|
+
campos
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
parseRegra() {
|
|
209
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da regra");
|
|
210
|
+
const nome = nomeToken.value;
|
|
211
|
+
this.consume(TokenType.QUANDO, "Esperado 'quando'");
|
|
212
|
+
const condicao = this.parseExpressao();
|
|
213
|
+
this.consume(TokenType.ENTAO, "Esperado 'entao'");
|
|
214
|
+
const entao = this.parseBloco();
|
|
215
|
+
let senao;
|
|
216
|
+
if (this.match(TokenType.SENAO)) {
|
|
217
|
+
senao = this.parseBloco();
|
|
218
|
+
}
|
|
219
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar regra");
|
|
220
|
+
return {
|
|
221
|
+
kind: 'Regra',
|
|
222
|
+
line: nomeToken.line,
|
|
223
|
+
column: nomeToken.column,
|
|
224
|
+
nome,
|
|
225
|
+
condicao,
|
|
226
|
+
entao,
|
|
227
|
+
senao
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
parseInterface() {
|
|
231
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da interface");
|
|
232
|
+
const nome = nomeToken.value;
|
|
233
|
+
const assinaturas = [];
|
|
234
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
235
|
+
this.consume(TokenType.FUNCAO, "Esperado 'funcao' na interface");
|
|
236
|
+
const nomeAssinaturaToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da assinatura");
|
|
237
|
+
const nomeAssinatura = nomeAssinaturaToken.value;
|
|
238
|
+
this.consume(TokenType.ABRE_PAREN, "Esperado '('");
|
|
239
|
+
const parametros = [];
|
|
240
|
+
if (!this.check(TokenType.FECHA_PAREN)) {
|
|
241
|
+
do {
|
|
242
|
+
parametros.push(this.parseParametro());
|
|
243
|
+
} while (this.match(TokenType.VIRGULA));
|
|
244
|
+
}
|
|
245
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')'");
|
|
246
|
+
this.consume(TokenType.SETA, "Esperado '->'");
|
|
247
|
+
const tipoRetorno = this.parseTipo();
|
|
248
|
+
assinaturas.push({
|
|
249
|
+
kind: 'Assinatura',
|
|
250
|
+
line: nomeAssinaturaToken.line,
|
|
251
|
+
column: nomeAssinaturaToken.column,
|
|
252
|
+
nome: nomeAssinatura,
|
|
253
|
+
parametros,
|
|
254
|
+
tipoRetorno
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar interface");
|
|
258
|
+
return {
|
|
259
|
+
kind: 'Interface',
|
|
260
|
+
line: nomeToken.line,
|
|
261
|
+
column: nomeToken.column,
|
|
262
|
+
nome,
|
|
263
|
+
assinaturas
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
parseEnum() {
|
|
267
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do enum");
|
|
268
|
+
const nome = nomeToken.value;
|
|
269
|
+
const valores = [];
|
|
270
|
+
do {
|
|
271
|
+
const valor = this.consume(TokenType.IDENTIFICADOR, "Esperado valor do enum").value;
|
|
272
|
+
valores.push(valor);
|
|
273
|
+
} while (!this.check(TokenType.FIM) && !this.isAtEnd());
|
|
274
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar enum");
|
|
275
|
+
return {
|
|
276
|
+
kind: 'Enum',
|
|
277
|
+
line: nomeToken.line,
|
|
278
|
+
column: nomeToken.column,
|
|
279
|
+
nome,
|
|
280
|
+
valores
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
parseImportacao() {
|
|
284
|
+
const moduloToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do módulo");
|
|
285
|
+
const modulo = moduloToken.value;
|
|
286
|
+
let item;
|
|
287
|
+
let wildcard = false;
|
|
288
|
+
let alias;
|
|
289
|
+
if (this.match(TokenType.PONTO)) {
|
|
290
|
+
if (this.match(TokenType.ASTERISCO)) {
|
|
291
|
+
wildcard = true;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
item = this.consume(TokenType.IDENTIFICADOR, "Esperado item importado").value;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else if (this.match(TokenType.COMO)) {
|
|
298
|
+
alias = this.consume(TokenType.IDENTIFICADOR, "Esperado alias").value;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
kind: 'Importacao',
|
|
302
|
+
line: moduloToken.line,
|
|
303
|
+
column: moduloToken.column,
|
|
304
|
+
modulo,
|
|
305
|
+
item,
|
|
306
|
+
wildcard,
|
|
307
|
+
alias
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
parseTela() {
|
|
311
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da tela");
|
|
312
|
+
const nome = nomeToken.value;
|
|
313
|
+
const tituloToken = this.consume(TokenType.LITERAL_TEXTO, "Esperado título da tela entre aspas");
|
|
314
|
+
const titulo = tituloToken.value;
|
|
315
|
+
const elementos = [];
|
|
316
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
317
|
+
elementos.push(this.parseTelaElemento());
|
|
318
|
+
}
|
|
319
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar tela");
|
|
320
|
+
return {
|
|
321
|
+
kind: 'Tela',
|
|
322
|
+
line: nomeToken.line,
|
|
323
|
+
column: nomeToken.column,
|
|
324
|
+
nome,
|
|
325
|
+
titulo,
|
|
326
|
+
elementos
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
parseTelaElemento() {
|
|
330
|
+
const tipoToken = this.consume(TokenType.IDENTIFICADOR, "Esperado tipo do elemento (tabela, formulario, botao, card)");
|
|
331
|
+
const tipo = tipoToken.value;
|
|
332
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do elemento");
|
|
333
|
+
const nome = nomeToken.value;
|
|
334
|
+
const propriedades = [];
|
|
335
|
+
while (!this.check(TokenType.FIM) && !this.isAtEnd()) {
|
|
336
|
+
// Chaves de propriedade podem ser keywords como 'entidade'
|
|
337
|
+
const chaveToken = this.consumeAny([
|
|
338
|
+
TokenType.IDENTIFICADOR,
|
|
339
|
+
TokenType.ENTIDADE,
|
|
340
|
+
], "Esperado nome da propriedade");
|
|
341
|
+
const chave = chaveToken.value;
|
|
342
|
+
this.consume(TokenType.DOIS_PONTOS, "Esperado ':' após nome da propriedade");
|
|
343
|
+
let valor;
|
|
344
|
+
if (this.check(TokenType.LITERAL_TEXTO)) {
|
|
345
|
+
valor = this.advance().value;
|
|
346
|
+
}
|
|
347
|
+
else if (this.check(TokenType.VERDADEIRO)) {
|
|
348
|
+
this.advance();
|
|
349
|
+
valor = 'verdadeiro';
|
|
350
|
+
}
|
|
351
|
+
else if (this.check(TokenType.FALSO)) {
|
|
352
|
+
this.advance();
|
|
353
|
+
valor = 'falso';
|
|
354
|
+
}
|
|
355
|
+
else if (this.checkAny([
|
|
356
|
+
TokenType.IDENTIFICADOR,
|
|
357
|
+
TokenType.TIPO_TEXTO, TokenType.TIPO_NUMERO, TokenType.TIPO_DECIMAL,
|
|
358
|
+
TokenType.TIPO_BOOLEANO, TokenType.TIPO_DATA, TokenType.TIPO_HORA,
|
|
359
|
+
TokenType.TIPO_ID, TokenType.TIPO_LISTA, TokenType.TIPO_MAPA
|
|
360
|
+
])) {
|
|
361
|
+
// Pode ser: nome simples, nome() chamada, ou lista: a, b, c
|
|
362
|
+
// Aceita palavras-chave de tipo (data, texto, etc.) como valores
|
|
363
|
+
const first = this.advance().value;
|
|
364
|
+
if (this.check(TokenType.ABRE_PAREN)) {
|
|
365
|
+
// acao: funcao()
|
|
366
|
+
this.advance(); // (
|
|
367
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')'");
|
|
368
|
+
valor = first + '()';
|
|
369
|
+
}
|
|
370
|
+
else if (this.check(TokenType.VIRGULA)) {
|
|
371
|
+
// lista: a, b, c (pode incluir palavras-chave de tipo como 'data')
|
|
372
|
+
const lista = [first];
|
|
373
|
+
while (this.match(TokenType.VIRGULA)) {
|
|
374
|
+
lista.push(this.consumeAny([
|
|
375
|
+
TokenType.IDENTIFICADOR,
|
|
376
|
+
TokenType.TIPO_TEXTO, TokenType.TIPO_NUMERO, TokenType.TIPO_DECIMAL,
|
|
377
|
+
TokenType.TIPO_BOOLEANO, TokenType.TIPO_DATA, TokenType.TIPO_HORA,
|
|
378
|
+
TokenType.TIPO_ID
|
|
379
|
+
], "Esperado identificador").value);
|
|
380
|
+
}
|
|
381
|
+
valor = lista;
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
valor = first;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
throw this.error("Esperado valor para a propriedade");
|
|
389
|
+
}
|
|
390
|
+
propriedades.push({ chave, valor });
|
|
391
|
+
}
|
|
392
|
+
this.consume(TokenType.FIM, `Esperado 'fim' para fechar elemento '${tipo}'`);
|
|
393
|
+
return {
|
|
394
|
+
kind: 'TelaElemento',
|
|
395
|
+
line: tipoToken.line,
|
|
396
|
+
column: tipoToken.column,
|
|
397
|
+
tipo,
|
|
398
|
+
nome,
|
|
399
|
+
propriedades
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
parseCampo() {
|
|
403
|
+
const nomeToken = this.consumeAny([
|
|
404
|
+
TokenType.IDENTIFICADOR,
|
|
405
|
+
TokenType.TIPO_ID,
|
|
406
|
+
TokenType.TIPO_TEXTO,
|
|
407
|
+
TokenType.TIPO_NUMERO,
|
|
408
|
+
TokenType.TIPO_DECIMAL,
|
|
409
|
+
TokenType.TIPO_BOOLEANO,
|
|
410
|
+
TokenType.TIPO_DATA,
|
|
411
|
+
TokenType.TIPO_HORA,
|
|
412
|
+
TokenType.TIPO_LISTA,
|
|
413
|
+
TokenType.TIPO_MAPA,
|
|
414
|
+
TokenType.TIPO_OBJETO
|
|
415
|
+
], "Esperado nome do campo");
|
|
416
|
+
const nome = nomeToken.value;
|
|
417
|
+
this.consume(TokenType.DOIS_PONTOS, "Esperado ':'");
|
|
418
|
+
const tipo = this.parseTipo();
|
|
419
|
+
return {
|
|
420
|
+
kind: 'Campo',
|
|
421
|
+
line: nomeToken.line,
|
|
422
|
+
column: nomeToken.column,
|
|
423
|
+
nome,
|
|
424
|
+
tipo
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
parseParametro() {
|
|
428
|
+
const nomeToken = this.consumeAny([
|
|
429
|
+
TokenType.IDENTIFICADOR,
|
|
430
|
+
TokenType.TIPO_ID,
|
|
431
|
+
TokenType.TIPO_TEXTO,
|
|
432
|
+
TokenType.TIPO_NUMERO,
|
|
433
|
+
TokenType.TIPO_DECIMAL,
|
|
434
|
+
TokenType.TIPO_BOOLEANO,
|
|
435
|
+
TokenType.TIPO_DATA,
|
|
436
|
+
TokenType.TIPO_HORA,
|
|
437
|
+
TokenType.TIPO_LISTA,
|
|
438
|
+
TokenType.TIPO_MAPA,
|
|
439
|
+
TokenType.TIPO_OBJETO
|
|
440
|
+
], "Esperado nome do parâmetro");
|
|
441
|
+
const nome = nomeToken.value;
|
|
442
|
+
this.consume(TokenType.DOIS_PONTOS, "Esperado ':'");
|
|
443
|
+
const tipo = this.parseTipo();
|
|
444
|
+
return {
|
|
445
|
+
kind: 'Parametro',
|
|
446
|
+
line: nomeToken.line,
|
|
447
|
+
column: nomeToken.column,
|
|
448
|
+
nome,
|
|
449
|
+
tipo
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
parseTipo() {
|
|
453
|
+
if (this.match(TokenType.TIPO_LISTA)) {
|
|
454
|
+
this.consume(TokenType.MENOR, "Esperado '<'");
|
|
455
|
+
const elementoTipo = this.parseTipo();
|
|
456
|
+
this.consume(TokenType.MAIOR, "Esperado '>'");
|
|
457
|
+
return {
|
|
458
|
+
kind: 'TipoLista',
|
|
459
|
+
line: elementoTipo.line,
|
|
460
|
+
column: elementoTipo.column,
|
|
461
|
+
elementoTipo
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
if (this.match(TokenType.TIPO_MAPA)) {
|
|
465
|
+
this.consume(TokenType.MENOR, "Esperado '<'");
|
|
466
|
+
const chaveTipo = this.parseTipo();
|
|
467
|
+
this.consume(TokenType.VIRGULA, "Esperado ','");
|
|
468
|
+
const valorTipo = this.parseTipo();
|
|
469
|
+
this.consume(TokenType.MAIOR, "Esperado '>'");
|
|
470
|
+
return {
|
|
471
|
+
kind: 'TipoMapa',
|
|
472
|
+
line: chaveTipo.line,
|
|
473
|
+
column: chaveTipo.column,
|
|
474
|
+
chaveTipo,
|
|
475
|
+
valorTipo
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
const token = this.consumeAny([
|
|
479
|
+
TokenType.IDENTIFICADOR,
|
|
480
|
+
TokenType.TIPO_ID,
|
|
481
|
+
TokenType.TIPO_TEXTO,
|
|
482
|
+
TokenType.TIPO_NUMERO,
|
|
483
|
+
TokenType.TIPO_DECIMAL,
|
|
484
|
+
TokenType.TIPO_BOOLEANO,
|
|
485
|
+
TokenType.TIPO_DATA,
|
|
486
|
+
TokenType.TIPO_HORA,
|
|
487
|
+
TokenType.TIPO_LISTA,
|
|
488
|
+
TokenType.TIPO_MAPA,
|
|
489
|
+
TokenType.TIPO_OBJETO
|
|
490
|
+
], "Esperado tipo");
|
|
491
|
+
let opcional = false;
|
|
492
|
+
let obrigatorio = false;
|
|
493
|
+
if (this.match(TokenType.INTERROGACAO)) {
|
|
494
|
+
opcional = true;
|
|
495
|
+
}
|
|
496
|
+
else if (this.match(TokenType.EXCLAMACAO)) {
|
|
497
|
+
obrigatorio = true;
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
kind: 'TipoSimples',
|
|
501
|
+
line: token.line,
|
|
502
|
+
column: token.column,
|
|
503
|
+
nome: token.value,
|
|
504
|
+
opcional,
|
|
505
|
+
obrigatorio
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
parseOuvinte() {
|
|
509
|
+
const eventoToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do evento");
|
|
510
|
+
const evento = eventoToken.value;
|
|
511
|
+
const corpo = this.parseBloco();
|
|
512
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar ouvinte");
|
|
513
|
+
return {
|
|
514
|
+
kind: 'Ouvinte',
|
|
515
|
+
line: eventoToken.line,
|
|
516
|
+
column: eventoToken.column,
|
|
517
|
+
evento,
|
|
518
|
+
corpo
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
parseBloco() {
|
|
522
|
+
const instrucoes = [];
|
|
523
|
+
while (!this.check(TokenType.FIM) && !this.check(TokenType.SENAO) && !this.isAtEnd()) {
|
|
524
|
+
const before = this.current;
|
|
525
|
+
const instrucao = this.parseInstrucao();
|
|
526
|
+
if (instrucao) {
|
|
527
|
+
instrucoes.push(instrucao);
|
|
528
|
+
}
|
|
529
|
+
// Evita loop infinito - se nenhum token foi consumido, avança ou lança erro
|
|
530
|
+
if (this.current === before) {
|
|
531
|
+
if (this.isAtEnd()) {
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
throw this.error(`Parser travado: token inesperado ${this.peek().type}='${this.peek().value}'`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
kind: 'Bloco',
|
|
539
|
+
line: 1,
|
|
540
|
+
column: 1,
|
|
541
|
+
instrucoes
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
parseInstrucao() {
|
|
545
|
+
if (this.match(TokenType.VARIAVEL))
|
|
546
|
+
return this.parseVariavel();
|
|
547
|
+
if (this.match(TokenType.RETORNAR))
|
|
548
|
+
return this.parseRetorno();
|
|
549
|
+
if (this.match(TokenType.SE))
|
|
550
|
+
return this.parseCondicional();
|
|
551
|
+
if (this.match(TokenType.ENQUANTO))
|
|
552
|
+
return this.parseEnquanto();
|
|
553
|
+
if (this.match(TokenType.PARA))
|
|
554
|
+
return this.parsePara();
|
|
555
|
+
if (this.match(TokenType.EMITIR))
|
|
556
|
+
return this.parseEmissaoEvento();
|
|
557
|
+
if (this.match(TokenType.ERRO))
|
|
558
|
+
return this.parseErro();
|
|
559
|
+
// Atribuição ou chamada de função
|
|
560
|
+
return this.parseAtribuicaoOuChamada();
|
|
561
|
+
}
|
|
562
|
+
parseVariavel() {
|
|
563
|
+
const nomeToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome da variável");
|
|
564
|
+
const nome = nomeToken.value;
|
|
565
|
+
let tipo;
|
|
566
|
+
if (this.match(TokenType.DOIS_PONTOS)) {
|
|
567
|
+
tipo = this.parseTipo();
|
|
568
|
+
}
|
|
569
|
+
let inicializador;
|
|
570
|
+
if (this.match(TokenType.IGUAL)) {
|
|
571
|
+
inicializador = this.parseExpressao();
|
|
572
|
+
}
|
|
573
|
+
return {
|
|
574
|
+
kind: 'Variavel',
|
|
575
|
+
line: nomeToken.line,
|
|
576
|
+
column: nomeToken.column,
|
|
577
|
+
nome,
|
|
578
|
+
tipo,
|
|
579
|
+
inicializador
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
parseAtribuicaoOuChamada() {
|
|
583
|
+
// Só age se o token atual for IDENTIFICADOR
|
|
584
|
+
if (!this.check(TokenType.IDENTIFICADOR)) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const nomeToken = this.advance();
|
|
588
|
+
const nome = nomeToken.value;
|
|
589
|
+
// Caso: nome.membro... (acesso a membro — pode ser atribuição ou chamada)
|
|
590
|
+
if (this.check(TokenType.PONTO)) {
|
|
591
|
+
// Constrói a cadeia de acesso: objeto.membro1.membro2...
|
|
592
|
+
let objeto = {
|
|
593
|
+
kind: 'Identificador',
|
|
594
|
+
nome,
|
|
595
|
+
line: nomeToken.line,
|
|
596
|
+
column: nomeToken.column
|
|
597
|
+
};
|
|
598
|
+
while (this.match(TokenType.PONTO)) {
|
|
599
|
+
const membroToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do membro após '.'");
|
|
600
|
+
const membro = membroToken.value;
|
|
601
|
+
// produto.metodo(...) — chamada de método
|
|
602
|
+
if (this.check(TokenType.ABRE_PAREN)) {
|
|
603
|
+
this.advance(); // consome '('
|
|
604
|
+
const argumentos = [];
|
|
605
|
+
if (!this.check(TokenType.FECHA_PAREN)) {
|
|
606
|
+
do {
|
|
607
|
+
argumentos.push(this.parseExpressao());
|
|
608
|
+
} while (this.match(TokenType.VIRGULA));
|
|
609
|
+
}
|
|
610
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')'");
|
|
611
|
+
objeto = {
|
|
612
|
+
kind: 'AcessoMembro',
|
|
613
|
+
objeto,
|
|
614
|
+
membro,
|
|
615
|
+
chamada: argumentos,
|
|
616
|
+
line: membroToken.line,
|
|
617
|
+
column: membroToken.column
|
|
618
|
+
};
|
|
619
|
+
// Após chamada de método como instrução, retorna
|
|
620
|
+
if (!this.check(TokenType.PONTO)) {
|
|
621
|
+
return objeto;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
objeto = {
|
|
626
|
+
kind: 'AcessoMembro',
|
|
627
|
+
objeto,
|
|
628
|
+
membro,
|
|
629
|
+
line: membroToken.line,
|
|
630
|
+
column: membroToken.column
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// produto.campo = valor — atribuição de membro
|
|
635
|
+
if (this.match(TokenType.IGUAL)) {
|
|
636
|
+
const valor = this.parseExpressao();
|
|
637
|
+
return {
|
|
638
|
+
kind: 'Atribuicao',
|
|
639
|
+
line: nomeToken.line,
|
|
640
|
+
column: nomeToken.column,
|
|
641
|
+
alvo: objeto,
|
|
642
|
+
valor
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
// Se chegou aqui sem atribuição, é uma expressão usada como instrução
|
|
646
|
+
return objeto;
|
|
647
|
+
}
|
|
648
|
+
// Caso: nome = valor — atribuição simples
|
|
649
|
+
if (this.match(TokenType.IGUAL)) {
|
|
650
|
+
const valor = this.parseExpressao();
|
|
651
|
+
return {
|
|
652
|
+
kind: 'Atribuicao',
|
|
653
|
+
line: nomeToken.line,
|
|
654
|
+
column: nomeToken.column,
|
|
655
|
+
alvo: nome,
|
|
656
|
+
valor
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
// Caso: nome(...) — chamada de função simples
|
|
660
|
+
if (this.match(TokenType.ABRE_PAREN)) {
|
|
661
|
+
const argumentos = [];
|
|
662
|
+
if (!this.check(TokenType.FECHA_PAREN)) {
|
|
663
|
+
do {
|
|
664
|
+
argumentos.push(this.parseExpressao());
|
|
665
|
+
} while (this.match(TokenType.VIRGULA));
|
|
666
|
+
}
|
|
667
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')'");
|
|
668
|
+
return {
|
|
669
|
+
kind: 'ChamadaFuncao',
|
|
670
|
+
line: nomeToken.line,
|
|
671
|
+
column: nomeToken.column,
|
|
672
|
+
nome,
|
|
673
|
+
argumentos
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
// Token consumido mas não reconhecido como instrução válida
|
|
677
|
+
// Volta o token para não perder (usando current--)
|
|
678
|
+
this.current--;
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
parseRetorno() {
|
|
682
|
+
let valor;
|
|
683
|
+
if (!this.check(TokenType.FIM) && !this.check(TokenType.EOF)) {
|
|
684
|
+
valor = this.parseExpressao();
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
kind: 'Retorno',
|
|
688
|
+
line: 1,
|
|
689
|
+
column: 1,
|
|
690
|
+
valor
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
parseCondicional() {
|
|
694
|
+
const condicao = this.parseExpressao();
|
|
695
|
+
const entao = this.parseBloco();
|
|
696
|
+
let senao;
|
|
697
|
+
if (this.match(TokenType.SENAO)) {
|
|
698
|
+
if (this.match(TokenType.SE)) {
|
|
699
|
+
// senao se — encadeia novo condicional sem fim próprio
|
|
700
|
+
const inner = this.parseCondicionalSemFim();
|
|
701
|
+
senao = {
|
|
702
|
+
kind: 'Bloco',
|
|
703
|
+
line: inner.line,
|
|
704
|
+
column: inner.column,
|
|
705
|
+
instrucoes: [inner]
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
senao = this.parseBloco();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar condicional");
|
|
713
|
+
return {
|
|
714
|
+
kind: 'Condicional',
|
|
715
|
+
line: condicao.line,
|
|
716
|
+
column: condicao.column,
|
|
717
|
+
condicao,
|
|
718
|
+
entao,
|
|
719
|
+
senao
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
parseCondicionalSemFim() {
|
|
723
|
+
const condicao = this.parseExpressao();
|
|
724
|
+
const entao = this.parseBloco();
|
|
725
|
+
let senao;
|
|
726
|
+
if (this.match(TokenType.SENAO)) {
|
|
727
|
+
if (this.match(TokenType.SE)) {
|
|
728
|
+
const inner = this.parseCondicionalSemFim();
|
|
729
|
+
senao = {
|
|
730
|
+
kind: 'Bloco',
|
|
731
|
+
line: inner.line,
|
|
732
|
+
column: inner.column,
|
|
733
|
+
instrucoes: [inner]
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
senao = this.parseBloco();
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
kind: 'Condicional',
|
|
742
|
+
line: condicao.line,
|
|
743
|
+
column: condicao.column,
|
|
744
|
+
condicao,
|
|
745
|
+
entao,
|
|
746
|
+
senao
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
parseEnquanto() {
|
|
750
|
+
const condicao = this.parseExpressao();
|
|
751
|
+
const corpo = this.parseBloco();
|
|
752
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar enquanto");
|
|
753
|
+
return {
|
|
754
|
+
kind: 'Enquanto',
|
|
755
|
+
line: condicao.line,
|
|
756
|
+
column: condicao.column,
|
|
757
|
+
condicao,
|
|
758
|
+
corpo
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
parsePara() {
|
|
762
|
+
const variavelToken = this.consume(TokenType.IDENTIFICADOR, "Esperado variável");
|
|
763
|
+
const variavel = variavelToken.value;
|
|
764
|
+
this.consume(TokenType.EM, "Esperado 'em' após variável do 'para'");
|
|
765
|
+
const iteravel = this.parseExpressao();
|
|
766
|
+
const corpo = this.parseBloco();
|
|
767
|
+
this.consume(TokenType.FIM, "Esperado 'fim' para fechar para");
|
|
768
|
+
return {
|
|
769
|
+
kind: 'Para',
|
|
770
|
+
line: variavelToken.line,
|
|
771
|
+
column: variavelToken.column,
|
|
772
|
+
variavel,
|
|
773
|
+
iteravel,
|
|
774
|
+
corpo
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
parseEmissaoEvento() {
|
|
778
|
+
const eventoToken = this.consume(TokenType.IDENTIFICADOR, "Esperado nome do evento");
|
|
779
|
+
const evento = eventoToken.value;
|
|
780
|
+
this.consume(TokenType.ABRE_PAREN, "Esperado '('");
|
|
781
|
+
const argumentos = [];
|
|
782
|
+
if (!this.check(TokenType.FECHA_PAREN)) {
|
|
783
|
+
do {
|
|
784
|
+
argumentos.push(this.parseExpressao());
|
|
785
|
+
} while (this.match(TokenType.VIRGULA));
|
|
786
|
+
}
|
|
787
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')'");
|
|
788
|
+
return {
|
|
789
|
+
kind: 'EmissaoEvento',
|
|
790
|
+
line: eventoToken.line,
|
|
791
|
+
column: eventoToken.column,
|
|
792
|
+
evento,
|
|
793
|
+
argumentos
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
parseErro() {
|
|
797
|
+
const mensagem = this.parseExpressao();
|
|
798
|
+
return {
|
|
799
|
+
kind: 'Erro',
|
|
800
|
+
line: mensagem.line,
|
|
801
|
+
column: mensagem.column,
|
|
802
|
+
mensagem
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
parseExpressao() {
|
|
806
|
+
return this.parseExpressaoLogica();
|
|
807
|
+
}
|
|
808
|
+
parseExpressaoLogica() {
|
|
809
|
+
let expr = this.parseExpressaoComparacao();
|
|
810
|
+
while (this.match(TokenType.OU) || this.match(TokenType.E)) {
|
|
811
|
+
const operador = this.previous().value;
|
|
812
|
+
const direita = this.parseExpressaoComparacao();
|
|
813
|
+
expr = {
|
|
814
|
+
kind: 'Binario',
|
|
815
|
+
line: expr.line,
|
|
816
|
+
column: expr.column,
|
|
817
|
+
esquerda: expr,
|
|
818
|
+
operador,
|
|
819
|
+
direita
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
return expr;
|
|
823
|
+
}
|
|
824
|
+
// O lexer salva apenas o último caractere do token para operadores duplos (==, !=, <=, >=)
|
|
825
|
+
// então derivamos o operador correto pelo tipo do token
|
|
826
|
+
tipoParaOperador(type) {
|
|
827
|
+
switch (type) {
|
|
828
|
+
case TokenType.IGUAL_IGUAL: return '==';
|
|
829
|
+
case TokenType.DIFERENTE: return '!=';
|
|
830
|
+
case TokenType.MENOR_IGUAL: return '<=';
|
|
831
|
+
case TokenType.MAIOR_IGUAL: return '>=';
|
|
832
|
+
case TokenType.MENOR: return '<';
|
|
833
|
+
case TokenType.MAIOR: return '>';
|
|
834
|
+
default: return this.previous().value;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
parseExpressaoComparacao() {
|
|
838
|
+
let expr = this.parseExpressaoAritmetica();
|
|
839
|
+
while (this.match(TokenType.IGUAL_IGUAL) || this.match(TokenType.DIFERENTE) ||
|
|
840
|
+
this.match(TokenType.MENOR) || this.match(TokenType.MENOR_IGUAL) ||
|
|
841
|
+
this.match(TokenType.MAIOR) || this.match(TokenType.MAIOR_IGUAL)) {
|
|
842
|
+
const operador = this.tipoParaOperador(this.previous().type);
|
|
843
|
+
const direita = this.parseExpressaoAritmetica();
|
|
844
|
+
expr = {
|
|
845
|
+
kind: 'Binario',
|
|
846
|
+
line: expr.line,
|
|
847
|
+
column: expr.column,
|
|
848
|
+
esquerda: expr,
|
|
849
|
+
operador,
|
|
850
|
+
direita
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
return expr;
|
|
854
|
+
}
|
|
855
|
+
parseExpressaoAritmetica() {
|
|
856
|
+
let expr = this.parseTermo();
|
|
857
|
+
while (this.match(TokenType.MAIS) || this.match(TokenType.MENOS)) {
|
|
858
|
+
const operador = this.previous().value;
|
|
859
|
+
const direita = this.parseTermo();
|
|
860
|
+
expr = {
|
|
861
|
+
kind: 'Binario',
|
|
862
|
+
line: expr.line,
|
|
863
|
+
column: expr.column,
|
|
864
|
+
esquerda: expr,
|
|
865
|
+
operador,
|
|
866
|
+
direita
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return expr;
|
|
870
|
+
}
|
|
871
|
+
parseTermo() {
|
|
872
|
+
let expr = this.parseFator();
|
|
873
|
+
while (this.match(TokenType.ASTERISCO) || this.match(TokenType.BARRA)) {
|
|
874
|
+
const operador = this.previous().value;
|
|
875
|
+
const direita = this.parseFator();
|
|
876
|
+
expr = {
|
|
877
|
+
kind: 'Binario',
|
|
878
|
+
line: expr.line,
|
|
879
|
+
column: expr.column,
|
|
880
|
+
esquerda: expr,
|
|
881
|
+
operador,
|
|
882
|
+
direita
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
return expr;
|
|
886
|
+
}
|
|
887
|
+
parseFator() {
|
|
888
|
+
if (this.match(TokenType.MENOS) || this.match(TokenType.NAO)) {
|
|
889
|
+
const operador = this.previous().value;
|
|
890
|
+
const operando = this.parseFator();
|
|
891
|
+
return {
|
|
892
|
+
kind: 'Unario',
|
|
893
|
+
line: operando.line,
|
|
894
|
+
column: operando.column,
|
|
895
|
+
operador,
|
|
896
|
+
operando
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
return this.parsePrimario();
|
|
900
|
+
}
|
|
901
|
+
parsePrimario() {
|
|
902
|
+
if (this.match(TokenType.LITERAL_NUMERO)) {
|
|
903
|
+
const token = this.previous();
|
|
904
|
+
return {
|
|
905
|
+
kind: 'Literal',
|
|
906
|
+
line: token.line,
|
|
907
|
+
column: token.column,
|
|
908
|
+
valor: Number(token.value),
|
|
909
|
+
tipoLiteral: 'numero'
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
if (this.match(TokenType.LITERAL_TEXTO)) {
|
|
913
|
+
const token = this.previous();
|
|
914
|
+
return {
|
|
915
|
+
kind: 'Literal',
|
|
916
|
+
line: token.line,
|
|
917
|
+
column: token.column,
|
|
918
|
+
valor: token.value,
|
|
919
|
+
tipoLiteral: 'texto'
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
if (this.match(TokenType.VERDADEIRO)) {
|
|
923
|
+
const token = this.previous();
|
|
924
|
+
return {
|
|
925
|
+
kind: 'Literal',
|
|
926
|
+
line: token.line,
|
|
927
|
+
column: token.column,
|
|
928
|
+
valor: true,
|
|
929
|
+
tipoLiteral: 'booleano'
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
if (this.match(TokenType.FALSO)) {
|
|
933
|
+
const token = this.previous();
|
|
934
|
+
return {
|
|
935
|
+
kind: 'Literal',
|
|
936
|
+
line: token.line,
|
|
937
|
+
column: token.column,
|
|
938
|
+
valor: false,
|
|
939
|
+
tipoLiteral: 'booleano'
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
if (this.match(TokenType.ABRE_PAREN)) {
|
|
943
|
+
const expr = this.parseExpressao();
|
|
944
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')' após expressão");
|
|
945
|
+
return expr;
|
|
946
|
+
}
|
|
947
|
+
if (this.match(TokenType.LITERAL_DECIMAL)) {
|
|
948
|
+
const token = this.previous();
|
|
949
|
+
return {
|
|
950
|
+
kind: 'Literal',
|
|
951
|
+
line: token.line,
|
|
952
|
+
column: token.column,
|
|
953
|
+
valor: Number(token.value),
|
|
954
|
+
tipoLiteral: 'decimal'
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
if (this.match(TokenType.IDENTIFICADOR)) {
|
|
958
|
+
const token = this.previous();
|
|
959
|
+
// nome(...) — chamada de função em expressão
|
|
960
|
+
if (this.match(TokenType.ABRE_PAREN)) {
|
|
961
|
+
const argumentos = [];
|
|
962
|
+
if (!this.check(TokenType.FECHA_PAREN)) {
|
|
963
|
+
do {
|
|
964
|
+
argumentos.push(this.parseExpressao());
|
|
965
|
+
} while (this.match(TokenType.VIRGULA));
|
|
966
|
+
}
|
|
967
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')' após argumentos");
|
|
968
|
+
return {
|
|
969
|
+
kind: 'ChamadaFuncao',
|
|
970
|
+
line: token.line,
|
|
971
|
+
column: token.column,
|
|
972
|
+
nome: token.value,
|
|
973
|
+
argumentos
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
// nome.membro... — acesso a membro (encadeado)
|
|
977
|
+
if (this.check(TokenType.PONTO)) {
|
|
978
|
+
let objeto = {
|
|
979
|
+
kind: 'Identificador',
|
|
980
|
+
line: token.line,
|
|
981
|
+
column: token.column,
|
|
982
|
+
nome: token.value
|
|
983
|
+
};
|
|
984
|
+
while (this.match(TokenType.PONTO)) {
|
|
985
|
+
const membroToken = this.consumeAny([
|
|
986
|
+
TokenType.IDENTIFICADOR,
|
|
987
|
+
TokenType.TIPO_ID, TokenType.TIPO_TEXTO, TokenType.TIPO_NUMERO,
|
|
988
|
+
TokenType.TIPO_DECIMAL, TokenType.TIPO_BOOLEANO, TokenType.TIPO_DATA,
|
|
989
|
+
TokenType.TIPO_HORA, TokenType.TIPO_LISTA, TokenType.TIPO_MAPA, TokenType.TIPO_OBJETO
|
|
990
|
+
], "Esperado nome do membro");
|
|
991
|
+
if (this.match(TokenType.ABRE_PAREN)) {
|
|
992
|
+
const argumentos = [];
|
|
993
|
+
if (!this.check(TokenType.FECHA_PAREN)) {
|
|
994
|
+
do {
|
|
995
|
+
argumentos.push(this.parseExpressao());
|
|
996
|
+
} while (this.match(TokenType.VIRGULA));
|
|
997
|
+
}
|
|
998
|
+
this.consume(TokenType.FECHA_PAREN, "Esperado ')'");
|
|
999
|
+
objeto = { kind: 'AcessoMembro', line: membroToken.line, column: membroToken.column, objeto, membro: membroToken.value, chamada: argumentos };
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
objeto = { kind: 'AcessoMembro', line: membroToken.line, column: membroToken.column, objeto, membro: membroToken.value };
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return objeto;
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
kind: 'Identificador',
|
|
1009
|
+
line: token.line,
|
|
1010
|
+
column: token.column,
|
|
1011
|
+
nome: token.value
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
throw this.error(`Expressão inesperada: ${this.peek().value}`);
|
|
1015
|
+
}
|
|
1016
|
+
// ── Utilitários ───────────────────────────────────────────
|
|
1017
|
+
peek() {
|
|
1018
|
+
return this.tokens[this.current];
|
|
1019
|
+
}
|
|
1020
|
+
previous() {
|
|
1021
|
+
return this.tokens[this.current - 1];
|
|
1022
|
+
}
|
|
1023
|
+
advance() {
|
|
1024
|
+
if (!this.isAtEnd())
|
|
1025
|
+
this.current++;
|
|
1026
|
+
return this.previous();
|
|
1027
|
+
}
|
|
1028
|
+
checkAny(types) {
|
|
1029
|
+
for (const type of types) {
|
|
1030
|
+
if (this.check(type))
|
|
1031
|
+
return true;
|
|
1032
|
+
}
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
check(type) {
|
|
1036
|
+
if (this.isAtEnd())
|
|
1037
|
+
return false;
|
|
1038
|
+
return this.peek().type === type;
|
|
1039
|
+
}
|
|
1040
|
+
match(...types) {
|
|
1041
|
+
for (const type of types) {
|
|
1042
|
+
if (this.check(type)) {
|
|
1043
|
+
this.advance();
|
|
1044
|
+
return true;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
consume(type, message) {
|
|
1050
|
+
if (this.check(type))
|
|
1051
|
+
return this.advance();
|
|
1052
|
+
throw this.error(message);
|
|
1053
|
+
}
|
|
1054
|
+
consumeAny(types, message) {
|
|
1055
|
+
for (const type of types) {
|
|
1056
|
+
if (this.check(type))
|
|
1057
|
+
return this.advance();
|
|
1058
|
+
}
|
|
1059
|
+
throw this.error(message);
|
|
1060
|
+
}
|
|
1061
|
+
isAtEnd() {
|
|
1062
|
+
return this.peek().type === TokenType.EOF;
|
|
1063
|
+
}
|
|
1064
|
+
error(message, token) {
|
|
1065
|
+
const errorToken = token || this.peek();
|
|
1066
|
+
return {
|
|
1067
|
+
message,
|
|
1068
|
+
line: errorToken.line,
|
|
1069
|
+
column: errorToken.column
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
synchronize() {
|
|
1073
|
+
this.advance();
|
|
1074
|
+
while (!this.isAtEnd()) {
|
|
1075
|
+
if (this.previous().type === TokenType.FIM)
|
|
1076
|
+
return;
|
|
1077
|
+
switch (this.peek().type) {
|
|
1078
|
+
case TokenType.CLASSE:
|
|
1079
|
+
case TokenType.ENTIDADE:
|
|
1080
|
+
case TokenType.SERVICO:
|
|
1081
|
+
case TokenType.FUNCAO:
|
|
1082
|
+
case TokenType.EVENTO:
|
|
1083
|
+
case TokenType.REGRA:
|
|
1084
|
+
case TokenType.INTERFACE:
|
|
1085
|
+
case TokenType.ENUM:
|
|
1086
|
+
case TokenType.TELA:
|
|
1087
|
+
case TokenType.MODULO:
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
this.advance();
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
//# sourceMappingURL=parser.js.map
|