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