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