bahasa-simpl 1.0.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.
@@ -0,0 +1,2157 @@
1
+ // Errors
2
+ class SimplError extends Error {
3
+ constructor(message, line) {
4
+ super(message);
5
+ this.line = line;
6
+ }
7
+ }class SimplErrorEksekusi extends SimplError {}class SimplErrorStruktur extends SimplError {}class SimplErrorTulisan extends SimplError {}
8
+
9
+ // Environment defines a scope for all data to live in
10
+ class Environment {
11
+
12
+ constructor(environment) {
13
+ this.enclosing = environment;
14
+ this.memory = new Map();
15
+ }
16
+
17
+ define(thing, value) {
18
+ this.memory.set(thing, value);
19
+ }
20
+
21
+ assign(thing, value) {
22
+ if (this.has(thing)) {
23
+ this.memory.set(thing, value);
24
+ } else if (this.enclosing) {
25
+ this.enclosing.assign(thing, value);
26
+ }
27
+
28
+ return null;
29
+ }
30
+
31
+ get(thing) {
32
+ if (this.has(thing)) {
33
+ return this.memory.get(thing);
34
+ } else if (this.enclosing) {
35
+ return this.enclosing.get(thing);
36
+ }
37
+
38
+ return null;
39
+ }
40
+
41
+ has(thing) {
42
+ return this.memory.has(thing);
43
+ }
44
+
45
+ }
46
+
47
+ const RESERVED_KEYWORDS = [
48
+ "rubah",
49
+ "kalau",
50
+ "namun",
51
+ "slagi",
52
+ "untuk",
53
+ "cetak",
54
+ "henti",
55
+ "lewat",
56
+ "dalam",
57
+ "hasil",
58
+ "kerja",
59
+ "datum",
60
+ "jenis",
61
+ "model",
62
+ "error",
63
+ "tetap",
64
+ "modul",
65
+ ];
66
+
67
+ const RUBAH = 0,
68
+ KALAU = 1,
69
+ NAMUN = 2,
70
+ SLAGI = 3,
71
+ UNTUK = 4,
72
+ CETAK = 5,
73
+ HENTI = 6,
74
+ LEWAT = 7,
75
+ DALAM = 8,
76
+ HASIL = 9,
77
+ KERJA = 10,
78
+ DATUM = 11,
79
+ JENIS = 12,
80
+ MODEL = 13,
81
+ TETAP = 15,
82
+ MODUL = 16,
83
+ EOF = 17,
84
+ ID = 18,
85
+ LITERAL = 19,
86
+ PLUS = 20,
87
+ MINUS = 21,
88
+ STAR = 22,
89
+ SLASH = 23,
90
+ LPAREN = 24,
91
+ RPAREN = 25,
92
+ GREATER = 26,
93
+ GREATER_EQUAL = 27,
94
+ LESS = 28,
95
+ LESS_EQUAL = 29,
96
+ EQUAL = 30,
97
+ EQUAL_EQUAL = 31,
98
+ DOT = 32,
99
+ LCURLY = 33,
100
+ RCURLY = 34,
101
+ COMMA = 35,
102
+ LSQUARE = 36,
103
+ RSQUARE = 37,
104
+ PIPE = 38,
105
+ AMPERSAND = 39,
106
+ BANG = 40,
107
+ ARROW = 41,
108
+ BANG_EQUAL = 42,
109
+ MODULUS = 43;
110
+
111
+ const TOKEN_STRING = [
112
+ "RUBAH",
113
+ "KALAU",
114
+ "NAMUN",
115
+ "SLAGI",
116
+ "UNTUK",
117
+ "CETAK",
118
+ "HENTI",
119
+ "LEWAT",
120
+ "DALAM",
121
+ "HASIL",
122
+ "KERJA",
123
+ "DATUM",
124
+ "JENIS",
125
+ "MODEL",
126
+ "ERROR",
127
+ "TETAP",
128
+ "MODUL",
129
+ "EOF",
130
+ "ID",
131
+ "LITERAL",
132
+ "PLUS",
133
+ "MINUS",
134
+ "BINTANG",
135
+ "GARIS_MIRING",
136
+ "LPAREN",
137
+ "RPAREN",
138
+ "LEBIH",
139
+ "LEBIH_SAMA",
140
+ "KURANG",
141
+ "KURANG_SAMA",
142
+ "SAMA",
143
+ "SAMA_SAMA",
144
+ "DOT",
145
+ "LCURLY",
146
+ "RCURLY",
147
+ "COMMA",
148
+ "LSQUARE",
149
+ "RSQUARE",
150
+ "PIPA",
151
+ "AMPERSAN",
152
+ "SERU",
153
+ "ARROW",
154
+ "SERU_SAMA",
155
+ "MODULUS",
156
+ ];
157
+
158
+ const RESERVED_NAMES = [
159
+ "petik", "angka", "logis", "mesin", "baris", "stipe", "modul"
160
+ ];
161
+
162
+ const petikSymbol = Symbol("petik"),
163
+ angkaSymbol = Symbol("angka"),
164
+ logisSymbol = Symbol("logis"),
165
+ mesinSymbol = Symbol("mesin"),
166
+ barisSymbol = Symbol("baris"),
167
+ stipeSymbol = Symbol("stipe"),
168
+ modulSymbol = Symbol("modul");
169
+
170
+ class Value {
171
+ constructor(type, data) {
172
+ this.type = type;
173
+ this.data = data;
174
+ }
175
+ }
176
+
177
+ class Variable extends Value {
178
+ constructor(type, tetap, data) {
179
+ super(type, data);
180
+ this.tetap = tetap;
181
+ this.isDatum = false;
182
+ }
183
+ }
184
+
185
+ class Stipe extends Variable {
186
+ constructor(type, data) {
187
+ super(stipeSymbol, true, data);
188
+ this.symbol = type;
189
+ this.operators = new Environment();
190
+ this.member = null;
191
+ }
192
+
193
+ operate(visitor, op, right, left) {
194
+ let opLexeme = TOKEN_STRING[op] + (left ? "" : "_UNARY");
195
+ let operatorFunc = this.operators.get(opLexeme);
196
+ if (!operatorFunc)
197
+ visitor.error(`operator ${opLexeme} tidak terdefinisi untuk Model ${right.type.description}.`);
198
+
199
+ let result = null;
200
+ if (left) { // Binary
201
+ result = operatorFunc.data.callFunc(visitor, [right, left]);
202
+ } else { // Unary, safe because valid unary op is just + - ! in the parser
203
+ result = operatorFunc.data.callFunc(visitor, [right]);
204
+ }
205
+ return result;
206
+ }
207
+ }
208
+
209
+ class PetikTipe extends Stipe {
210
+ constructor() {
211
+ super(petikSymbol,
212
+ {
213
+ callFunc: (v,args) => {
214
+ if (args.length !== 1) {
215
+ v.error(`Jumlah argumen tidak sama dengan parameter mesin: ${args.length} != 1.`);
216
+ }
217
+
218
+ const kePetik = (thing) => {
219
+ if (thing.type === logisSymbol) {
220
+ return thing.data ? "benar" : "salah";
221
+ } else if (thing.type === barisSymbol) {
222
+ return '[' + thing.data.reduce((str, val)=>str+" "+kePetik(val)+",", "") + ' ]'
223
+ } else if (thing.type === stipeSymbol) {
224
+ return `Model:${thing.symbol.description}`;
225
+ } else if (thing.type === mesinSymbol) {
226
+ return `Mesin<>`;
227
+ } else if (thing.type === angkaSymbol || thing.type === petikSymbol) {
228
+ return thing.data !== null ? thing.data.toString() : "nihil";
229
+ } else {
230
+ return `Objek<${thing.symbol.description}>` + thing;
231
+ }
232
+ };
233
+
234
+ return new Value(petikSymbol, kePetik(args[0]));
235
+ }
236
+
237
+ });
238
+ this.init();
239
+ }
240
+
241
+ init() {
242
+ // BINARY / UNARY OPERATORS
243
+ this.operators.define("PLUS",
244
+ makeBuiltInFunc([petikSymbol, petikSymbol], petikSymbol, (v, [r, l]) => new Value(petikSymbol, l.data+r.data)));
245
+ this.operators.define("LEBIH",
246
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data>r.data)));
247
+ this.operators.define("KURANG",
248
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data<r.data)));
249
+ this.operators.define("SAMA_SAMA",
250
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data===r.data)));
251
+ this.operators.define("LEBIH_SAMA",
252
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data>=r.data)));
253
+ this.operators.define("KURANG_SAMA",
254
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data<=r.data)));
255
+ this.operators.define("SERU_SAMA",
256
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data!==r.data)));
257
+ this.operators.define("AMPERSAN",
258
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data&&r.data)));
259
+ this.operators.define("PIPA",
260
+ makeBuiltInFunc([petikSymbol, petikSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data||r.data)));
261
+
262
+ this.operators.define("SERU_UNARY",
263
+ makeBuiltInFunc([petikSymbol], logisSymbol, (v, [r]) => new Value(logisSymbol, !Boolean(r.data))));
264
+
265
+ this.member = new Environment();
266
+
267
+ this.member.define("pisah", makeBuiltInFunc([petikSymbol, petikSymbol], barisSymbol, (v, [d, sep])=>{
268
+ return new Value(barisSymbol, d.data.split(sep.data).map(val=>new Value(petikSymbol, val)));
269
+ }));
270
+ this.member.define("bersih", makeBuiltInFunc([petikSymbol], petikSymbol, (v, [d])=> {
271
+ return new Value(petikSymbol, d.data.trim())
272
+ }));
273
+ this.member.define("ganti", makeBuiltInFunc([petikSymbol, petikSymbol, petikSymbol], petikSymbol,
274
+ (v, [d, what, rep]) => new Value(petikSymbol, d.data.replaceAll(what.data, rep.data))
275
+ ));
276
+ }
277
+ }
278
+
279
+ class AngkaTipe extends Stipe {
280
+ constructor() {
281
+ super(angkaSymbol, {
282
+ callFunc: (v, args) => {
283
+ if (args.length !== 1) {
284
+ v.error(`Jumlah argumen tidak sama dengan parameter mesin: ${args.length} != 1.`);
285
+ }
286
+
287
+ switch (args[0].type) {
288
+ case petikSymbol:
289
+ if (isNaN(Number(args[0].data))) {
290
+ v.error("Nilai dari petik bukanlah sebuah angka, konversi gagal.");
291
+ }
292
+ return new Value(angkaSymbol, Number(args[0].data));
293
+ case angkaSymbol:
294
+ return new Value(angkaSymbol, args[0].data);
295
+ case logisSymbol:
296
+ return new Value(angkaSymbol, Number(args[0].data));
297
+ default:
298
+ v.error(`Tipe yang dapat diterima hanyalah petik, angka, dan logis. Mendapatkan ${args[0].type.description}.`);
299
+ return;
300
+ }
301
+ }
302
+ });
303
+ this.init();
304
+ }
305
+
306
+ init() {
307
+ this.operators.define("PLUS",
308
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], angkaSymbol, (v, [r, l]) => new Value(angkaSymbol, l.data+r.data)));
309
+ this.operators.define("MINUS",
310
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], angkaSymbol, (v, [r, l]) => new Value(angkaSymbol, l.data-r.data)));
311
+ this.operators.define("BINTANG",
312
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], angkaSymbol, (v, [r, l]) => new Value(angkaSymbol, l.data*r.data)));
313
+ this.operators.define("GARIS_MIRING",
314
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], angkaSymbol, (v, [r, l]) => new Value(angkaSymbol, l.data/r.data)));
315
+ this.operators.define("MODULUS",
316
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], angkaSymbol, (v, [r, l]) => new Value(angkaSymbol, l.data%r.data)));
317
+
318
+ this.operators.define("LEBIH",
319
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data>r.data)));
320
+ this.operators.define("KURANG",
321
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data<r.data)));
322
+ this.operators.define("SAMA_SAMA",
323
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data===r.data)));
324
+ this.operators.define("LEBIH_SAMA",
325
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data>=r.data)));
326
+ this.operators.define("KURANG_SAMA",
327
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data<=r.data)));
328
+ this.operators.define("SERU_SAMA",
329
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data!==r.data)));
330
+ this.operators.define("AMPERSAN",
331
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data&&r.data)));
332
+ this.operators.define("PIPA",
333
+ makeBuiltInFunc([angkaSymbol, angkaSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data||r.data)));
334
+ this.operators.define("SERU_UNARY",
335
+ makeBuiltInFunc([angkaSymbol], logisSymbol, (v, [r]) => new Value(logisSymbol, !Boolean(r.data))));
336
+ this.operators.define("PLUS_UNARY",
337
+ makeBuiltInFunc([angkaSymbol], angkaSymbol, (v, [r]) => new Value(angkaSymbol, +r.data)));
338
+ this.operators.define("MINUS_UNARY",
339
+ makeBuiltInFunc([angkaSymbol], angkaSymbol, (v, [r]) => new Value(angkaSymbol, -r.data)));
340
+
341
+ this.member = new Environment();
342
+ }
343
+ }
344
+
345
+ class LogisTipe extends Stipe {
346
+ constructor() {
347
+ super(logisSymbol, {
348
+ callFunc: (v, args) => {
349
+ if (args.length !== 1) {
350
+ v.error(`Jumlah argumen tidak sama dengan parameter mesin: ${args.length} != 1.`);
351
+ }
352
+
353
+ return new Value(logisSymbol, Boolean(args[0].data) || Boolean(args[0].data.member));
354
+ }
355
+ });
356
+ this.init();
357
+ }
358
+
359
+ init() {
360
+ this.operators.define("SAMA_SAMA",
361
+ makeBuiltInFunc([logisSymbol, logisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data===r.data)));
362
+ this.operators.define("SERU_SAMA",
363
+ makeBuiltInFunc([logisSymbol, logisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data!==r.data)));
364
+
365
+ this.operators.define("AMPERSAN",
366
+ makeBuiltInFunc([logisSymbol, logisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data&&r.data)));
367
+ this.operators.define("PIPA",
368
+ makeBuiltInFunc([logisSymbol, logisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data||r.data)));
369
+ this.operators.define("SERU_UNARY",
370
+ makeBuiltInFunc([logisSymbol], logisSymbol, (v, [r]) => new Value(logisSymbol, !Boolean(r.data))));
371
+
372
+ this.member = true;
373
+ }
374
+ }
375
+
376
+ class BarisTipe extends Stipe {
377
+ constructor() {
378
+ super(barisSymbol);
379
+ this.init();
380
+ }
381
+
382
+ init () {
383
+ this.operators.define("PLUS",
384
+ makeBuiltInFunc([barisSymbol, barisSymbol], barisSymbol, (v, [r,l]) => new Value(barisSymbol, Array(...l.data, ...r.data))));
385
+
386
+ this.operators.define("MINUS_UNARY",
387
+ makeBuiltInFunc([barisSymbol], barisSymbol, (v, [r]) => {
388
+ let newBaris = copier(r);
389
+ newBaris.data.pop();
390
+ return newBaris;
391
+ }));
392
+
393
+ this.operators.define("SAMA_SAMA",
394
+ makeBuiltInFunc([barisSymbol, barisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, ((a,b)=>{
395
+ if (a.length !== b.length) return false;
396
+ for (let i = 0; i < a.length; i++) {
397
+ if (a[i].data !== b[i].data) return false;
398
+ }
399
+ return true;
400
+ })(l.data,r.data)))
401
+ );
402
+
403
+ this.operators.define("SERU_SAMA",
404
+ makeBuiltInFunc([barisSymbol, barisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, ((a,b)=>{
405
+ if (a.length !== b.length) return true;
406
+ for (let i = 0; i < a.length; i++) {
407
+ if (a[i].data !== b[i].data) return true;
408
+ }
409
+ return false;
410
+ })(l.data,r.data)))
411
+ );
412
+
413
+ this.operators.define("AMPERSAN",
414
+ makeBuiltInFunc([barisSymbol, barisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data&&r.data)));
415
+ this.operators.define("PIPA",
416
+ makeBuiltInFunc([barisSymbol, barisSymbol], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data||r.data)));
417
+ this.operators.define("SERU_UNARY",
418
+ makeBuiltInFunc([barisSymbol], logisSymbol, (v, [r]) => new Value(logisSymbol, !Boolean(r.data))));
419
+
420
+ this.member = new Environment();
421
+
422
+ this.member.define("hapus", makeBuiltInFunc([barisSymbol, angkaSymbol], barisSymbol, (v, [b, i]) => {
423
+ if (b.data.length <= i.data) {
424
+ v.error(`Indeks tidak boleh lebih besar atau sama dengan ukuran baris, ${i.data} >= ${b.data.length}`);
425
+ }
426
+
427
+ while (i.data < 0) i.data += b.data.length;
428
+ return new Value(barisSymbol, b.data.filter((val, idx)=>idx!==i.data));
429
+ }));
430
+ }
431
+ }
432
+
433
+ class MesinTipe extends Stipe {
434
+ constructor() {
435
+ super(mesinSymbol);
436
+ this.member = true;
437
+ }
438
+ }
439
+
440
+ let Model$1 = class Model extends Stipe {
441
+ constructor(name, params) {
442
+ let sym = Symbol(name);
443
+ super(sym, {
444
+ callFunc: (v, args) => {
445
+ if (args.length !== params.length) {
446
+ v.error("Jumlah argumen tidak sama dengan parameter mesin:" + ` ${args.length} != ${params.length}.`);
447
+ }
448
+ // for call error info
449
+ let callLineNum = v.line;
450
+
451
+ let obj = new Value(sym, true);
452
+ obj.member = new Environment();
453
+
454
+ for (let i = 0; i < args.length; i++) {
455
+ let type = params[i][0];
456
+ let symbol = type.accept(v);
457
+ let name = params[i][1].lexeme;
458
+
459
+ let val = new Variable(symbol, type.tetap, args[i].data);
460
+ if (args[i].data === null) ; else if (symbol === null) {
461
+ val.isDatum = true;
462
+ val.type = args[i].type;
463
+ } else if (symbol !== args[i].type) {
464
+ v.line = callLineNum;
465
+ v.error(`Tipe member tidak sama dengan argumen. ${symbol.description} != ${args[i].type.description}`);
466
+ }
467
+ val.member = args[i].member;
468
+ obj.member.define(name, val);
469
+ }
470
+
471
+ obj.member.define("objek", obj);
472
+ return obj;
473
+ }
474
+ });
475
+ }
476
+ };
477
+
478
+ let Jenis$1 = class Jenis extends Stipe {
479
+ constructor(name, enums) {
480
+ let sym = Symbol(name);
481
+ super(sym, null);
482
+ this.member = new Environment();
483
+ enums.forEach((thing, idx) => {
484
+ this.member.define(thing.lexeme, new Value(sym, idx));
485
+ });
486
+ this.init(sym);
487
+ }
488
+
489
+ init(sym) {
490
+ this.operators.define("SAMA_SAMA",
491
+ makeBuiltInFunc([sym, sym], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data===r.data)));
492
+ this.operators.define("SERU_SAMA",
493
+ makeBuiltInFunc([sym, sym], logisSymbol, (v, [r, l]) => new Value(logisSymbol, l.data!==r.data)));
494
+ }
495
+ };
496
+
497
+ class Callable {
498
+ constructor(enclosing, block, parameters, returnType) {
499
+ this.closure = enclosing;
500
+ this.block = block;
501
+ this.parameters = parameters;
502
+ this.returnType = returnType;
503
+ }
504
+
505
+ // call with arguments (of value)
506
+ callFunc (visitor, args) {
507
+ // this function may go into the interpreter idk
508
+ if (args.length !== this.parameters.length) {
509
+ visitor.error("Jumlah argumen tidak sama dengan parameter mesin:" + ` ${args.length} != ${this.parameters.length}.`);
510
+ }
511
+
512
+ // for asserting _call_ error.
513
+ let callLineNum = visitor.line;
514
+
515
+ let visitorEnv = visitor.environment;
516
+ let funcEnv = new Environment(this.closure);
517
+ visitor.environment = funcEnv;
518
+ for(let i = 0; i < args.length; i++) {
519
+ let [type, tetap, name] = this.parameters[i];
520
+ let val = new Variable(type, tetap, args[i].data);
521
+
522
+ if (type === null) {
523
+ val.isDatum = true;
524
+ val.type = args[i].type;
525
+ } else if (args[i].data === null) ; else if (args[i].type !== type) {
526
+ visitor.line = callLineNum;
527
+ visitor.error(`Tipe argumen tidak sama dengan parameter. ${args[i].type} != ${type.description}`);
528
+ }
529
+ val.member = args[i].member;
530
+ funcEnv.define(name, val);
531
+ }
532
+ let result = new Value(null, null);
533
+ for(let stmt of this.block.statements) {
534
+ try {
535
+ stmt.accept(visitor);
536
+ } catch (err) {
537
+ if (err instanceof Hasil$1) {
538
+ result = err.value;
539
+ break;
540
+ } else if (err instanceof Henti$1 || err instanceof Lewat$1) {
541
+ visitor.error("Tidak bisa menghentikan atau melewatkan mesin. ");
542
+ } else throw err;
543
+ }
544
+ }
545
+ visitor.environment = visitorEnv;
546
+ return result;
547
+ }
548
+ }
549
+
550
+ // this approach is too slow. Will fix later...
551
+
552
+ function makeBuiltInFunc(parameters, returnType, funcBody) {
553
+ let lambda = new Callable(null, null, parameters.map((val)=>[val]), returnType);
554
+ lambda.callFunc = (v, args) => {
555
+ if (args.length != parameters.length) {
556
+ v.error("Jumlah Argumen tidak sama dengan parameter:" + ` Seharusnya ${parameters.length} dan bukan ${args.length}.`);
557
+ }
558
+ for (let i = 0; i < args.length; i++) {
559
+ if (parameters[i] === null) continue;
560
+ if (args[i].type !== parameters[i]) {
561
+ v.error("Tipe argumen tidak sama dengan tipe parameter." + ` Seharusnya ${parameters[i].type.description} dan bukan ${args[i].description}`);
562
+ }
563
+ }
564
+ return funcBody(v, args);
565
+ };
566
+ let variable = new Variable(mesinSymbol, true, lambda);
567
+ return variable;
568
+ }
569
+
570
+ function copier(thing) {
571
+ switch(thing.type) {
572
+ case petikSymbol: return new Value(petikSymbol, thing.data);
573
+ case angkaSymbol: return new Value(angkaSymbol, thing.data);
574
+ case logisSymbol: return new Value(logisSymbol, thing.data);
575
+ case mesinSymbol: return new Value(mesinSymbol, thing.data);
576
+ case barisSymbol: return new Value(barisSymbol, thing.data.map((val)=>copier(val)));
577
+ case stipeSymbol: return new Value(null, null);
578
+ case modulSymbol: return new Value(null, null);
579
+ default:
580
+ if (thing.member && thing.member instanceof Environment) {
581
+ let keys = thing.member.memory.keys();
582
+ let valCopy = new Value(thing.type, null);
583
+ let newMember = new Environment();
584
+ for (let i of keys) {
585
+ if (i === "objek") continue;
586
+ newMember.define(i, copier(thing.member.get(i)));
587
+ }
588
+ newMember.define("objek", valCopy);
589
+ valCopy.member = newMember;
590
+
591
+ return valCopy;
592
+ }
593
+ return new Value(null, null);
594
+ }
595
+ }
596
+
597
+
598
+ const GLOBAL_ENV = (() => {
599
+ let env = new Environment();
600
+ env.define("petik", new PetikTipe());
601
+ env.define("angka", new AngkaTipe());
602
+ env.define("logis", new LogisTipe());
603
+ env.define("baris", new BarisTipe());
604
+ env.define("mesin", new MesinTipe());
605
+
606
+ env.define("jarak", makeBuiltInFunc([angkaSymbol, angkaSymbol], barisSymbol,
607
+ (v, [from, to])=>new Value(barisSymbol,
608
+ Array(Math.abs(Math.floor(to.data-from.data)))
609
+ .fill(0)
610
+ .map((val, idx)=>new Value(angkaSymbol, from.data+((to.data>from.data)?1:-1)*idx))))
611
+ );
612
+
613
+ env.define("nihil?", makeBuiltInFunc([null], logisSymbol, (v, [d]) => new Value(logisSymbol, d.data === null)));
614
+ env.define("ukuran", makeBuiltInFunc([null], angkaSymbol,(v, [d]) => d.type === barisSymbol || d.type === petikSymbol
615
+ ? new Value(angkaSymbol, d.data.length)
616
+ : v.error(`Ukuran hanya terdapat untuk tipe petik atau baris. Menemukan tipe ${d.type.description}.`)
617
+ ));
618
+
619
+ env.define("salin", makeBuiltInFunc([null], null, (v, [d]) => copier(d)));
620
+ env.define("tipe", makeBuiltInFunc([null], petikSymbol, (v, [d]) => (d.type?.description)
621
+ ? new Value(petikSymbol, d.type.description)
622
+ : new Value(petikSymbol, "datum")
623
+ ));
624
+ return env;
625
+ })();
626
+
627
+ let Henti$1 = class Henti {};
628
+ let Lewat$1 = class Lewat {};
629
+ let Hasil$1 = class Hasil {
630
+ constructor(value) {
631
+ this.value = value;
632
+ }
633
+ };
634
+
635
+ const location = {
636
+ GLOBAL: 1,
637
+ SLAGI: 2,
638
+ UNTUK: 3,
639
+ MESIN: 4,
640
+ };
641
+ const MAX_LOOP_ALLOWED = 1000000;
642
+ const MAX_STACK_SIZE = 500;
643
+
644
+ // Implements all Expressions and Statements Visitor
645
+ class Interpreter {
646
+ constructor() {
647
+ this.globalEnvironment = GLOBAL_ENV;
648
+ this.line = 0;
649
+ this.environment = new Environment(this.globalEnvironment);
650
+ this.tree = null;
651
+ this.state = location.GLOBAL;
652
+ this.stack = [];
653
+ this.output = [];
654
+ }
655
+
656
+ // EXPRESSION VISITORS
657
+
658
+ visitLiteralExpr(literalExpr) {
659
+ let lit = literalExpr.token;
660
+ this.line = lit.line;
661
+ switch(typeof lit.value) {
662
+ case "string":
663
+ return new Value(petikSymbol, lit.value);
664
+ case "number":
665
+ return new Value(angkaSymbol, lit.value);
666
+ case "boolean":
667
+ return new Value(logisSymbol, lit.value);
668
+ default:
669
+ return new Value(null, null);
670
+ }
671
+ }
672
+
673
+ visitArrayExpr(arrayExpr) {
674
+ let value = new Value(barisSymbol, []);
675
+
676
+ for (let expr of arrayExpr.contents) {
677
+ let v = this.validvalue(expr.accept(this));
678
+ // value.data.push(v);
679
+ let result = new Value(v.type, v.data);
680
+ result.member = v.member;
681
+ value.data.push(new Value(v.type, v.data));
682
+ }
683
+
684
+ return value;
685
+ }
686
+
687
+ visitIndexExpr(indexExpr) {
688
+ // a real TODO would've been to implement real iterables
689
+ let iterable = indexExpr.iterable.accept(this);
690
+ if (iterable.type === petikSymbol) {
691
+ iterable = new Value(barisSymbol, iterable.data.split("").map(str=>new Value(petikSymbol, str)));
692
+ }
693
+ if (iterable.type !== barisSymbol) {
694
+ this.error(`'${iterable.type.description}' bukan baris/petik, tidak bisa di-indeks`);
695
+ }
696
+
697
+ let index = indexExpr.index.accept(this);
698
+ if (index.type !== angkaSymbol) {
699
+ this.error(`Ekspresi di dalam indeks harus bertipe angka. Menemukan ${index.type.description}`);
700
+ }
701
+
702
+ if (index.data >= iterable.data.length) {
703
+ this.error(`Indeks tidak boleh lebih besar atau sama dengan ukuran baris: ${index.data} >= ${iterable.data.length}`);
704
+ }
705
+
706
+ while (index.data < 0) index.data += iterable.data.length;
707
+ return iterable.data[index.data];
708
+ }
709
+
710
+ visitLambdaExpr(lambdaExpr) {
711
+ // let type = lambdaExpr.returnValue.accept(this);
712
+ let params = lambdaExpr.params.map(val=>[val[0].accept(this), val[0].tetap, val[1].lexeme]);
713
+ if (params.length > 1
714
+ && params.some(param=>params.reduce((count, anotherParam)=>param[2]===anotherParam[2] ? count+1:count, 0) > 1 ? true : false)) {
715
+ this.error("Parameter mesin tidak boleh mempunyai nama yang sama.");
716
+ }
717
+ let retType = null;
718
+ if (lambdaExpr.returnType) {
719
+ retType = lambdaExpr.returnType.accept(this);
720
+ }
721
+ let lambda = new Callable(this.environment, lambdaExpr.block, params, retType);
722
+ return new Value(mesinSymbol, lambda);
723
+ }
724
+
725
+ visitCallExpr(callExpr) {
726
+ this.stack.push(this.line);
727
+ if (this.stack.length > MAX_STACK_SIZE)
728
+ this.error(`Rekursi melebihi batas: Lebih dari ${MAX_STACK_SIZE}`);
729
+ this.state = location.MESIN;
730
+ let callable = callExpr.callable.accept(this);
731
+
732
+ if (callable.type !== mesinSymbol && callable.type !== stipeSymbol) {
733
+ this.error(`Hanya bisa 'memanggil' mesin atau model, malah menemukan ${callable.type.description}. `);
734
+ }
735
+
736
+ if (!callable.data?.callFunc) {
737
+ this.error(`Mesin tidak terdefinisi, tidak bisa dipanggil.`);
738
+ }
739
+ this.line = this.stack[this.stack.length-1];
740
+ let result = callable.data.callFunc(this, callExpr.args.map(val=>this.validvalue(val.accept(this))));
741
+ this.stack.pop();
742
+ if (this.stack.length == 0) this.state = location.GLOBAL;
743
+ return result;
744
+ }
745
+
746
+ visitBinaryExpr(binaryExpr) {
747
+ let leftValue = this.validvalue(binaryExpr.left.accept(this));
748
+ let rightValue = this.validvalue(binaryExpr.right.accept(this));
749
+
750
+ this.line = binaryExpr.op.line;
751
+
752
+ let isNull = this.nullCheck(leftValue, rightValue);
753
+ if (isNull) {
754
+ this.error(`Tidak bisa mengoperasikan nilai Nihil.`);
755
+ }
756
+ this.typeCheck(leftValue, rightValue, `Pada operasi biner ${binaryExpr.op.lexeme}`);
757
+
758
+ let result = this.environment.get(leftValue.type.description) // get Model
759
+ .operate(this, binaryExpr.op.type, rightValue, leftValue); // dispatch the operation
760
+
761
+ return result;
762
+ }
763
+
764
+ visitUnaryExpr(unaryExpr) {
765
+ let rightValue = this.validvalue(unaryExpr.right.accept(this));
766
+
767
+ this.line = unaryExpr.op.line;
768
+
769
+ let isNull = this.nullCheck(rightValue);
770
+ if (isNull) {
771
+ this.error(`Tidak bisa mengoperasikan nilai Nihil.`);
772
+ }
773
+
774
+ let result = this.environment.get(rightValue.type.description) // get Model
775
+ .operate(this, unaryExpr.op.type, rightValue); // dispatch the operation
776
+
777
+ return result;
778
+ }
779
+
780
+ visitGroupingExpr(groupingExpr) {
781
+ return groupingExpr.expr.accept(this);
782
+ }
783
+
784
+ visitIdentifierExpr(identifierExpr) {
785
+ let value = this.environment.get(identifierExpr.token.lexeme);
786
+ this.line = identifierExpr.token.line;
787
+ if (!value) this.error(`'${identifierExpr.token.lexeme}' tidak dapat ditemukan.`);
788
+ return value;
789
+ }
790
+
791
+ visitMemberExpr(memberExpr) {
792
+ let main = memberExpr.main.accept(this);
793
+ let name = memberExpr.member.token.lexeme;
794
+ this.line = memberExpr.member.token.line;
795
+ if (!main.member) {
796
+ this.error(`Nilai tidak mempunyai atribut sama sekali.`);
797
+ } else if (!main.member.has(name)) {
798
+ this.error(`atribut .${name} tidak dapat ditemukan.`);
799
+ }
800
+ return main.member.get(name);
801
+ }
802
+
803
+ // STATEMENT VISITORS
804
+
805
+ // this is needed for interpreting generics TODO!
806
+ visitTypeStmt(typeStmt) {
807
+ if (typeStmt.type === null) return null;
808
+ let type = typeStmt.type.accept(this);
809
+ if (type.type !== stipeSymbol) this.error(`'${type.type.description}' bukan sebuah Model/Tipe Valid.`);
810
+ return type.symbol;
811
+ }
812
+
813
+ visitCetakStmt(cetakStmt) {
814
+ let result = cetakStmt.expr.accept(this);
815
+ const kePetik = (thing) => {
816
+ if (thing.type === logisSymbol) {
817
+ return thing.data ? "benar" : "salah";
818
+ } else if (thing.type === barisSymbol) {
819
+ return '[' + thing.data.reduce((str, val)=>str+", "+kePetik(val), "").slice(1) + ' ]';
820
+ } else if (thing.type === stipeSymbol) {
821
+ return `Model<${thing.symbol.description}>`;
822
+ } else if (thing.type === mesinSymbol) {
823
+ let underlying = thing.data.returnType?.description;
824
+ return `Mesin<${underlying? underlying : 'datum'}>`;
825
+ } else if (thing.type === angkaSymbol) {
826
+ return thing.data.toString();
827
+ } else if (thing.type === petikSymbol) {
828
+ return '"' + thing.data + '"';
829
+ } else {
830
+ if (!thing?.type) return `nihil`;
831
+ return `${thing.type.description}<>`;
832
+ }
833
+ };
834
+
835
+ this.output.push(kePetik(result));
836
+
837
+ }
838
+
839
+ visitKerjaStmt(kerjaStmt) {
840
+ kerjaStmt.expr.accept(this);
841
+ }
842
+
843
+ visitDatumStmt(datumStmt) {
844
+ let type = datumStmt.type.accept(this);
845
+ let name = this.validName(datumStmt.name.lexeme);
846
+ this.line = datumStmt.name.line;
847
+
848
+ let variable = new Variable(type, datumStmt.type.tetap);
849
+
850
+ let value = this.validvalue(datumStmt.expr.accept(this));
851
+
852
+ if (value.data === null) value.type = variable.type; // if nihil, ok
853
+
854
+ if (type === null) {
855
+ variable.isDatum = true;
856
+ variable.type = value.type;
857
+ } else this.typeCheck(variable, value, `Pada pembuatan variabel '${name}'`);
858
+
859
+ variable.data = value.data;
860
+ variable.member = value.member;
861
+
862
+ this.environment.define(name, variable);
863
+ }
864
+
865
+ visitRubahStmt(rubahStmt) {
866
+ let variable = rubahStmt.variable.accept(this); // is a reference to the variable data
867
+ if (variable.tetap) {
868
+ this.error(`Variabel tetap tidak dapat di-rubah.`);
869
+ }
870
+ let value = this.validvalue(rubahStmt.value.accept(this));
871
+ if (value.data === null) value.type = variable.type; // if nihil, ok
872
+
873
+ if (variable.isDatum)
874
+ variable.type = value.type;
875
+ else this.typeCheck(variable, value, `Pada perubahan variabel.`);
876
+
877
+ variable.data = value.data; // reference modifying (THANK GOD FOR THIS I LOVE YOU GARBAGE COLLECTOR)
878
+ variable.member = value.member;
879
+ }
880
+
881
+ visitBlockStmt(blockStmt) {
882
+ let blockEnv = new Environment(this.environment);
883
+ this.environment = blockEnv;
884
+ try {
885
+ for (let stmt of blockStmt.statements) {
886
+ stmt.accept(this);
887
+ }
888
+ } catch (err) {
889
+ this.environment = blockEnv.enclosing; // close the environment first
890
+ throw err;
891
+ }
892
+ this.environment = blockEnv.enclosing;
893
+ }
894
+
895
+ visitKalauStmt(kalauStmt) {
896
+ // condition may be null for 'namun', accept the thenBlock if it is
897
+ let condition = kalauStmt.condition?.accept(this);
898
+ if (condition === null || condition === undefined || condition.data || condition.member) {
899
+ kalauStmt.thenBlock.accept(this);
900
+ } else {
901
+ kalauStmt.elseKalau?.accept(this); // kalau may not have namun
902
+ }
903
+ }
904
+
905
+ visitHentiStmt() {
906
+ if (this.state !== location.GLOBAL)
907
+ throw new Henti$1(); // throws exception to escape from deep recursion
908
+ else this.error("Tidak ada pengulangan untuk dihentikan.");
909
+ }
910
+
911
+
912
+ visitLewatStmt() {
913
+ if (this.state !== location.GLOBAL)
914
+ throw new Lewat$1(); // throws exception to escape from deep recursion
915
+ else this.error("Tidak ada pengulangan untuk dilewatkan.");
916
+ }
917
+
918
+ visitHasilStmt(hasilStmt) {
919
+ if (this.stack.length == 0) {
920
+ this.error("Tidak bisa menghasilkan diluar blok mesin.");
921
+ }
922
+ throw new Hasil$1(hasilStmt.expr.accept(this));
923
+ }
924
+
925
+ visitSlagiStmt(slagiStmt) {
926
+ const checkTruthy = (v) => v.data || v.member;
927
+ let lastEnv = this.environment;
928
+ let howManyLoop = 0;
929
+ while (checkTruthy(slagiStmt.condition.accept(this))) {
930
+ howManyLoop++;
931
+ if (howManyLoop > MAX_LOOP_ALLOWED)
932
+ this.error(`Pengulangan melebihi batas: lebih dari ${MAX_LOOP_ALLOWED}.`);
933
+ this.environment = new Environment(lastEnv);
934
+ try {
935
+ for (let stmt of slagiStmt.block.statements) {
936
+ this.state = location.SLAGI;
937
+ stmt.accept(this);
938
+ this.state = location.SLAGI;
939
+ }
940
+ } catch (err) {
941
+ if (err instanceof Henti$1) {
942
+ break;
943
+ } else if (err instanceof Lewat$1) {
944
+ continue;
945
+ } else throw err;
946
+ }
947
+ }
948
+ this.environment = lastEnv;
949
+ if (this.stackNum !== 0) this.state = location.MESIN;
950
+ else this.state = location.GLOBAL;
951
+ }
952
+
953
+ visitUntukStmt(untukStmt) {
954
+ let name = this.validName(untukStmt.varName.lexeme, false);
955
+ let type = untukStmt.varType.accept(this);
956
+
957
+ let iter = untukStmt.iterable.accept(this);
958
+ if (iter.type === petikSymbol) {
959
+ iter = new Value(barisSymbol, iter.data.split("").map(str=>new Value(petikSymbol, str)));
960
+ }
961
+ if (iter.type !== barisSymbol) {
962
+ this.error(`Pernyataan 'untuk' harus mengiterasi sebuah baris atau petik, bukan ${iter.type.description}`);
963
+ }
964
+
965
+ for (let idx = 0; idx < iter.data.length; idx++) {
966
+ let i = iter.data[idx];
967
+ let val = new Variable(i.type, untukStmt.type?.tetap ? true : false, i.data);
968
+ if (type === null) {
969
+ val.isDatum = true;
970
+ } else if (i.type !== type) this.error(`Tipe data tidak sama pada indeks ke-${idx}: ${i.type.description} != ${type.description}.`);
971
+
972
+ let untukEnv = new Environment(this.environment);
973
+ untukEnv.define(name, val);
974
+ this.environment = untukEnv;
975
+ try {
976
+ // didn't 'accept' the block, just uses it directly
977
+ for (let stmt of untukStmt.block.statements) {
978
+ this.state = location.UNTUK;
979
+ stmt.accept(this);
980
+ this.state = location.UNTUK;
981
+ }
982
+ } catch (err) {
983
+ this.environment = untukEnv.enclosing;
984
+ if (err instanceof Lewat$1) {
985
+ continue;
986
+ } else if (err instanceof Henti$1) {
987
+ if (this.stackNum !== 0) this.state = location.MESIN;
988
+ else this.state = location.GLOBAL;
989
+ return;
990
+ } else {
991
+ throw err;
992
+ }
993
+ }
994
+ this.environment = untukEnv.enclosing;
995
+ }
996
+ if (this.stackNum !== 0) this.state = location.MESIN;
997
+ else this.state = location.GLOBAL;
998
+ }
999
+
1000
+ visitJenisStmt(jenisStmt) {
1001
+ let name = this.validName(jenisStmt.name.lexeme);
1002
+ this.line = jenisStmt.name.line;
1003
+ this.environment.define(name, new Jenis$1(name, jenisStmt.enums));
1004
+ }
1005
+
1006
+ visitSimplStmt(simpl) {
1007
+ for (let stmt of simpl.statements) {
1008
+ stmt.accept(this);
1009
+ }
1010
+ }
1011
+
1012
+ visitModelStmt(modelStmt) {
1013
+ let name = this.validName(modelStmt.name.lexeme);
1014
+ this.line = modelStmt.name.line;
1015
+ this.environment.define(name, new Model$1(name, modelStmt.contents));
1016
+ }
1017
+
1018
+ visitModulStmt(modulStmt) {
1019
+ let name = this.validName(modulStmt.name.lexeme, false);
1020
+ this.line = modulStmt.name.line;
1021
+ let variable = this.environment.get(name);
1022
+ if (variable) {
1023
+ if (variable.type !== stipeSymbol) {
1024
+ this.error(`Modul hanya bisa _ditambahkan_ pada tipe. '${name}' bukan merupakan tipe.`);
1025
+ }
1026
+ if (variable.member) {
1027
+ this.error(`Tipe '${name}' sudah memiliki modul sendiri, tidak bisa definisi ulang.`);
1028
+ }
1029
+ }
1030
+
1031
+ let lastEnv = this.environment;
1032
+ this.environment = new Environment(this.globalEnvironment);
1033
+ if (variable) this.environment.define(name, variable);
1034
+ for (let stmt of modulStmt.statements) {
1035
+ stmt.accept(this);
1036
+ }
1037
+
1038
+ [this.environment, lastEnv] = [lastEnv, this.environment];
1039
+
1040
+ if (variable) {
1041
+ variable.member = lastEnv;
1042
+ } else {
1043
+ let res = new Variable(modulSymbol, true, null);
1044
+ res.member = lastEnv;
1045
+ this.environment.define(name, res);
1046
+ }
1047
+ }
1048
+
1049
+ // UTILITIES
1050
+
1051
+ typeCheck(a, b, message) {
1052
+ if (a.type !== b.type) {
1053
+ this.error(`Tipe data tidak sama: ${a.type.description} != ${b.type.description}. ` + message);
1054
+ }
1055
+ }
1056
+
1057
+ nullCheck(...args) {
1058
+ for (let i of args) {
1059
+ if (i.data === null || i.type === null) return i;
1060
+ }
1061
+ return false;
1062
+ }
1063
+
1064
+ validvalue(v) {
1065
+ if (v.type !== stipeSymbol) return v;
1066
+ this.error("stipe tidak dapat menjadi nilai.");
1067
+ }
1068
+
1069
+ validName(n, checkExisted = true) {
1070
+ if (RESERVED_NAMES.some(v=>v===n)) {
1071
+ this.error(`Nama sistem (${n}) tidak boleh didefinisi ulang.`);
1072
+ } else if (checkExisted && this.environment.has(n)) {
1073
+ this.error(`Variabel dengan nama '${n}' sudah ada. Tidak bisa didefinisi ulang.`);
1074
+ }
1075
+ return n;
1076
+ }
1077
+
1078
+ error(message) {
1079
+ throw new SimplErrorEksekusi(`Error Eksekusi => ${message}`, this.line);
1080
+ }
1081
+
1082
+ interpret(tree) {
1083
+ this.line = 0;
1084
+ this.output = [];
1085
+ this.tree = tree;
1086
+ tree.accept(this);
1087
+ return this.output;
1088
+ }
1089
+ }
1090
+
1091
+ class Token {
1092
+ constructor(type, lexeme, value, line) {
1093
+ this.type = type;
1094
+ this.lexeme = lexeme;
1095
+ this.value = value;
1096
+ this.line = line;
1097
+ }
1098
+
1099
+ toString() {
1100
+ return `< [${this.line}] ${TOKEN_STRING[this.type]}, ${this.lexeme} >`;
1101
+ }
1102
+ }
1103
+
1104
+ class Lexer {
1105
+ constructor() {
1106
+ this.init();
1107
+ }
1108
+
1109
+ init() {
1110
+ this.text = null;
1111
+ this.charStart = 0;
1112
+ this.charIndex = 0;
1113
+ this.lineIndex = 1;
1114
+ this.tokens = [];
1115
+ this.errors = [];
1116
+ }
1117
+
1118
+ isAtEnd() {
1119
+ return this.charIndex >= this.text.length;
1120
+ }
1121
+
1122
+ advance() {
1123
+ if (this.isAtEnd()) return;
1124
+ if (this.see() === "\n") this.lineIndex++;
1125
+ this.charIndex++;
1126
+ }
1127
+
1128
+ see() {
1129
+ if (this.isAtEnd()) return "EOF"
1130
+ return this.text[this.charIndex];
1131
+ }
1132
+
1133
+ peek(num = 1) {
1134
+ return this.isAtEnd() ? null : this.text[this.charIndex+num];
1135
+ }
1136
+
1137
+ isAlpha(char) {
1138
+ return /^[a-zA-Z_]$/.test(char);
1139
+ }
1140
+
1141
+ isNumeric(char) {
1142
+ return /^[0-9]$/.test(char);
1143
+ }
1144
+
1145
+ isAlphaNumeric(char) {
1146
+ return this.isAlpha(char) || this.isNumeric(char) || char === "?";
1147
+ }
1148
+
1149
+ skipWhitespaces() {
1150
+ while (!this.isAtEnd() && /\s/.test(this.see())) {
1151
+ this.advance();
1152
+ this.charStart++;
1153
+ }
1154
+ }
1155
+
1156
+ parseLexeme() {
1157
+ return this.text.slice(this.charStart, this.charIndex);
1158
+ }
1159
+
1160
+ scanTokens(text) {
1161
+ this.init();
1162
+ this.text = text;
1163
+ let tokens = [];
1164
+
1165
+ while (!this.isAtEnd()) {
1166
+ let token = this.scan();
1167
+ this.charStart = this.charIndex;
1168
+ if (token) tokens.push(token);
1169
+ }
1170
+
1171
+ tokens.push(new Token(EOF, null, null, this.lineIndex));
1172
+
1173
+ this.tokens = tokens;
1174
+ return this.tokens;
1175
+ }
1176
+
1177
+ id() {
1178
+ while(!this.isAtEnd() && this.isAlphaNumeric(this.see())) this.advance();
1179
+
1180
+ let lexeme = this.parseLexeme();
1181
+
1182
+ let reservedIndex = RESERVED_KEYWORDS.findIndex((val)=>val===lexeme);
1183
+ if (reservedIndex != -1) {
1184
+ return new Token(reservedIndex, lexeme, null, this.lineIndex);
1185
+ }
1186
+
1187
+ let isLogis = ["benar", "salah"].some((val)=>val===lexeme);
1188
+ if (isLogis) {
1189
+ return new Token(LITERAL, lexeme, "benar" === lexeme ? true : false, this.lineIndex);
1190
+ }
1191
+
1192
+ if (lexeme === "nihil") {
1193
+ return new Token(LITERAL, lexeme, null, this.lineIndex);
1194
+ }
1195
+
1196
+ return new Token(
1197
+ ID,
1198
+ lexeme,
1199
+ null,
1200
+ this.lineIndex
1201
+ )
1202
+ }
1203
+
1204
+ number() {
1205
+ let isFloat = false;
1206
+ while(!this.isAtEnd() && this.isNumeric(this.see())) {
1207
+ this.advance();
1208
+ if (this.see() === '.') {
1209
+ if (isFloat) break;
1210
+ isFloat = true;
1211
+ this.advance();
1212
+ }
1213
+ }
1214
+ let value = parseFloat(isFloat ? this.parseLexeme() : this.parseLexeme() + ".");
1215
+ return new Token(LITERAL, this.parseLexeme(), value, this.lineIndex);
1216
+ }
1217
+
1218
+ string() {
1219
+ this.advance();
1220
+ while(!this.isAtEnd() && this.see() !== '"') this.advance();
1221
+ if (this.isAtEnd()) this.error("Petik tidak tertutup.");
1222
+ this.advance();
1223
+ let lexeme = this.parseLexeme();
1224
+ return new Token(LITERAL, lexeme, lexeme.slice(1, lexeme.length-1), this.lineIndex);
1225
+ }
1226
+
1227
+ comment() {
1228
+ while(!this.isAtEnd() && this.see() !== '\n') {
1229
+ this.advance();
1230
+ this.charStart++;
1231
+ }
1232
+ this.advance();
1233
+ this.charStart++;
1234
+ }
1235
+
1236
+ makeToken(type) {
1237
+ this.advance();
1238
+ return new Token(type, this.parseLexeme(), null, this.lineIndex);
1239
+ }
1240
+
1241
+ scan() {
1242
+ this.skipWhitespaces();
1243
+ if(this.see() === '#') {
1244
+ this.comment();
1245
+ this.skipWhitespaces();
1246
+ }
1247
+
1248
+
1249
+ if(this.isAlpha(this.see())) return this.id();
1250
+ if(this.isNumeric(this.see())) return this.number();
1251
+
1252
+ switch(this.see()) {
1253
+ case "+": return this.makeToken(PLUS);
1254
+ case "-": return this.makeToken(MINUS);
1255
+ case "/": return this.makeToken(SLASH);
1256
+ case "*": return this.makeToken(STAR);
1257
+ case "(": return this.makeToken(LPAREN);
1258
+ case ")": return this.makeToken(RPAREN);
1259
+ case ".": return this.makeToken(DOT);
1260
+ case ",": return this.makeToken(COMMA);
1261
+ case "{": return this.makeToken(LCURLY);
1262
+ case "}": return this.makeToken(RCURLY);
1263
+ case "[": return this.makeToken(LSQUARE);
1264
+ case "]": return this.makeToken(RSQUARE);
1265
+ case "|": return this.makeToken(PIPE);
1266
+ case "&": return this.makeToken(AMPERSAND);
1267
+ case "%": return this.makeToken(MODULUS);
1268
+ case "!":
1269
+ if (this.peek() === "=") {
1270
+ this.advance();
1271
+ return this.makeToken(BANG_EQUAL);
1272
+ }
1273
+ return this.makeToken(BANG);
1274
+
1275
+ case ">":
1276
+ if (this.peek() === "=") {
1277
+ this.advance();
1278
+ return this.makeToken(GREATER_EQUAL)
1279
+ }
1280
+ return this.makeToken(GREATER);
1281
+ case "<":
1282
+ if (this.peek() === "=") {
1283
+ this.advance();
1284
+ return this.makeToken(LESS_EQUAL)
1285
+ }
1286
+ return this.makeToken(LESS);
1287
+
1288
+ case "=":
1289
+ if (this.peek() === "=" && this.peek(2) === ">") {
1290
+ return this.makeToken(EQUAL);
1291
+ } else if (this.peek() === "=") {
1292
+ this.advance();
1293
+ return this.makeToken(EQUAL_EQUAL);
1294
+ } else if (this.peek() === ">") {
1295
+ this.advance();
1296
+ return this.makeToken(ARROW);
1297
+ }
1298
+ return this.makeToken(EQUAL);
1299
+
1300
+ case '"': return this.string();
1301
+ }
1302
+ if (this.isAtEnd()) return;
1303
+
1304
+ this.error(`karakter tidak valid: Menemukan '${this.see()}'.`);
1305
+ }
1306
+
1307
+ error(errmsg) {
1308
+ throw new SimplErrorTulisan(`Error Tulisan => ` + errmsg, this.see().line);
1309
+ }
1310
+
1311
+ debugPrintTokens(tokens) {
1312
+ for (let tok of tokens) {
1313
+ console.log(tok.toString());
1314
+ }
1315
+ }
1316
+ }
1317
+
1318
+ class ExprBase {
1319
+ accept(visitor) {
1320
+ return this.visit(visitor);
1321
+ }
1322
+ }
1323
+
1324
+ class Binary extends ExprBase {
1325
+ // Expr.ExprBase left, Token op, Expr.ExprBase right
1326
+ constructor (left, op, right) {
1327
+ super();
1328
+ this.left = left;
1329
+ this.op = op;
1330
+ this.right = right;
1331
+ }
1332
+
1333
+ visit(visitor) {
1334
+ return visitor.visitBinaryExpr(this);
1335
+ }
1336
+ }
1337
+
1338
+ class Unary extends ExprBase {
1339
+ // Token op, Expr.ExprBase right
1340
+ constructor (op, right) {
1341
+ super();
1342
+ this.op = op;
1343
+ this.right = right;
1344
+ }
1345
+
1346
+ visit(visitor) {
1347
+ return visitor.visitUnaryExpr(this);
1348
+ }
1349
+ }
1350
+
1351
+ class Literal extends ExprBase {
1352
+ // Token token
1353
+ constructor (token) {
1354
+ super();
1355
+ this.token = token;
1356
+ }
1357
+
1358
+ visit(visitor) {
1359
+ return visitor.visitLiteralExpr(this);
1360
+ }
1361
+ }
1362
+
1363
+ class Grouping extends ExprBase {
1364
+ // Expr.ExprBase expr
1365
+ constructor (expr) {
1366
+ super();
1367
+ this.expr = expr;
1368
+ }
1369
+
1370
+ visit(visitor) {
1371
+ return visitor.visitGroupingExpr(this);
1372
+ }
1373
+ }
1374
+
1375
+ class Member extends ExprBase {
1376
+ // Expr.ExprBase main, Expr.Identifier member
1377
+ constructor (main, member) {
1378
+ super();
1379
+ this.main = main;
1380
+ this.member = member;
1381
+ }
1382
+
1383
+ visit(visitor) {
1384
+ return visitor.visitMemberExpr(this);
1385
+ }
1386
+ }
1387
+
1388
+ class Identifier extends ExprBase {
1389
+ // Token token
1390
+ constructor (token) {
1391
+ super();
1392
+ this.token = token;
1393
+ }
1394
+
1395
+ visit(visitor) {
1396
+ return visitor.visitIdentifierExpr(this);
1397
+ }
1398
+ }
1399
+
1400
+ class Lambda extends ExprBase {
1401
+ // Array<Stmt.Types-Token> params, Stmt.Type returnType, Stmt.Block block
1402
+ constructor (params, returnType, block) {
1403
+ super();
1404
+ this.params = params;
1405
+ this.returnType = returnType;
1406
+ this.block = block;
1407
+ }
1408
+
1409
+ visit(visitor) {
1410
+ return visitor.visitLambdaExpr(this);
1411
+ }
1412
+ }
1413
+
1414
+ class Call extends ExprBase {
1415
+ // Expr.ExprBase callable, Expr.ExprBase args
1416
+ constructor (callable, args) {
1417
+ super();
1418
+ this.callable = callable;
1419
+ this.args = args;
1420
+ }
1421
+
1422
+ visit(visitor) {
1423
+ return visitor.visitCallExpr(this);
1424
+ }
1425
+ }
1426
+
1427
+ let Array$1 = class Array extends ExprBase {
1428
+ // Array<Expr.ExprBase> contents
1429
+ constructor (contents) {
1430
+ super();
1431
+ this.contents = contents;
1432
+ }
1433
+
1434
+ visit(visitor) {
1435
+ return visitor.visitArrayExpr(this);
1436
+ }
1437
+ };
1438
+
1439
+ class Index extends ExprBase {
1440
+ // Expr.ExprBase iterable, Expr.ExprBase index
1441
+ constructor (iterable, index) {
1442
+ super();
1443
+ this.iterable = iterable;
1444
+ this.index = index;
1445
+ }
1446
+
1447
+ visit(visitor) {
1448
+ return visitor.visitIndexExpr(this);
1449
+ }
1450
+ }
1451
+
1452
+ class StmtBase {
1453
+ accept(visitor) {
1454
+ return this.visit(visitor);
1455
+ }
1456
+ }
1457
+
1458
+ class Cetak extends StmtBase {
1459
+ // Expr.ExprBase expr
1460
+ constructor (expr) {
1461
+ super();
1462
+ this.expr = expr;
1463
+ }
1464
+
1465
+ visit(visitor) {
1466
+ return visitor.visitCetakStmt(this);
1467
+ }
1468
+ }
1469
+
1470
+ class Datum extends StmtBase {
1471
+ // Stmt.Type type, Token name, Expr.ExprBase expr
1472
+ constructor (type, name, expr) {
1473
+ super();
1474
+ this.type = type;
1475
+ this.name = name;
1476
+ this.expr = expr;
1477
+ }
1478
+
1479
+ visit(visitor) {
1480
+ return visitor.visitDatumStmt(this);
1481
+ }
1482
+ }
1483
+
1484
+ class Type extends StmtBase {
1485
+ // Expr.ExprBase type, bool tetap, Array<Stmt.Type> contents
1486
+ constructor (type, tetap, contents) {
1487
+ super();
1488
+ this.type = type;
1489
+ this.tetap = tetap;
1490
+ this.contents = contents;
1491
+ }
1492
+
1493
+ visit(visitor) {
1494
+ return visitor.visitTypeStmt(this);
1495
+ }
1496
+ }
1497
+
1498
+ class Kerja extends StmtBase {
1499
+ // Expr.ExprBase expr
1500
+ constructor (expr) {
1501
+ super();
1502
+ this.expr = expr;
1503
+ }
1504
+
1505
+ visit(visitor) {
1506
+ return visitor.visitKerjaStmt(this);
1507
+ }
1508
+ }
1509
+
1510
+ class Block extends StmtBase {
1511
+ // Array<Stmt.StmtBase> statements
1512
+ constructor (statements) {
1513
+ super();
1514
+ this.statements = statements;
1515
+ }
1516
+
1517
+ visit(visitor) {
1518
+ return visitor.visitBlockStmt(this);
1519
+ }
1520
+ }
1521
+
1522
+ class Kalau extends StmtBase {
1523
+ // Expr.ExprBase condition, Stmt.Block thenBlock, Stmt.Kalau elseKalau
1524
+ constructor (condition, thenBlock, elseKalau) {
1525
+ super();
1526
+ this.condition = condition;
1527
+ this.thenBlock = thenBlock;
1528
+ this.elseKalau = elseKalau;
1529
+ }
1530
+
1531
+ visit(visitor) {
1532
+ return visitor.visitKalauStmt(this);
1533
+ }
1534
+ }
1535
+
1536
+ class Untuk extends StmtBase {
1537
+ // Stmt.Type varType, Token varName, Expr.Base iterable, Stmt.Block block
1538
+ constructor (varType, varName, iterable, block) {
1539
+ super();
1540
+ this.varType = varType;
1541
+ this.varName = varName;
1542
+ this.iterable = iterable;
1543
+ this.block = block;
1544
+ }
1545
+
1546
+ visit(visitor) {
1547
+ return visitor.visitUntukStmt(this);
1548
+ }
1549
+ }
1550
+
1551
+ class Slagi extends StmtBase {
1552
+ // Expr.ExprBase condition, Stmt.Block block
1553
+ constructor (condition, block) {
1554
+ super();
1555
+ this.condition = condition;
1556
+ this.block = block;
1557
+ }
1558
+
1559
+ visit(visitor) {
1560
+ return visitor.visitSlagiStmt(this);
1561
+ }
1562
+ }
1563
+
1564
+ class Henti extends StmtBase {
1565
+ constructor () {
1566
+ super();
1567
+ }
1568
+
1569
+ visit(visitor) {
1570
+ return visitor.visitHentiStmt(this);
1571
+ }
1572
+ }
1573
+
1574
+ class Lewat extends StmtBase {
1575
+ constructor () {
1576
+ super();
1577
+ }
1578
+
1579
+ visit(visitor) {
1580
+ return visitor.visitLewatStmt(this);
1581
+ }
1582
+ }
1583
+
1584
+ class Rubah extends StmtBase {
1585
+ // Expr.ExprBase variable, Expr.ExprBase value
1586
+ constructor (variable, value) {
1587
+ super();
1588
+ this.variable = variable;
1589
+ this.value = value;
1590
+ }
1591
+
1592
+ visit(visitor) {
1593
+ return visitor.visitRubahStmt(this);
1594
+ }
1595
+ }
1596
+
1597
+ class Hasil extends StmtBase {
1598
+ // Expr.ExprBase expr
1599
+ constructor (expr) {
1600
+ super();
1601
+ this.expr = expr;
1602
+ }
1603
+
1604
+ visit(visitor) {
1605
+ return visitor.visitHasilStmt(this);
1606
+ }
1607
+ }
1608
+
1609
+ class Jenis extends StmtBase {
1610
+ // Token name, Array<Token> enums
1611
+ constructor (name, enums) {
1612
+ super();
1613
+ this.name = name;
1614
+ this.enums = enums;
1615
+ }
1616
+
1617
+ visit(visitor) {
1618
+ return visitor.visitJenisStmt(this);
1619
+ }
1620
+ }
1621
+
1622
+ class Model extends StmtBase {
1623
+ // Token name, Array<Stmt.Type-Token> contents
1624
+ constructor (name, contents) {
1625
+ super();
1626
+ this.name = name;
1627
+ this.contents = contents;
1628
+ }
1629
+
1630
+ visit(visitor) {
1631
+ return visitor.visitModelStmt(this);
1632
+ }
1633
+ }
1634
+
1635
+ let Simpl$1 = class Simpl extends StmtBase {
1636
+ // Array<Stmt.StmtBase> statements
1637
+ constructor (statements) {
1638
+ super();
1639
+ this.statements = statements;
1640
+ }
1641
+
1642
+ visit(visitor) {
1643
+ return visitor.visitSimplStmt(this);
1644
+ }
1645
+ };
1646
+
1647
+ class Modul extends StmtBase {
1648
+ // Token name, Array<Stmt.StmtBase> statements
1649
+ constructor (name, statements) {
1650
+ super();
1651
+ this.name = name;
1652
+ this.statements = statements;
1653
+ }
1654
+
1655
+ visit(visitor) {
1656
+ return visitor.visitModulStmt(this);
1657
+ }
1658
+ }
1659
+
1660
+ class Parser {
1661
+ constructor() {
1662
+ this.init();
1663
+ }
1664
+
1665
+ init() {
1666
+ this.tokens = [];
1667
+ this.tokenIndex = 0;
1668
+ this.tree = null;
1669
+ }
1670
+
1671
+ see() {
1672
+ return this.tokens[this.tokenIndex];
1673
+ }
1674
+
1675
+ peek() {
1676
+ return this.tokenIndex+1 >= this.tokens.length ? null : this.tokens[this.tokenIndex+1];
1677
+ }
1678
+
1679
+ check(type) {
1680
+ return this.tokens[this.tokenIndex].type === type;
1681
+ }
1682
+
1683
+ isAtEnd() {
1684
+ return this.tokenIndex >= this.tokens.length;
1685
+ }
1686
+
1687
+ match(...args) {
1688
+ if(this.isAtEnd()) return false;
1689
+
1690
+ for (let i of args) {
1691
+ if (this.check(i)) {
1692
+ this.tokenIndex++;
1693
+ return true;
1694
+ }
1695
+ }
1696
+ return false;
1697
+ }
1698
+
1699
+ eat(expected, errMsg) {
1700
+ if (this.match(expected)) return
1701
+ this.error(errMsg);
1702
+ }
1703
+
1704
+ previous() {
1705
+ return this.tokens[this.tokenIndex - 1];
1706
+ }
1707
+
1708
+ blockStmt() {
1709
+ let statements= [];
1710
+ while(!this.match(RCURLY)) {
1711
+ statements.push(this.statement());
1712
+ }
1713
+ return new Block(statements);
1714
+ }
1715
+
1716
+ functionCallExpr(callable) {
1717
+ let args = [];
1718
+
1719
+ if (this.check(RPAREN)) ; else {
1720
+ do {
1721
+ let expr = this.expression();
1722
+ args.push(expr);
1723
+ } while(this.match(COMMA))
1724
+ }
1725
+
1726
+ this.eat(RPAREN, "Mengharapkan ')' setelah penggunaan mesin.");
1727
+
1728
+ return new Call(callable, args);
1729
+ }
1730
+
1731
+ lambda() {
1732
+ let params = [];
1733
+ this.eat(LPAREN, "Mengharapkan '(' setelah '=>' untuk deklarasi Lamda.");
1734
+
1735
+ if (this.match(ID, DATUM)) {
1736
+ let type = this.typeStmt();
1737
+ this.eat(ID, "Mengharapkan Nama parameter setelah deklarasi Tipe parameter dalam Lamda.");
1738
+ let name = this.previous();
1739
+ params.push([type, name]);
1740
+
1741
+ while(this.match(COMMA)) {
1742
+ if(!this.match(ID, DATUM))
1743
+ this.error("Mengharapkan Tipe parameter setelah koma dalam Lamda.");
1744
+ let type = this.typeStmt();
1745
+ this.eat(ID, "Mengharapkan Nama parameter setelah deklarasi Tipe parameter dalam Lamda.");
1746
+ let name = this.previous();
1747
+ params.push([type, name]);
1748
+ }
1749
+ }
1750
+ // deepPrint(params);
1751
+ this.eat(RPAREN, "Mengharapkan ')' setelah deklarasi parameter Lamda.");
1752
+ let returnType = null;
1753
+ if (this.match(ID, DATUM)) {
1754
+ returnType = this.typeStmt();
1755
+ } if (this.match(LITERAL)) {
1756
+ if (this.previous().value !== null) {
1757
+ this.error("Mengharapkan Tipe Hasil yang valid.");
1758
+ }
1759
+ returnType = null;
1760
+ }
1761
+ this.eat(LCURLY, "Mengharapkan Blok { } untuk Lamda.");
1762
+ let block = this.blockStmt();
1763
+
1764
+ return new Lambda(params, returnType, block);
1765
+ }
1766
+
1767
+ arrayExpr() {
1768
+ if (this.match(RSQUARE)) {
1769
+ return new Array$1([]);
1770
+ }
1771
+
1772
+ let contents = [];
1773
+ do {
1774
+ if (this.check(RSQUARE)) break;
1775
+ let expr = this.expression();
1776
+ contents.push(expr);
1777
+ } while (this.match(COMMA))
1778
+
1779
+ this.eat(RSQUARE, "Mengharapkan ']' untuk menutup 'baris'.");
1780
+
1781
+ return new Array$1(contents);
1782
+ }
1783
+
1784
+ arrayIndex(iterable) {
1785
+ let index = this.expression();
1786
+
1787
+ this.eat(RSQUARE, "Mengharapkan ']' untuk menutup indeks." );
1788
+
1789
+ return new Index(iterable, index);
1790
+ }
1791
+
1792
+ primary() {
1793
+ if (this.match(LPAREN)) {
1794
+ let expr = this.expression();
1795
+ this.eat(RPAREN, "Mengharapkan ')' untuk mengakhiri ekspresi kurung");
1796
+ return new Grouping(expr);
1797
+ } else if (this.match(LITERAL)){
1798
+ return new Literal(this.previous());
1799
+ } else if (this.match(ID)) {
1800
+ return this.identifier();
1801
+ } else if (this.match(ARROW)) {
1802
+ return this.lambda();
1803
+ } else if (this.match(LSQUARE)) {
1804
+ return this.arrayExpr();
1805
+ }
1806
+
1807
+ this.error("Mengharapkan Ekspresi valid.");
1808
+ }
1809
+
1810
+ valuable() {
1811
+ let result = this.primary();
1812
+
1813
+ while(true) {
1814
+ if (this.match(LPAREN)) {
1815
+ result = this.functionCallExpr(result);
1816
+ } else if (this.match(LSQUARE)) {
1817
+ result = this.arrayIndex(result);
1818
+ } else if (this.match(DOT)) {
1819
+ result = this.member(result);
1820
+ } else {
1821
+ break;
1822
+ }
1823
+ }
1824
+
1825
+ return result;
1826
+ }
1827
+
1828
+ unary() {
1829
+ if (this.match(PLUS, MINUS, BANG)) {
1830
+ let op = this.previous();
1831
+ let right = this.unary();
1832
+ return new Unary(op, right);
1833
+ } else {
1834
+ return this.valuable();
1835
+ }
1836
+ }
1837
+
1838
+ identifier() {
1839
+ return new Identifier(this.previous());
1840
+ }
1841
+
1842
+ member(parent) {
1843
+ this.eat(ID, "Mengharapkan Nama member setelah '.'.");
1844
+ let id = this.identifier();
1845
+ return new Member(parent, id);
1846
+ }
1847
+
1848
+ factor() {
1849
+ let expr = this.unary();
1850
+
1851
+ while(this.match(STAR, SLASH)) {
1852
+ let op = this.previous();
1853
+ let right = this.unary();
1854
+ expr = new Binary(expr, op, right);
1855
+ }
1856
+
1857
+ return expr;
1858
+ }
1859
+
1860
+ term() {
1861
+ let expr = this.factor();
1862
+
1863
+ while (this.match(MODULUS, PLUS, MINUS)) {
1864
+ let op = this.previous();
1865
+ let right = this.factor();
1866
+ expr = new Binary(expr, op, right);
1867
+ }
1868
+
1869
+ return expr;
1870
+ }
1871
+
1872
+ equality() {
1873
+ let expr = this.term();
1874
+
1875
+ while(this.match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL, EQUAL_EQUAL, BANG_EQUAL)) {
1876
+ let op = this.previous();
1877
+ let right = this.term();
1878
+ expr = new Binary(expr, op, right);
1879
+ }
1880
+
1881
+ return expr;
1882
+ }
1883
+
1884
+ andTerm() {
1885
+ let expr = this.equality();
1886
+
1887
+ while(this.match(AMPERSAND)) {
1888
+ let op = this.previous();
1889
+ let right = this.equality();
1890
+ expr = new Binary(expr, op, right);
1891
+ }
1892
+
1893
+ return expr;
1894
+ }
1895
+
1896
+ orTerm() {
1897
+ let expr = this.andTerm();
1898
+
1899
+ while(this.match(PIPE)) {
1900
+ let op = this.previous();
1901
+ let right = this.equality();
1902
+ expr = new Binary(expr, op, right);
1903
+ }
1904
+
1905
+ return expr;
1906
+ }
1907
+
1908
+ expression() {
1909
+ return this.orTerm();
1910
+ }
1911
+
1912
+ cetakStmt() {
1913
+ let expr = this.expression();
1914
+ return new Cetak(expr);
1915
+ }
1916
+
1917
+ typeStmt() {
1918
+ if (this.previous().type === DATUM) {
1919
+ let tetap = false;
1920
+ if (this.match(TETAP)) {
1921
+ tetap = true;
1922
+ }
1923
+ return new Type(null, tetap, null);
1924
+ }
1925
+
1926
+ let type = this.identifier();
1927
+ // while (this.match(TokenType.DOT)) {
1928
+ // type = this.member(type);
1929
+ // }
1930
+
1931
+ let contents = null;
1932
+ // if (this.match(TokenType.LESS)) {
1933
+ // this.eat(TokenType.ID, "Mengharapkan Tipe setelah '<' di dalam < spesifikasi Tipe >.");
1934
+ // let innerType = this.typeStmt();
1935
+ // contents.push(innerType);
1936
+
1937
+ // // while(this.match(TokenType.COMMA)) {
1938
+ // // this.eat(TokenType.ID, "Mengharapkan Tipe setelah ',' di dalam < spesifikasi Tipe >.");
1939
+ // // innerType = this.typeStmt();
1940
+ // // contents.push(innerType);
1941
+ // // }
1942
+
1943
+ // this.eat(TokenType.GREATER, "Mengharapkan '>' setelah spesifikasi Tipe.");
1944
+ // }
1945
+
1946
+ let tetap = false;
1947
+ if (this.match(TETAP)) {
1948
+ tetap = true;
1949
+ }
1950
+ return new Type(type, tetap, contents);
1951
+ }
1952
+
1953
+ datumStmt() {
1954
+ let type = this.typeStmt();
1955
+ this.eat(ID, "Mengharapkan Nama setelah Tipe pada deklarasi variabel.");
1956
+ let id = this.previous();
1957
+ this.eat(EQUAL, "Mengharapkan '=' setelah Nama variabel.");
1958
+ let expr = this.expression();
1959
+ return new Datum(type, id, expr);
1960
+ }
1961
+
1962
+ kalauStmt() {
1963
+ if (this.check(LCURLY)) {
1964
+ this.error("Mengharapkan ekspresi setelah 'kalau'.");
1965
+ }
1966
+ let condition = this.expression();
1967
+ this.eat(LCURLY, "Mengharapkan blok { } setelah kondisi untuk pernyataan 'kalau'.");
1968
+ let block = this.blockStmt();
1969
+ let elseKalau = null;
1970
+
1971
+ if (this.match(NAMUN)) {
1972
+ if (this.match(KALAU)) {
1973
+ elseKalau = this.kalauStmt();
1974
+ } else if (this.match(LCURLY)) {
1975
+ let elseCond = null;
1976
+ let elseBlock = this.blockStmt();
1977
+ elseKalau = new Kalau(elseCond, elseBlock);
1978
+ } else {
1979
+ this.error("Mengharapkan sebuah 'kalau' atau blok { } setelah 'namun'.");
1980
+ }
1981
+ }
1982
+
1983
+ return new Kalau(condition, block, elseKalau);
1984
+
1985
+ }
1986
+
1987
+ untukStmt() {
1988
+ if(!this.match(ID, DATUM))
1989
+ this.error("Mengharapkan Tipe variabel untuk diinisialisasi setelah 'untuk'.");
1990
+ let varType = this.typeStmt();
1991
+ this.eat(ID, "Mengharapkan Nama variabel setelah Tipe dalam pernyataan 'untuk'.");
1992
+ let varName = this.previous();
1993
+ this.eat(DALAM, "Mengharapkan 'dalam' setelah deklarasi variabel pada pernyataan 'untuk'.");
1994
+
1995
+ let iterable = this.expression();
1996
+
1997
+ this.eat(LCURLY, "Mengharapkan Blok { } setelah kondisi pada pernyataan 'untuk'.");
1998
+ let block = this.blockStmt();
1999
+
2000
+ return new Untuk(varType, varName, iterable, block);
2001
+ }
2002
+
2003
+ slagiStmt() {
2004
+ let condition = this.expression();
2005
+
2006
+ this.eat(LCURLY, "Mengharapkan Blok { } setelah kondisi pada pernyataan 'slagi'.");
2007
+ let block = this.blockStmt();
2008
+
2009
+ return new Slagi(condition, block);
2010
+ }
2011
+
2012
+ rubahStmt() {
2013
+ let id = this.valuable();
2014
+ this.eat(EQUAL, "Mengharapkan '=' setelah variable yang ingin di-'rubah'.");
2015
+ let expr = this.expression();
2016
+ return new Rubah(id, expr);
2017
+ }
2018
+
2019
+ jenisStmt() {
2020
+ this.eat(ID, "Mengharapkan Nama Jenis setelah 'jenis'.");
2021
+ let id = this.previous();
2022
+ this.eat(LPAREN, "Mengharapkan '(' setelah deklarasi Nama Jenis.");
2023
+ if (this.check(RPAREN)) {
2024
+ this.error("Isian Jenis tidak boleh kosong.", false);
2025
+ }
2026
+ let enums = [];
2027
+ do {
2028
+ this.eat(ID, "Mengharapkan macam jenis dalam 'jenis'.");
2029
+ let enumb = this.previous();
2030
+ enums.push(enumb);
2031
+ } while(this.match(COMMA))
2032
+
2033
+ this.eat(RPAREN, "Mengharapkan ')' untuk mengakhiri deklarasi 'jenis'.");
2034
+
2035
+ return new Jenis(id, enums);
2036
+ }
2037
+
2038
+ modelStmt() {
2039
+ this.eat(ID, "Mengharapkan Nama Model setelah 'model'.");
2040
+ let id = this.previous();
2041
+
2042
+ this.eat(LPAREN, "Mengharapkan '(' setelah Nama Model dalam 'model'.");
2043
+ if (this.check(RPAREN)) {
2044
+ this.error("Model tidak boleh tanpa isian.", false);
2045
+ }
2046
+
2047
+ let contents = [];
2048
+ do {
2049
+ if(!this.match(ID, DATUM))
2050
+ this.error("Mengharapkan Tipe member dalam deklarasi 'Model'.");
2051
+ let type = this.typeStmt();
2052
+ this.eat(ID, "Mengharapkan Nama member dalam deklarasi 'model'.");
2053
+ let memberName = this.previous();
2054
+
2055
+ contents.push([type, memberName]);
2056
+ } while(this.match(COMMA))
2057
+
2058
+ this.eat(RPAREN, "Mengharapkan ')' untuk mengakhiri deklarasi 'model'.");
2059
+
2060
+ return new Model(id, contents);
2061
+ }
2062
+
2063
+ modulStmt() {
2064
+ this.eat(ID, "Mengharapkan Nama modul setelah deklarasi.");
2065
+ let token = this.previous();
2066
+ this.eat(LCURLY, "Mengharapkan '{' setelah Nama modul.");
2067
+ let statements = [];
2068
+ while(!this.match(RCURLY)) {
2069
+ statements.push(this.statement());
2070
+ }
2071
+ return new Modul(token, statements);
2072
+ }
2073
+
2074
+ statement() {
2075
+ if(this.match(CETAK)) {
2076
+ return this.cetakStmt();
2077
+ } else if (this.match(ID, DATUM)) {
2078
+ return this.datumStmt();
2079
+ } else if (this.match(KERJA)) {
2080
+ return new Kerja(this.expression());
2081
+ } else if (this.match(KALAU)) {
2082
+ return this.kalauStmt();
2083
+ } else if (this.match(LCURLY)) {
2084
+ return this.blockStmt();
2085
+ } else if (this.match(UNTUK)){
2086
+ return this.untukStmt();
2087
+ } else if (this.match(SLAGI)) {
2088
+ return this.slagiStmt();
2089
+ } else if (this.match(HENTI)) {
2090
+ return new Henti();
2091
+ } else if (this.match(LEWAT)) {
2092
+ return new Lewat();
2093
+ } else if (this.match(RUBAH)) {
2094
+ return this.rubahStmt();
2095
+ } else if (this.match(HASIL)) {
2096
+ return new Hasil(this.expression());
2097
+ } else if (this.match(JENIS)) {
2098
+ return this.jenisStmt();
2099
+ } else if (this.match(MODEL)) {
2100
+ return this.modelStmt();
2101
+ } else if (this.match(MODUL)) {
2102
+ return this.modulStmt();
2103
+ } else {
2104
+ this.error(`Pernyataan tidak bisa diawali '${this.see().lexeme}'.`, false);
2105
+ }
2106
+ }
2107
+
2108
+ error(errmsg, found = true) {
2109
+ throw new SimplErrorStruktur(`Error Struktur => ` + errmsg + ((found) ? ` Menemukan '${this.see().lexeme }'.` : ""), this.see().line);
2110
+ }
2111
+
2112
+ parse(tokens) {
2113
+ this.init();
2114
+ this.tokens = tokens;
2115
+ let treeList = [];
2116
+ while(!this.match(EOF)) {
2117
+ treeList.push(this.statement());
2118
+ }
2119
+ this.tree = new Simpl$1(treeList);
2120
+ return this.tree;
2121
+ }
2122
+ }
2123
+
2124
+ // Simpl: Indonesian Mini Programming Language !!
2125
+
2126
+ class Simpl {
2127
+ constructor() {
2128
+ this.lexer = new Lexer();
2129
+ this.parser = new Parser();
2130
+ this.interpreter = new Interpreter();
2131
+ }
2132
+
2133
+ runCode(text) {
2134
+ // console.log(text.split("\n").reduce((codeStr, line, idx) => codeStr + `${idx + 1}.\t${line}\n`, ''));
2135
+ const textLines = text.split("\n");
2136
+ try {
2137
+ let tokens = this.lexer.scanTokens(text);
2138
+ let pohon = this.parser.parse(tokens);
2139
+ let output = this.interpreter.interpret(pohon);
2140
+ return output.join("\n");
2141
+ } catch (err) {
2142
+ if (err instanceof SimplError) {
2143
+ const errorText = textLines[err.line-1];
2144
+ return (errorText ? `ERROR! Pada baris ke-${err.line}\n>> ` + errorText + '\n': "") + err.message;
2145
+ } else {
2146
+ return `[Pada baris ke-${this.interpreter.line}] Uh Oh, ini error sistem. Mohon laporkan agar diperbaiki. [ ${err} ]`;
2147
+ }
2148
+ }
2149
+
2150
+ }
2151
+ }
2152
+
2153
+ function run(code) {
2154
+ return new Simpl().runCode(code);
2155
+ }
2156
+
2157
+ export { run as default };