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