rip-lang 3.9.0 → 3.9.1

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