rip-lang 3.9.0 → 3.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1419 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Rip Playground</title>
7
- <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg width='420' height='420' viewBox='0 0 420 420' fill='none' xmlns='http://www.w3.org/2000/svg'><circle cx='210' cy='210' r='210' fill='white'/><path d='M114.5 263C79.772 263 45.7872 275.051 34.271 283.579C33.4994 284.151 34.012 285.229 34.9656 285.117C73.1916 280.629 115.309 292.74 146.5 304C178.5 315.552 221 336 269 336C314.651 336 372.074 310.499 386.401 293.944C387.038 293.208 386.301 292.353 385.435 292.799C376.614 297.341 364.243 306.018 316.073 306.948C265.273 307.928 159 263 114.5 263Z' fill='%230389FF'/><path d='M223.46 84C239.53 84 253.592 86.9253 265.645 92.7754C277.697 98.6254 287.071 107.048 293.767 118.043C300.462 129.038 303.811 142.219 303.811 157.584C303.811 173.09 300.357 186.165 293.449 196.808C287.068 206.742 278.259 214.402 267.026 219.792L309.603 297.958C297.885 297.851 283.094 295.413 266.52 291.62C257.58 289.574 248.214 287.157 238.645 284.547L209.127 229.054H188.782V270.333C176.996 266.968 165.515 263.788 154.823 261.15C146.038 258.984 137.665 257.152 130 255.885V84H223.46ZM188.782 183.381H209.505C216.412 183.381 222.297 182.535 227.16 180.844C232.094 179.082 235.865 176.297 238.473 172.491C241.151 168.685 242.49 163.716 242.49 157.584C242.49 151.382 241.151 146.342 238.473 142.466C235.865 138.519 232.094 135.628 227.16 133.796C222.297 131.893 216.412 130.941 209.505 130.941H188.782V183.381Z' fill='%23BB0000'/></svg>">
8
- <style>
9
- * {
10
- margin: 0;
11
- padding: 0;
12
- box-sizing: border-box;
13
- }
14
-
15
- /* Hide body until fully initialized to prevent render flicker */
16
- body {
17
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
- background: #1e1e1e;
19
- color: #d4d4d4;
20
- height: 100vh;
21
- display: flex;
22
- flex-direction: column;
23
- opacity: 0;
24
- visibility: hidden;
25
- }
26
-
27
- body.ready {
28
- visibility: visible;
29
- opacity: 1;
30
- transition: opacity 0.15s ease-in;
31
- }
32
-
33
- #app {
34
- flex: 1;
35
- display: flex;
36
- flex-direction: column;
37
- min-height: 0;
38
- }
39
-
40
- #app > *, #app > * > * {
41
- flex: 1;
42
- display: flex;
43
- flex-direction: column;
44
- min-height: 0;
45
- }
46
-
47
- .header {
48
- background: #252526;
49
- padding: 15px 20px;
50
- border-bottom: 1px solid #3e3e42;
51
- display: flex;
52
- justify-content: space-between;
53
- align-items: center;
54
- flex: none;
55
- }
56
-
57
- .header-left {
58
- display: flex;
59
- flex-direction: column;
60
- }
61
-
62
- h1 {
63
- display: flex;
64
- align-items: center;
65
- gap: 12px;
66
- font-size: 20px;
67
- font-weight: 600;
68
- color: #cccccc;
69
- margin-bottom: 5px;
70
- }
71
-
72
- .logo {
73
- height: 32px;
74
- width: auto;
75
- }
76
-
77
- .subtitle {
78
- font-size: 13px;
79
- color: #858585;
80
- }
81
-
82
- .github-link {
83
- color: #858585;
84
- transition: color 0.2s;
85
- align-self: flex-start;
86
- margin-top: 4px;
87
- }
88
-
89
- .github-link:hover {
90
- color: #ffffff;
91
- }
92
-
93
- .github-link svg {
94
- width: 24px;
95
- height: 24px;
96
- fill: currentColor;
97
- }
98
-
99
- .tabs {
100
- display: flex;
101
- background: #2d2d30;
102
- border-bottom: 1px solid #3e3e42;
103
- flex: none;
104
- }
105
-
106
- .tab {
107
- padding: 12px 24px;
108
- background: transparent;
109
- border: none;
110
- color: #858585;
111
- cursor: pointer;
112
- font-size: 13px;
113
- font-weight: 500;
114
- border-bottom: 2px solid transparent;
115
- transition: all 0.2s;
116
- }
117
-
118
- .tab:hover {
119
- color: #cccccc;
120
- background: #2a2a2d;
121
- }
122
-
123
- .tab.active {
124
- color: #ffffff;
125
- border-bottom-color: #007acc;
126
- background: #1e1e1e;
127
- }
128
-
129
- .content {
130
- flex: 1;
131
- display: flex;
132
- flex-direction: column;
133
- overflow: hidden;
134
- }
135
-
136
- .pane {
137
- display: none;
138
- flex: 1;
139
- overflow: hidden;
140
- }
141
-
142
- .pane.active {
143
- display: flex;
144
- flex-direction: column;
145
- }
146
-
147
- /* REPL Styles */
148
- .repl-container {
149
- flex: 1;
150
- display: flex;
151
- flex-direction: column;
152
- padding: 20px;
153
- overflow: hidden;
154
- }
155
-
156
- .repl-output {
157
- flex: 1;
158
- overflow-y: auto;
159
- font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
160
- font-size: 14px;
161
- line-height: 1.6;
162
- padding: 10px;
163
- background: #1e1e1e;
164
- border: 1px solid #3e3e42;
165
- border-radius: 4px;
166
- margin-bottom: 10px;
167
- }
168
-
169
- .repl-line {
170
- margin-bottom: 8px;
171
- }
172
-
173
- .prompt {
174
- color: #4ec9b0;
175
- font-weight: 600;
176
- }
177
-
178
- .repl-code {
179
- display: inline;
180
- }
181
-
182
- .repl-code > span {
183
- display: inline !important;
184
- background: none !important;
185
- padding: 0 !important;
186
- }
187
-
188
- .result {
189
- color: #ce9178;
190
- margin-left: 20px;
191
- }
192
-
193
- .error {
194
- color: #f48771;
195
- margin-left: 20px;
196
- }
197
-
198
- .command-output {
199
- color: #858585;
200
- margin-left: 20px;
201
- white-space: pre-wrap;
202
- }
203
-
204
- .repl-input-area {
205
- display: flex;
206
- align-items: flex-start;
207
- gap: 4px;
208
- background: #2d2d30;
209
- padding: 8px 10px;
210
- border: 1px solid #3e3e42;
211
- border-radius: 4px;
212
- }
213
-
214
- .repl-prompt-text {
215
- color: #4ec9b0;
216
- font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
217
- font-weight: 600;
218
- font-size: 14px;
219
- line-height: 24px;
220
- margin-top: 0;
221
- }
222
-
223
- .repl-editor-container {
224
- flex: 1;
225
- height: 24px;
226
- overflow: hidden;
227
- border-radius: 4px;
228
- }
229
-
230
- .repl-editor-container .monaco-editor,
231
- .repl-editor-container .monaco-editor .focused,
232
- .repl-editor-container .monaco-editor-background {
233
- outline: none !important;
234
- box-shadow: none !important;
235
- }
236
-
237
- .repl-editor-container .monaco-editor,
238
- .repl-editor-container .monaco-editor .overflow-guard {
239
- border-radius: 0;
240
- }
241
-
242
- .repl-editor-container .monaco-editor .lines-content {
243
- padding-right: 0 !important;
244
- }
245
-
246
- /* Compiler Styles */
247
- .compiler-container {
248
- flex: 1;
249
- min-height: 0;
250
- display: flex;
251
- background: #3e3e42;
252
- padding: 20px;
253
- position: relative;
254
- }
255
-
256
- .resizer {
257
- width: 10px;
258
- background: #3e3e42;
259
- cursor: col-resize;
260
- position: relative;
261
- flex-shrink: 0;
262
- }
263
-
264
- .resizer:hover {
265
- background: #007acc;
266
- }
267
-
268
- .resizer::after {
269
- content: '';
270
- position: absolute;
271
- top: 50%;
272
- left: 50%;
273
- transform: translate(-50%, -50%);
274
- width: 4px;
275
- height: 40px;
276
- background: #888888;
277
- border-radius: 2px;
278
- }
279
-
280
- .editor-pane {
281
- display: flex;
282
- flex-direction: column;
283
- background: #1e1e1e;
284
- border-radius: 4px;
285
- overflow: hidden;
286
- min-height: 0;
287
- flex: 1;
288
- min-width: 200px;
289
- }
290
-
291
- .editor-left {
292
- flex-basis: 50%;
293
- }
294
-
295
- .editor-right {
296
- flex-basis: 50%;
297
- }
298
-
299
- .pane-header {
300
- background: #2d2d30;
301
- padding: 10px 15px;
302
- border-bottom: 1px solid #3e3e42;
303
- font-size: 13px;
304
- font-weight: 600;
305
- color: #cccccc;
306
- display: flex;
307
- justify-content: space-between;
308
- align-items: center;
309
- min-height: 49px;
310
- box-sizing: border-box;
311
- }
312
-
313
- .pane-header-buttons {
314
- display: flex;
315
- gap: 8px;
316
- }
317
-
318
- .pane-header button {
319
- background: #5a5a5d;
320
- color: #ffffff;
321
- border: none;
322
- padding: 6px 12px;
323
- border-radius: 3px;
324
- font-size: 12px;
325
- font-weight: 500;
326
- cursor: pointer;
327
- transition: background 0.2s;
328
- }
329
-
330
- .pane-header button:hover {
331
- background: #6e6e71;
332
- }
333
-
334
- .pane-header button.active {
335
- background: #007acc;
336
- }
337
-
338
- .pane-header button.active:hover {
339
- background: #005a9e;
340
- }
341
-
342
- .editor-wrapper {
343
- flex: 1;
344
- min-height: 0;
345
- overflow: hidden;
346
- }
347
-
348
- .theme-select {
349
- background: #3c3c3c;
350
- color: #cccccc;
351
- border: 1px solid #5a5a5d;
352
- padding: 4px 8px;
353
- border-radius: 3px;
354
- font-size: 12px;
355
- cursor: pointer;
356
- outline: none;
357
- }
358
-
359
- .theme-select:hover {
360
- border-color: #007acc;
361
- }
362
-
363
- /* Scrollbar styling */
364
- ::-webkit-scrollbar {
365
- width: 10px;
366
- height: 10px;
367
- }
368
-
369
- ::-webkit-scrollbar-track {
370
- background: #1e1e1e;
371
- }
372
-
373
- ::-webkit-scrollbar-thumb {
374
- background: #555555;
375
- border-radius: 5px;
376
- }
377
-
378
- ::-webkit-scrollbar-thumb:hover {
379
- background: #686868;
380
- }
381
-
382
- ::-webkit-scrollbar-corner {
383
- background: #1e1e1e;
384
- }
385
-
386
- .monaco-editor .slider {
387
- border-radius: 5px !important;
388
- }
389
-
390
- .monaco-editor .deprecated {
391
- text-decoration: none !important;
392
- }
393
-
394
- /* Welcome message */
395
- .welcome {
396
- color: #858585;
397
- font-style: italic;
398
- margin-bottom: 15px;
399
- }
400
-
401
- .help-text {
402
- color: #6a9955;
403
- }
404
-
405
- .command {
406
- color: #4fc1ff;
407
- }
408
-
409
- .var-name {
410
- color: #9cdcfe;
411
- }
412
-
413
- .var-value {
414
- color: #ce9178;
415
- }
416
-
417
- /* Light/Dark mode toggle */
418
- .mode-toggle {
419
- background: none;
420
- border: 1px solid #5a5a5d;
421
- border-radius: 4px;
422
- color: #858585;
423
- cursor: pointer;
424
- width: 32px;
425
- height: 32px;
426
- font-size: 16px;
427
- line-height: 32px;
428
- text-align: center;
429
- padding: 0;
430
- transition: all 0.2s;
431
- margin-right: 8px;
432
- }
433
-
434
- .mode-toggle:hover {
435
- color: #ffffff;
436
- border-color: #007acc;
437
- }
438
-
439
- .header-right {
440
- display: flex;
441
- align-items: center;
442
- gap: 4px;
443
- }
444
-
445
- /* Light mode overrides */
446
- body.light {
447
- background: #ffffff;
448
- color: #1e1e1e;
449
- }
450
-
451
- body.light .header {
452
- background: #f3f3f3;
453
- border-bottom-color: #d4d4d4;
454
- }
455
-
456
- body.light h1 { color: #333333; }
457
- body.light .subtitle { color: #666666; }
458
- body.light .github-link { color: #666666; }
459
- body.light .github-link:hover { color: #000000; }
460
- body.light .mode-toggle { color: #666666; border-color: #cccccc; }
461
- body.light .mode-toggle:hover { color: #000000; border-color: #007acc; }
462
-
463
- body.light .tabs {
464
- background: #f3f3f3;
465
- border-bottom-color: #d4d4d4;
466
- }
467
-
468
- body.light .tab { color: #666666; }
469
- body.light .tab:hover { color: #333333; background: #e8e8e8; }
470
- body.light .tab.active { color: #333333; border-bottom-color: #007acc; background: #ffffff; }
471
-
472
- body.light .compiler-container { background: #e0e0e0; }
473
- body.light .editor-pane { background: #ffffff; border-color: #d4d4d4; }
474
- body.light .content { background: #ffffff; }
475
-
476
- body.light .pane-header {
477
- background: #f3f3f3;
478
- border-bottom-color: #d4d4d4;
479
- color: #333333;
480
- }
481
-
482
- body.light .pane-header button { background: #cccccc; color: #333333; }
483
- body.light .pane-header button:hover { background: #bbbbbb; }
484
- body.light .pane-header button.active { background: #007acc; color: #ffffff; }
485
- body.light .theme-select { background: #f3f3f3; color: #333333; border-color: #cccccc; }
486
-
487
- body.light .resizer { background: #e0e0e0; }
488
- body.light .resizer:hover { background: #007acc; }
489
-
490
- body.light ::-webkit-scrollbar-track { background: #f3f3f3; }
491
- body.light ::-webkit-scrollbar-thumb { background: #c1c1c1; }
492
- body.light ::-webkit-scrollbar-thumb:hover { background: #a0a0a0; }
493
- body.light ::-webkit-scrollbar-corner { background: #f3f3f3; }
494
-
495
- body.light .repl-container { background: #e8e8e8; }
496
- body.light .repl-output { background: #ffffff; border-color: #d4d4d4; color: #1e1e1e; }
497
- body.light .repl-output .prompt { color: #007acc; }
498
- body.light .repl-output .result { color: #a31515; }
499
- body.light .repl-output .error { color: #d32f2f; }
500
- body.light .repl-output .welcome { color: #666666; }
501
- body.light .repl-output .command { color: #0070c1; }
502
- body.light .repl-output .command-output { color: #555555; }
503
- body.light .repl-input-area { background: #f3f3f3; border-color: #d4d4d4; border-radius: 6px; }
504
- body.light .repl-prompt-text { color: #007acc; }
505
- </style>
506
- <link rel="preload" href="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.0/min/vs/loader.js" as="script">
507
- <script type="module" src="dist/rip-ui.min.js"></script>
508
- </head>
509
- <body>
510
-
511
- <div id="app"></div>
512
-
513
- <!-- ====================================================================
514
- Monarch Tokenizer — Rip syntax highlighting for Monaco
515
- Third-party configuration, kept as JS for clarity and maintainability
516
- ==================================================================== -->
517
- <script>
518
- window.ripMonarch = {
519
- defaultToken: '',
520
- tokenPostfix: '.rip',
521
-
522
- keywords: [
523
- 'if', 'else', 'unless', 'then', 'switch', 'when', 'for', 'while', 'until',
524
- 'loop', 'do', 'return', 'break', 'continue', 'throw', 'try', 'catch', 'finally',
525
- 'yield', 'await', 'import', 'export', 'from', 'default', 'delete', 'typeof',
526
- 'instanceof', 'new', 'super', 'debugger', 'use', 'own', 'extends', 'in', 'of',
527
- 'by', 'as', 'class', 'def', 'enum', 'interface', 'component', 'render',
528
- ],
529
-
530
- logicalWords: ['and', 'or', 'not', 'is', 'isnt'],
531
- booleans: ['true', 'false', 'yes', 'no', 'on', 'off'],
532
- constants: ['null', 'undefined', 'NaN', 'Infinity', 'this'],
533
- typeKeywords: ['number', 'string', 'boolean', 'void', 'any', 'never', 'unknown', 'object', 'symbol', 'bigint'],
534
-
535
- operators: [
536
- '|>', '::=', '::', ':=', '~=', '~>', '<=>', '=!', '!?', '=~', '??', '?.', '...',
537
- '..', '**', '//', '%%', '++', '--', '&&', '||', '===', '!==', '==', '!=',
538
- '<=', '>=', '=>', '->', '+=', '-=', '*=', '/=', '%=', '**=', '&&=', '||=',
539
- '??=', '<<=', '>>=', '>>>=', '&=', '|=', '^=', '>>>', '>>', '<<',
540
- '+', '-', '*', '/', '%', '&', '|', '^', '~', '<', '>', '=', '!', '?',
541
- ],
542
-
543
- symbols: /[=><!~?:&|+\-*\/\^%\.#]+/,
544
- escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
545
-
546
- tokenizer: {
547
- root: [
548
- // Block comments ###...###
549
- [/###/, 'comment', '@blockComment'],
550
-
551
- // Line comments
552
- [/#(?!\{).*$/, 'comment'],
553
-
554
- // Heredocs (before regular strings)
555
- [/"""/, 'string', '@heredocDouble'],
556
- [/'''/, 'string', '@heredocSingle'],
557
-
558
- // Strings
559
- [/"/, 'string', '@stringDouble'],
560
- [/'/, 'string', '@stringSingle'],
561
-
562
- // Heregex
563
- [/\/\/\//, 'regexp', '@heregex'],
564
-
565
- // Regex (after operator context)
566
- [/\/(?![/*])(?:[^[\/\n\\]|\\.|\[(?:\\[^\n]|[^\]\n\\])*\])*(\/)([gimsuy]*)/, 'regexp'],
567
-
568
- // Inline JavaScript
569
- [/`[^`]*`/, 'string.other'],
570
-
571
- // Instance variables @name
572
- [/@[a-zA-Z_$][\w$]*/, 'variable.instance'],
573
- [/@/, 'variable.instance'],
574
-
575
- // Type names (PascalCase) — before identifiers
576
- [/[A-Z][\w]*/, 'type.identifier'],
577
-
578
- // Identifiers and keywords
579
- [/[a-zA-Z_$][\w$]*[!?]?/, {
580
- cases: {
581
- '@keywords': 'keyword',
582
- '@logicalWords': 'keyword.operator',
583
- '@booleans': 'constant.language',
584
- '@constants': 'constant.language',
585
- '@typeKeywords': 'support.type',
586
- '@default': 'identifier',
587
- }
588
- }],
589
-
590
- // Numbers
591
- [/0x[0-9a-fA-F](?:_?[0-9a-fA-F])*n?/, 'number.hex'],
592
- [/0o[0-7](?:_?[0-7])*n?/, 'number.octal'],
593
- [/0b[01](?:_?[01])*n?/, 'number.binary'],
594
- [/\d[\d_]*(?:\.[\d][\d_]*)?(?:[eE][+-]?\d+)?n?/, 'number'],
595
-
596
- // Operators (multi-char first)
597
- [/::=/, 'keyword.operator.type'],
598
- [/::(?!=)/, 'keyword.operator.type'],
599
- [/~=|:=|~>|<=>/, 'keyword.operator.reactive'],
600
- [/=!/, 'keyword.operator.readonly'],
601
- [/!\?/, 'keyword.operator'],
602
- [/=~/, 'keyword.operator'],
603
- [/\?\?/, 'keyword.operator'],
604
- [/\?\./, 'keyword.operator'],
605
- [/\.\.\./, 'keyword.operator'],
606
- [/\.\./, 'keyword.operator'],
607
- [/===|!==|==|!=|<=|>=/, 'keyword.operator.comparison'],
608
- [/=>|->/, 'keyword.operator.arrow'],
609
- [/\*\*|\/\/|%%/, 'keyword.operator.arithmetic'],
610
- [/&&=|\|\|=|\?\?=|\+=|-=|\*=|\/=|%=/, 'keyword.operator.assignment'],
611
- [/&&|\|\|/, 'keyword.operator.logical'],
612
- [/>>>|>>|<</, 'keyword.operator.bitwise'],
613
- [/[+\-*\/%]/, 'keyword.operator.arithmetic'],
614
- [/[&|^~]/, 'keyword.operator.bitwise'],
615
- [/[<>]/, 'keyword.operator.comparison'],
616
- [/=/, 'keyword.operator.assignment'],
617
- [/!/, 'keyword.operator'],
618
- [/\?/, 'keyword.operator'],
619
-
620
- // Brackets
621
- [/[{}()\[\]]/, '@brackets'],
622
-
623
- // Delimiters
624
- [/[;,.]/, 'delimiter'],
625
- ],
626
-
627
- blockComment: [
628
- [/###/, 'comment', '@pop'],
629
- [/./, 'comment'],
630
- ],
631
-
632
- heredocDouble: [
633
- [/"""/, 'string', '@pop'],
634
- [/#\{/, { token: 'string.interpolation', next: '@interpolation' }],
635
- [/@escapes/, 'string.escape'],
636
- [/./, 'string'],
637
- ],
638
-
639
- heredocSingle: [
640
- [/'''/, 'string', '@pop'],
641
- [/@escapes/, 'string.escape'],
642
- [/./, 'string'],
643
- ],
644
-
645
- stringDouble: [
646
- [/"/, 'string', '@pop'],
647
- [/#\{/, { token: 'string.interpolation', next: '@interpolation' }],
648
- [/@escapes/, 'string.escape'],
649
- [/[^"\\#]+/, 'string'],
650
- [/./, 'string'],
651
- ],
652
-
653
- stringSingle: [
654
- [/'/, 'string', '@pop'],
655
- [/@escapes/, 'string.escape'],
656
- [/[^'\\]+/, 'string'],
657
- [/./, 'string'],
658
- ],
659
-
660
- interpolation: [
661
- [/\}/, { token: 'string.interpolation', next: '@pop' }],
662
- { include: 'root' },
663
- ],
664
-
665
- heregex: [
666
- [/\/\/\/[gimsuy]*/, 'regexp', '@pop'],
667
- [/#.*$/, 'comment'],
668
- [/@escapes/, 'regexp.escape'],
669
- [/#\{/, { token: 'string.interpolation', next: '@interpolation' }],
670
- [/./, 'regexp'],
671
- ],
672
- },
673
- };
674
-
675
- window.ripLanguageConfig = {
676
- comments: { lineComment: '#', blockComment: ['###', '###'] },
677
- brackets: [['{', '}'], ['[', ']'], ['(', ')']],
678
- autoClosingPairs: [
679
- { open: '{', close: '}' },
680
- { open: '[', close: ']' },
681
- { open: '(', close: ')' },
682
- { open: '"', close: '"', notIn: ['string'] },
683
- { open: "'", close: "'", notIn: ['string'] },
684
- ],
685
- surroundingPairs: [
686
- { open: '{', close: '}' },
687
- { open: '[', close: ']' },
688
- { open: '(', close: ')' },
689
- { open: '"', close: '"' },
690
- { open: "'", close: "'" },
691
- ],
692
- indentationRules: {
693
- increaseIndentPattern: /^\s*(?:def|class|component|render|if|unless|else|for|while|until|loop|switch|when|try|catch|finally|enum|interface)\b.*$|[-=]>\s*$/,
694
- decreaseIndentPattern: /^\s*(else|catch|finally)\b/,
695
- },
696
- folding: { offSide: true },
697
- };
698
- </script>
699
-
700
- <!-- Shared playground examples (loaded before main script) -->
701
- <script type="text/rip" src="examples.rip"></script>
702
-
703
- <!-- ====================================================================
704
- Application Logic — Rip UI Component via launch bundle:
705
- ==================================================================== -->
706
- <script type="text/rip">
707
-
708
- # ====================================================================
709
- # Load Compiler Exports + Monaco (in parallel)
710
- # ====================================================================
711
-
712
- MONACO_CDN =! 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.0'
713
-
714
- loadMonaco = ->
715
- new Promise (resolve) ->
716
- script = document.createElement 'script'
717
- script.src = "#{MONACO_CDN}/min/vs/loader.js"
718
- script.onload = ->
719
- window.require.config paths: vs: "#{MONACO_CDN}/min/vs"
720
- window.require ['vs/editor/editor.main'], (m) -> resolve m
721
- document.head.appendChild script
722
-
723
- # Start Monaco loading immediately (preload hint already fetching)
724
- monacoPromise = loadMonaco()
725
-
726
- # Compiler exports are already available — no network needed
727
- { compile, formatSExpr, VERSION, BUILD_DATE, getReactiveRuntime } = window.__ripExports
728
-
729
- # Compute subtitle while Monaco loads
730
- localDate = new Date(BUILD_DATE.replace('@', 'T').replace('GMT', 'Z'))
731
- .toLocaleString undefined,
732
- year: 'numeric', month: 'short', day: 'numeric'
733
- hour: 'numeric', minute: '2-digit', timeZoneName: 'short'
734
-
735
- # ====================================================================
736
- # Set Globals (accessible by the component via globalThis)
737
- # ====================================================================
738
-
739
- globalThis.compile = compile
740
- globalThis.formatSExpr = formatSExpr
741
- globalThis.getReactiveRuntime = getReactiveRuntime
742
- globalThis.subtitleText = "Interactive environment • Version #{VERSION} • Built #{localDate}"
743
- globalThis.MONACO_CDN = MONACO_CDN
744
- globalThis.THEME_CDN = 'https://cdn.jsdelivr.net/npm/monaco-themes@0.4.4/themes'
745
- globalThis.loadedThemes = new Set ['vs', 'vs-dark', 'hc-black', 'hc-light']
746
-
747
- # --- Helper Functions ---
748
-
749
- globalThis.escapeHtml = (str) ->
750
- String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
751
-
752
- globalThis.formatValue = (val) ->
753
- return 'null' if val is null
754
- return 'undefined' if val is undefined
755
- return val.toString() if val instanceof RegExp
756
- return val.toString() if typeof val is 'function'
757
- return JSON.stringify(val) if typeof val is 'string'
758
- return String(val) if typeof val is 'number' or typeof val is 'boolean'
759
- if Array.isArray val
760
- try
761
- return JSON.stringify val
762
- catch
763
- return '[Array]'
764
- if typeof val is 'object'
765
- try
766
- return JSON.stringify val, null, 2
767
- catch
768
- return '[Object]'
769
- String val
770
-
771
- globalThis.SUPPRESS_VALUE = new Set [
772
- 'DEF', 'IF', 'ELSE', 'UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP'
773
- 'SWITCH', 'WHEN', 'TRY', 'CATCH', 'FINALLY', 'THROW', 'RETURN'
774
- 'CLASS', 'EXTENDS', 'SUPER', 'NEW', 'DELETE', 'TYPEOF', 'INSTANCEOF'
775
- 'IMPORT', 'FROM', 'EXPORT', 'DEFAULT', 'ENUM', 'INTERFACE'
776
- 'AND', 'OR', 'NOT', 'IS', 'ISNT', 'IN', 'OF'
777
- 'TRUE', 'FALSE', 'NULL', 'UNDEFINED', 'YES', 'NO'
778
- 'CALL_START', 'CALL_END', 'PARAM_START', 'PARAM_END'
779
- 'INDEX_START', 'INDEX_END', 'TERMINATOR'
780
- ]
781
-
782
- globalThis.REPL_BUILTINS = new Set [
783
- 'console', 'showSexp', 'showTokens', 'eval', 'window', 'document'
784
- 'location', 'navigator', 'self', 'top', 'parent', 'frames'
785
- '__state', '__computed', '__effect', '__batch', '__readonly'
786
- '__currentEffect', '__pendingEffects', '__reactiveVars'
787
- '__rip', '__flushEffects', '__primitiveCoercion'
788
- '__setErrorHandler', '__handleError', '__catchErrors', '__errorHandler'
789
- '__batching'
790
- ]
791
-
792
- globalThis.formatTokens = (tokens) ->
793
- SV = globalThis.SUPPRESS_VALUE
794
- maxTag = 0
795
- for t in tokens
796
- maxTag = Math.max maxTag, t[0].length
797
- tokens.map((t) ->
798
- tag = t[0]
799
- val = t[1]
800
- return tag if SV.has tag
801
- return tag if tag is val or tag.toLowerCase() is val
802
- return "#{tag.padEnd(maxTag)} #{val}" if tag is 'INDENT' or tag is 'OUTDENT'
803
- return "#{tag.padEnd(maxTag)} #{val}" if tag is 'STRING' or tag is 'REGEX'
804
- "#{tag.padEnd(maxTag)} #{JSON.stringify(val)}"
805
- ).join "\n"
806
-
807
- # Examples loaded from external examples.rip
808
- globalThis.defaultCode = globalThis.ripExamples.demo
809
- globalThis.examples = globalThis.ripExamples
810
-
811
- # Regex patterns (defined here because \n in ''' heredoc becomes a real newline)
812
- globalThis.RE_LET_STRIP = /^let\s+[^;]+;\s*\n+/m
813
- globalThis.RE_REACTIVE = /^const\s+(\w+)\s*=\s*((?:__state|__computed|__effect)\(.+\));?$/gm
814
- globalThis.RE_CONST_STRIP = /^const\s+(\w+)\s*=/gm
815
- globalThis.RE_NEWLINE = /\n/g
816
-
817
- # ====================================================================
818
- # Import UI Framework & Launch
819
- # ====================================================================
820
-
821
- { launch } = importRip!('dist/ui.rip')
822
-
823
- # Await Monaco (all sync work above ran in parallel with the fetch)
824
- globalThis.monaco = await monacoPromise
825
- monaco = globalThis.monaco
826
- monaco.languages.register id: 'rip', extensions: ['.rip']
827
- monaco.languages.setMonarchTokensProvider 'rip', window.ripMonarch
828
- monaco.languages.setLanguageConfiguration 'rip', window.ripLanguageConfig
829
-
830
- launch window.location.pathname,
831
- target: '#app'
832
- bundle:
833
- components:
834
- 'components/index.rip': '''
835
- export Index = component
836
-
837
- # ==============================================================
838
- # Local State (closure variables — shared across all methods)
839
- # ==============================================================
840
-
841
- showTokens = false
842
- showSexp = false
843
- showPreamble = false
844
- showTypes = false
845
- showJS = true
846
- isDark = true
847
- replHistory = []
848
- historyIndex = -1
849
- replBuffer = ''
850
- reactiveVars = new Set()
851
- isResizing = false
852
- sourceEditor = null
853
- outputEditor = null
854
- replEditor = null
855
- replCtx = null
856
-
857
- # ==============================================================
858
- # Compilation
859
- # ==============================================================
860
-
861
- compileCode: ->
862
- try
863
- source = sourceEditor.getValue()
864
- opts = {}
865
- opts.skipPreamble = true unless showPreamble
866
- opts.types = 'emit' if showTypes
867
- result = globalThis.compile source, opts
868
- parts = []
869
- parts.push globalThis.formatTokens(result.tokens) if showTokens
870
- parts.push globalThis.formatSExpr(result.sexpr, 0, true) if showSexp
871
- parts.push "// .d.ts\\n#{result.dts.trim()}" if showTypes and result.dts
872
- parts.push result.code if showJS
873
- outputText = parts.join "\\n\\n"
874
- model = outputEditor.getModel()
875
- lang = if showTokens or showSexp then 'plaintext' else if showTypes and not showJS then 'typescript' else 'javascript'
876
- globalThis.monaco.editor.setModelLanguage model, lang
877
- outputEditor.setValue outputText
878
- catch error
879
- model = outputEditor.getModel()
880
- globalThis.monaco.editor.setModelLanguage model, 'plaintext'
881
- outputEditor.setValue "Error: #{error.message}"
882
-
883
- # ==============================================================
884
- # Run Code
885
- # ==============================================================
886
-
887
- runCode: ->
888
- try
889
- source = sourceEditor.getValue()
890
- result = globalThis.compile source
891
- console.clear()
892
- console.log '%c=== Rip Code Execution ===', 'color: #007acc; font-weight: bold'
893
- await window.eval("(async()=>{\\n#{result.code}\\n})()")
894
- console.log '%c=== Execution Complete ===', 'color: #4ec9b0; font-weight: bold'
895
- catch error
896
- console.error 'Execution Error:', error
897
-
898
- # ==============================================================
899
- # Theme Management
900
- # ==============================================================
901
-
902
- setTheme: (value) ->
903
- themeId = value
904
- if value.startsWith 'cdn:'
905
- themeName = value.slice 4
906
- themeId = themeName.toLowerCase().replace /[\\s_]+/g, '-'
907
- unless globalThis.loadedThemes.has themeId
908
- try
909
- res = fetch!("#{globalThis.THEME_CDN}/#{encodeURIComponent(themeName)}.json")
910
- data = res.json!
911
- globalThis.monaco.editor.defineTheme themeId, data
912
- globalThis.loadedThemes.add themeId
913
- catch err
914
- console.error "Failed to load theme: #{themeName}", err
915
- return
916
- globalThis.monaco.editor.setTheme themeId
917
- localStorage.setItem 'rip-repl-theme', value
918
-
919
- applyMode: ->
920
- document.body.classList.toggle 'light', not isDark
921
- @modeToggle.innerHTML = if isDark then '&#9790;' else '&#9788;'
922
- @modeToggle.title = if isDark then 'Switch to light mode' else 'Switch to dark mode'
923
- localStorage.setItem 'rip-repl-mode', if isDark then 'dark' else 'light'
924
-
925
- # ==============================================================
926
- # Tab Switching
927
- # ==============================================================
928
-
929
- switchToTab: (tabName) ->
930
- window.location.hash = tabName
931
- @tabCompiler.classList.toggle 'active', tabName is 'compiler'
932
- @tabRepl.classList.toggle 'active', tabName is 'repl'
933
- @compilerPane.classList.toggle 'active', tabName is 'compiler'
934
- @replPane.classList.toggle 'active', tabName is 'repl'
935
- if tabName is 'repl'
936
- globalThis.monaco.editor.setTheme if isDark then 'vs-dark' else 'vs'
937
- replEditor.focus()
938
- else if tabName is 'compiler'
939
- @setTheme! @themeSelect.value
940
- @compileCode()
941
-
942
- onTabCompiler: -> @switchToTab 'compiler'
943
- onTabRepl: -> @switchToTab 'repl'
944
-
945
- # ==============================================================
946
- # Editor Actions
947
- # ==============================================================
948
-
949
- onClear: ->
950
- sourceEditor.setValue ''
951
- @compileCode()
952
- sourceEditor.focus()
953
-
954
- onRun: -> @runCode()
955
-
956
- # ==============================================================
957
- # Toggle Buttons
958
- # ==============================================================
959
-
960
- onToggleTokens: ->
961
- showTokens = not showTokens
962
- @showTokensBtn.classList.toggle 'active', showTokens
963
- localStorage.setItem 'rip-repl-tokens', showTokens
964
- @compileCode()
965
-
966
- onToggleSexp: ->
967
- showSexp = not showSexp
968
- @showSexpBtn.classList.toggle 'active', showSexp
969
- localStorage.setItem 'rip-repl-sexp', showSexp
970
- @compileCode()
971
-
972
- onTogglePreamble: ->
973
- showPreamble = not showPreamble
974
- @showPreambleBtn.classList.toggle 'active', showPreamble
975
- localStorage.setItem 'rip-preamble', showPreamble
976
- @compileCode()
977
-
978
- onToggleTypes: ->
979
- showTypes = not showTypes
980
- @showTypesBtn.classList.toggle 'active', showTypes
981
- localStorage.setItem 'rip-types', showTypes
982
- @compileCode()
983
-
984
- onToggleJS: ->
985
- showJS = not showJS
986
- @showJSBtn.classList.toggle 'active', showJS
987
- localStorage.setItem 'rip-repl-js', showJS
988
- @compileCode()
989
-
990
- # ==============================================================
991
- # REPL Helpers
992
- # ==============================================================
993
-
994
- addOutput: (content, className = '') ->
995
- line = document.createElement 'div'
996
- line.className = "repl-line #{className}"
997
- line.innerHTML = content
998
- @replOutput.appendChild line
999
- @replOutput.scrollTop = @replOutput.scrollHeight
1000
-
1001
- evaluateRip: (code) ->
1002
- try
1003
- compileOpts = { skipPreamble: true }
1004
- compileOpts.reactiveVars = reactiveVars
1005
- result = globalThis.compile code, compileOpts
1006
- js = result.code
1007
- if result.reactiveVars
1008
- for v as result.reactiveVars
1009
- reactiveVars.add v
1010
- js .= replace globalThis.RE_LET_STRIP, ''
1011
- reactiveTransform = (match, varName, rhs) ->
1012
- "#{varName} = __reactiveVars['#{varName}'] ?? (__reactiveVars['#{varName}'] = #{rhs});"
1013
- js .= replace globalThis.RE_REACTIVE, reactiveTransform
1014
- js .= replace globalThis.RE_CONST_STRIP, '$1 ='
1015
- evalResult = replCtx.eval js
1016
- replCtx._ = evalResult if evalResult isnt undefined
1017
- { success: true, value: evalResult, result }
1018
- catch error
1019
- { success: false, error: error.message }
1020
-
1021
- # ==============================================================
1022
- # REPL Commands
1023
- # ==============================================================
1024
-
1025
- handleCommand: (cmd) ->
1026
- eh = globalThis.escapeHtml
1027
- fv = globalThis.formatValue
1028
- BUILTINS = globalThis.REPL_BUILTINS
1029
- switch cmd
1030
- when '.help'
1031
- helpLines = [
1032
- "<span class='help-text'>Rip Playground Commands:</span>"
1033
- ""
1034
- "<span class='command'>.help</span> - Show this help"
1035
- "<span class='command'>.clear</span> - Clear output"
1036
- "<span class='command'>.vars</span> - Show variables"
1037
- "<span class='command'>.sexp</span> - Toggle s-expression display"
1038
- "<span class='command'>.tokens</span> - Toggle token display"
1039
- ""
1040
- "<span class='help-text'>Features:</span>"
1041
- "- Variables persist across evaluations"
1042
- "- Previous result in <span class='var-name'>_</span> variable"
1043
- "- Shift+Enter for multi-line input"
1044
- "- All Rip features: heregex, regex+, classes, etc."
1045
- ]
1046
- @addOutput helpLines.join("\\n"), 'command-output'
1047
-
1048
- when '.clear'
1049
- @replOutput.innerHTML = ''
1050
- reactiveVars.clear()
1051
- replCtx.__reactiveVars = {}
1052
- for key as Object.keys(replCtx)
1053
- unless BUILTINS.has(key) or key.startsWith('__')
1054
- try
1055
- delete replCtx[key]
1056
- catch
1057
- null
1058
- @addOutput '<div class="welcome">Output and context cleared.</div>'
1059
-
1060
- when '.vars'
1061
- vars = Object.keys(replCtx).filter (k) ->
1062
- not BUILTINS.has(k) and not k.startsWith('__') or k is '_'
1063
- if vars.length is 0 or (vars.length is 1 and vars[0] is '_' and replCtx._ is undefined)
1064
- @addOutput "<span class='help-text'>No variables defined</span>", 'command-output'
1065
- else
1066
- output = "<span class='help-text'>Variables:</span>\\n"
1067
- for v in vars
1068
- try
1069
- val = replCtx[v]
1070
- typeIndicator = '='
1071
- if val and typeof val is 'object' and 'value' of val
1072
- if typeof val.markDirty is 'function'
1073
- typeIndicator = "<span style='color:#c586c0'>~=</span>"
1074
- else if typeof val is 'function'
1075
- typeIndicator = "<span style='color:#c586c0'>~&gt;</span>"
1076
- else
1077
- typeIndicator = "<span style='color:#c586c0'>:=</span>"
1078
- val = val.value
1079
- formatted = fv val
1080
- output += " <span class='var-name'>#{v}</span> #{typeIndicator} <span class='var-value'>#{eh(formatted)}</span>\\n"
1081
- catch
1082
- output += " <span class='var-name'>#{v}</span> = <span class='var-value'>[object]</span>\\n"
1083
- @addOutput output, 'command-output'
1084
-
1085
- when '.sexp'
1086
- replCtx.showSexp = not replCtx.showSexp
1087
- state = if replCtx.showSexp then 'ON' else 'OFF'
1088
- @addOutput "S-expression display: #{state}", 'command-output'
1089
-
1090
- when '.tokens'
1091
- replCtx.showTokens = not replCtx.showTokens
1092
- state = if replCtx.showTokens then 'ON' else 'OFF'
1093
- @addOutput "Token display: #{state}", 'command-output'
1094
-
1095
- else
1096
- @addOutput "Unknown command: #{cmd}", 'error'
1097
- @addOutput 'Type .help for available commands', 'help-text'
1098
-
1099
- # ==============================================================
1100
- # REPL Line Handling
1101
- # ==============================================================
1102
-
1103
- handleLine: (line) ->
1104
- eh = globalThis.escapeHtml
1105
- fv = globalThis.formatValue
1106
- if line.trim() and (replHistory.length is 0 or replHistory[-1] isnt line)
1107
- replHistory.push line
1108
- historyIndex = replHistory.length
1109
- promptText = if replBuffer then '....>' else 'rip>'
1110
- codeId = "repl-code-#{Date.now()}"
1111
- @addOutput "<span class='prompt'>#{promptText}</span> <span class='repl-code' id='#{codeId}'>#{eh(line)}</span>"
1112
- globalThis.monaco.editor.colorize(line, 'rip', {}).then (highlighted) ->
1113
- el = document.getElementById codeId
1114
- el.innerHTML = highlighted if el
1115
-
1116
- if line.startsWith '.'
1117
- @handleCommand line
1118
- return
1119
-
1120
- replBuffer = if replBuffer then replBuffer + "\\n" + line else line
1121
- evalResult = @evaluateRip replBuffer
1122
-
1123
- if evalResult.success
1124
- if replCtx.showTokens
1125
- @addOutput globalThis.formatTokens(evalResult.result.tokens).replace(globalThis.RE_NEWLINE, '<br>'), 'command-output'
1126
- if replCtx.showSexp
1127
- sexp = globalThis.formatSExpr evalResult.result.sexpr, 0, true
1128
- formatted = sexp.replace(globalThis.RE_NEWLINE, '<br>').replace(/ /g, '&nbsp;')
1129
- @addOutput formatted, 'command-output'
1130
- if evalResult.value isnt undefined
1131
- displayValue = evalResult.value
1132
- if displayValue and typeof displayValue is 'object' and 'value' of displayValue
1133
- displayValue = displayValue.value
1134
- formatted = fv displayValue
1135
- @addOutput "<span class='result'>→ #{eh(formatted)}</span>"
1136
- replBuffer = ''
1137
- @prompt.textContent = 'rip>'
1138
- else if evalResult.error.includes 'Unexpected end'
1139
- @prompt.textContent = '....>'
1140
- else
1141
- @addOutput "<span class='error'>✗ #{eh(evalResult.error)}</span>"
1142
- replBuffer = ''
1143
- @prompt.textContent = 'rip>'
1144
-
1145
- # ==============================================================
1146
- # Mounted — Initialize Everything
1147
- # ==============================================================
1148
-
1149
- mounted: ->
1150
- m = globalThis.monaco
1151
- inputLine = null
1152
- mdl = null
1153
-
1154
- # --- Restore Persisted State ---
1155
- showTokens = localStorage.getItem('rip-repl-tokens') is 'true'
1156
- showSexp = localStorage.getItem('rip-repl-sexp') is 'true'
1157
- showPreamble = localStorage.getItem('rip-preamble') is 'true'
1158
- showTypes = localStorage.getItem('rip-types') is 'true'
1159
- showJS = localStorage.getItem('rip-repl-js') isnt 'false'
1160
- savedMode = localStorage.getItem 'rip-repl-mode'
1161
- isDark = if savedMode then savedMode isnt 'light' else not window.matchMedia('(prefers-color-scheme: light)').matches
1162
-
1163
- # --- Populate Dynamic Content ---
1164
- @titleEl.innerHTML = '<img src="rip.svg" alt="Rip" class="logo" style="background-color:#fff;border-radius:5px;padding:4px"> Rip Playground'
1165
- @subtitleEl.textContent = globalThis.subtitleText
1166
- @applyMode()
1167
-
1168
- @githubLink.innerHTML = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>'
1169
-
1170
- @themeSelect.innerHTML = '<optgroup label="Dark Themes"><option value="vs-dark">Dark+ (default)</option><option value="hc-black">High Contrast Dark</option><option value="cdn:GitHub Dark">GitHub Dark</option><option value="cdn:Dracula">Dracula</option><option value="cdn:Monokai">Monokai</option><option value="cdn:Nord">Nord</option></optgroup><optgroup label="Light Themes"><option value="vs">Light+</option><option value="hc-light">High Contrast Light</option><option value="cdn:GitHub Light">GitHub Light</option><option value="cdn:Solarized-light">Solarized Light</option></optgroup>'
1171
-
1172
- # --- Toggle Button State ---
1173
- @showTokensBtn.classList.toggle 'active', showTokens
1174
- @showSexpBtn.classList.toggle 'active', showSexp
1175
- @showPreambleBtn.classList.toggle 'active', showPreamble
1176
- @showTypesBtn.classList.toggle 'active', showTypes
1177
- @showJSBtn.classList.toggle 'active', showJS
1178
-
1179
- # --- Welcome Message ---
1180
- @replOutput.innerHTML = '<div class="welcome"><strong>Rip Playground</strong> - Interactive Environment<br>Type <span class="command">.help</span> for commands, try Rip expressions!</div>'
1181
-
1182
- # --- Create Editors ---
1183
- sharedConfig =
1184
- theme: if isDark then 'vs-dark' else 'vs'
1185
- fontSize: 14
1186
- fontFamily: "'Monaco', 'Menlo', 'Consolas', monospace"
1187
- lineHeight: 22
1188
- minimap: { enabled: false }
1189
- scrollBeyondLastLine: false
1190
- automaticLayout: true
1191
- overviewRulerLanes: 0
1192
- hideCursorInOverviewRuler: true
1193
- renderWhitespace: 'none'
1194
- scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10, verticalSliderSize: 10, horizontalSliderSize: 10 }
1195
- padding: { top: 10 }
1196
-
1197
- savedSource = localStorage.getItem 'rip-playground-source'
1198
- savedExample = localStorage.getItem('rip-playground-example') or 'demo'
1199
- initialCode = savedSource ?? globalThis.defaultCode
1200
-
1201
- sourceEditor = m.editor.create @editorContainer, {
1202
- ...sharedConfig, value: initialCode, language: 'rip', tabSize: 2, insertSpaces: true
1203
- }
1204
-
1205
- outputEditor = m.editor.create @outputContainer, {
1206
- ...sharedConfig, value: '', language: 'javascript', readOnly: true, domReadOnly: true,
1207
- lineNumbers: 'off', folding: false, glyphMargin: false, contextmenu: false
1208
- }
1209
-
1210
- replEditor = m.editor.create @replEditorContainer, {
1211
- value: '', language: 'rip', theme: (if isDark then 'vs-dark' else 'vs'),
1212
- fontSize: 14, fontFamily: "'Monaco', 'Menlo', 'Consolas', monospace", lineHeight: 24,
1213
- minimap: { enabled: false }, scrollBeyondLastLine: false, automaticLayout: true,
1214
- lineNumbers: 'off', glyphMargin: false, folding: false,
1215
- lineDecorationsWidth: 6, lineNumbersMinChars: 0,
1216
- overviewRulerLanes: 0, hideCursorInOverviewRuler: true,
1217
- scrollbar: { vertical: 'hidden', horizontal: 'hidden', handleMouseWheel: false, verticalScrollbarSize: 0, horizontalScrollbarSize: 0 },
1218
- overviewRulerBorder: false, renderLineHighlight: 'none',
1219
- wordWrap: 'off', contextmenu: false, tabSize: 2, insertSpaces: true,
1220
- padding: { top: 0, bottom: 0 },
1221
- quickSuggestions: false, suggestOnTriggerCharacters: false,
1222
- acceptSuggestionOnEnter: 'off', tabCompletion: 'off',
1223
- parameterHints: { enabled: false },
1224
- find: { addExtraSpaceOnTop: false, autoFindInSelection: 'never' }
1225
- }
1226
-
1227
- # --- Example Snippets & Source Persistence ---
1228
- @exampleSelect.innerHTML = '<option value="demo">Full Demo</option><option value="basics">Basics</option><option value="reactive">Reactive State</option><option value="classes">Classes</option><option value="regex">Regex &amp; Strings</option><option value="async">Async &amp; Dammit</option><option value="custom">Custom</option>'
1229
- @exampleSelect.value = savedExample
1230
- settingExample = false
1231
- saveTimer = null
1232
-
1233
- saveSource = =>
1234
- clearTimeout saveTimer if saveTimer
1235
- saveTimer = setTimeout (=>
1236
- localStorage.setItem 'rip-playground-source', sourceEditor.getValue()
1237
- ), 2000
1238
-
1239
- sourceEditor.onDidChangeModelContent =>
1240
- @compileCode()
1241
- saveSource()
1242
- unless settingExample
1243
- if @exampleSelect.value isnt 'custom'
1244
- @exampleSelect.value = 'custom'
1245
- localStorage.setItem 'rip-playground-example', 'custom'
1246
-
1247
- @exampleSelect.addEventListener 'change', (e) =>
1248
- name = e.target.value
1249
- if name is 'custom'
1250
- localStorage.setItem 'rip-playground-example', 'custom'
1251
- else if globalThis.examples[name]
1252
- settingExample = true
1253
- sourceEditor.setValue globalThis.examples[name]
1254
- settingExample = false
1255
- localStorage.setItem 'rip-playground-example', name
1256
- localStorage.setItem 'rip-playground-source', globalThis.examples[name]
1257
-
1258
- # --- Keyboard Shortcuts ---
1259
- sourceEditor.addCommand (m.KeyMod.CtrlCmd | m.KeyCode.Enter), => @runCode()
1260
- document.addEventListener 'keydown', (e) =>
1261
- if e.key is 'F5'
1262
- e.preventDefault()
1263
- @runCode()
1264
-
1265
- # --- Theme Change ---
1266
- @themeSelect.addEventListener 'change', (e) => @setTheme! e.target.value
1267
-
1268
- # --- Mode Toggle ---
1269
- @modeToggle.addEventListener 'click', =>
1270
- isDark = not isDark
1271
- @applyMode()
1272
- current = @themeSelect.value
1273
- if current is 'vs' or current is 'vs-dark'
1274
- defaultTheme = if isDark then 'vs-dark' else 'vs'
1275
- @themeSelect.value = defaultTheme
1276
- @setTheme! defaultTheme
1277
-
1278
- # --- Resizer ---
1279
- @resizer.addEventListener 'mousedown', (e) =>
1280
- isResizing = true
1281
- document.body.style.cursor = 'col-resize'
1282
- e.preventDefault()
1283
-
1284
- document.addEventListener 'mousemove', (e) =>
1285
- return unless isResizing
1286
- containerRect = @compilerContainer.getBoundingClientRect()
1287
- offsetX = e.clientX - containerRect.left
1288
- leftPercent = (offsetX / containerRect.width) * 100
1289
- leftPercent = Math.max 20, Math.min(80, leftPercent)
1290
- @editorLeft.style.flexBasis = "#{leftPercent}%"
1291
- @editorRight.style.flexBasis = "#{100 - leftPercent}%"
1292
-
1293
- document.addEventListener 'mouseup', =>
1294
- if isResizing
1295
- isResizing = false
1296
- document.body.style.cursor = 'default'
1297
-
1298
- # --- REPL Isolated Iframe ---
1299
- iframeEl = document.createElement 'iframe'
1300
- iframeEl.style.display = 'none'
1301
- document.body.appendChild iframeEl
1302
- replCtx = iframeEl.contentWindow
1303
- replCtx.console = console
1304
- replCtx.showSexp = false
1305
- replCtx.showTokens = false
1306
- replCtx.__reactiveVars = {}
1307
- replCtx.eval globalThis.getReactiveRuntime()
1308
-
1309
- # --- REPL Input Handling ---
1310
- replLineHeight = 24
1311
- replEditor.onDidChangeModelContent =>
1312
- lineCount = replEditor.getModel().getLineCount()
1313
- newHeight = Math.max replLineHeight, lineCount * replLineHeight
1314
- @replEditorContainer.style.height = "#{newHeight}px"
1315
- replEditor.layout()
1316
-
1317
- replEditor.onKeyDown (e) =>
1318
- if e.keyCode is m.KeyCode.Enter and not e.shiftKey
1319
- e.preventDefault()
1320
- e.stopPropagation()
1321
- inputLine = replEditor.getValue()
1322
- replEditor.setValue ''
1323
- @replEditorContainer.style.height = "#{replLineHeight}px"
1324
- replEditor.layout()
1325
- @handleLine inputLine if inputLine.trim()
1326
- else if e.keyCode is m.KeyCode.Enter and e.shiftKey
1327
- return
1328
- else if e.keyCode is m.KeyCode.UpArrow
1329
- e.preventDefault()
1330
- e.stopPropagation()
1331
- if historyIndex > 0
1332
- historyIndex--
1333
- replEditor.setValue replHistory[historyIndex] or ''
1334
- mdl = replEditor.getModel()
1335
- replEditor.setPosition lineNumber: 1, column: mdl.getLineMaxColumn(1)
1336
- else if e.keyCode is m.KeyCode.DownArrow
1337
- e.preventDefault()
1338
- e.stopPropagation()
1339
- if historyIndex < replHistory.length
1340
- historyIndex++
1341
- replEditor.setValue replHistory[historyIndex] or ''
1342
- mdl = replEditor.getModel()
1343
- replEditor.setPosition lineNumber: 1, column: mdl.getLineMaxColumn(1)
1344
-
1345
- # --- Hash Routing ---
1346
- window.addEventListener 'hashchange', =>
1347
- hash = window.location.hash.slice 1
1348
- @switchToTab hash if hash is 'compiler' or hash is 'repl'
1349
-
1350
- # --- Restore Theme ---
1351
- savedTheme = localStorage.getItem 'rip-repl-theme'
1352
- if savedTheme
1353
- @themeSelect.value = savedTheme
1354
- @setTheme! savedTheme
1355
- else
1356
- @setTheme! if isDark then 'vs-dark' else 'vs'
1357
-
1358
- # --- Initial Tab ---
1359
- initialTab = window.location.hash.slice 1
1360
- if initialTab is 'compiler' or initialTab is 'repl'
1361
- @switchToTab! initialTab
1362
- else
1363
- @compileCode()
1364
-
1365
- # --- Reveal Page ---
1366
- document.body.classList.add 'ready'
1367
-
1368
- # ==============================================================
1369
- # Render
1370
- # ==============================================================
1371
-
1372
- render
1373
- div
1374
- .header
1375
- .header-left
1376
- h1 ref: "titleEl"
1377
- .subtitle ref: "subtitleEl"
1378
- .header-right
1379
- button.mode-toggle ref: "modeToggle", title: "Toggle light/dark mode"
1380
- a.github-link ref: "githubLink", href: "https://github.com/shreeve/rip-lang", target: "_blank", title: "View on GitHub"
1381
- .tabs
1382
- button.tab.active ref: "tabCompiler", @click: @onTabCompiler, "Live Compiler"
1383
- button.tab ref: "tabRepl", @click: @onTabRepl, "REPL Console"
1384
- .content
1385
- .pane.active ref: "compilerPane"
1386
- .compiler-container ref: "compilerContainer"
1387
- .editor-pane.editor-left ref: "editorLeft"
1388
- .pane-header
1389
- span "Rip Source"
1390
- .pane-header-buttons
1391
- select.theme-select ref: "exampleSelect", title: "Load example"
1392
- select.theme-select ref: "themeSelect", title: "Editor theme"
1393
- button ref: "clearBtn", title: "Clear the editor", @click: @onClear, "Clear"
1394
- button.active ref: "runBtn", title: "Execute code (F5, Cmd+Enter)", @click: @onRun, "Run"
1395
- .editor-wrapper ref: "editorContainer"
1396
- .resizer ref: "resizer"
1397
- .editor-pane.editor-right ref: "editorRight"
1398
- .pane-header
1399
- span "Output"
1400
- .pane-header-buttons
1401
- button ref: "showTokensBtn", title: "Toggle token stream display", @click: @onToggleTokens, "Tokens"
1402
- button ref: "showSexpBtn", title: "Toggle S-expression display", @click: @onToggleSexp, "S-Expressions"
1403
- button ref: "showPreambleBtn", title: "Toggle preamble code display", @click: @onTogglePreamble, "Preamble"
1404
- button ref: "showTypesBtn", title: "Toggle .d.ts type declarations", @click: @onToggleTypes, "Types"
1405
- button ref: "showJSBtn", title: "Toggle JavaScript output", @click: @onToggleJS, "JS"
1406
- .editor-wrapper ref: "outputContainer"
1407
- .pane ref: "replPane"
1408
- .repl-container
1409
- .repl-output ref: "replOutput"
1410
- .repl-input-area
1411
- span.repl-prompt-text ref: "prompt", "rip>"
1412
- .repl-editor-container ref: "replEditorContainer"
1413
- '''
1414
- data:
1415
- title: 'Rip Playground'
1416
-
1417
- </script>
1418
- </body>
1419
- </html>