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