rip-lang 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/dist/rip.browser.js +2 -2
- package/docs/dist/rip.browser.min.js +1 -1
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/index.html +1574 -2
- package/docs/rip-square.png +0 -0
- package/package.json +1 -1
- package/docs/repl.html +0 -1079
package/docs/repl.html
DELETED
|
@@ -1,1079 +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 REPL - Interactive Browser Environment</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
|
-
}
|
|
25
|
-
|
|
26
|
-
body.ready {
|
|
27
|
-
opacity: 1;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.header {
|
|
31
|
-
background: #252526;
|
|
32
|
-
padding: 15px 20px;
|
|
33
|
-
border-bottom: 1px solid #3e3e42;
|
|
34
|
-
display: flex;
|
|
35
|
-
justify-content: space-between;
|
|
36
|
-
align-items: center;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.header-left {
|
|
40
|
-
display: flex;
|
|
41
|
-
flex-direction: column;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
h1 {
|
|
45
|
-
display: flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
gap: 12px;
|
|
48
|
-
font-size: 20px;
|
|
49
|
-
font-weight: 600;
|
|
50
|
-
color: #cccccc;
|
|
51
|
-
margin-bottom: 5px;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.logo {
|
|
55
|
-
height: 32px;
|
|
56
|
-
width: auto;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.subtitle {
|
|
60
|
-
font-size: 13px;
|
|
61
|
-
color: #858585;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.github-link {
|
|
65
|
-
color: #858585;
|
|
66
|
-
transition: color 0.2s;
|
|
67
|
-
align-self: flex-start;
|
|
68
|
-
margin-top: 4px;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.github-link:hover {
|
|
72
|
-
color: #ffffff;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.github-link svg {
|
|
76
|
-
width: 24px;
|
|
77
|
-
height: 24px;
|
|
78
|
-
fill: currentColor;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.tabs {
|
|
82
|
-
display: flex;
|
|
83
|
-
gap: 0;
|
|
84
|
-
background: #2d2d30;
|
|
85
|
-
border-bottom: 1px solid #3e3e42;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.tab {
|
|
89
|
-
padding: 12px 24px;
|
|
90
|
-
background: transparent;
|
|
91
|
-
border: none;
|
|
92
|
-
color: #858585;
|
|
93
|
-
cursor: pointer;
|
|
94
|
-
font-size: 13px;
|
|
95
|
-
font-weight: 500;
|
|
96
|
-
border-bottom: 2px solid transparent;
|
|
97
|
-
transition: all 0.2s;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.tab:hover {
|
|
101
|
-
color: #cccccc;
|
|
102
|
-
background: #2a2a2d;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.tab.active {
|
|
106
|
-
color: #ffffff;
|
|
107
|
-
border-bottom-color: #007acc;
|
|
108
|
-
background: #1e1e1e;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.content {
|
|
112
|
-
flex: 1;
|
|
113
|
-
display: flex;
|
|
114
|
-
flex-direction: column;
|
|
115
|
-
overflow: hidden;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.pane {
|
|
120
|
-
display: none;
|
|
121
|
-
flex: 1;
|
|
122
|
-
overflow: hidden;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.pane.active {
|
|
126
|
-
display: flex;
|
|
127
|
-
flex-direction: column;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/* REPL Styles */
|
|
131
|
-
.repl-container {
|
|
132
|
-
flex: 1;
|
|
133
|
-
display: flex;
|
|
134
|
-
flex-direction: column;
|
|
135
|
-
padding: 20px;
|
|
136
|
-
overflow: hidden;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.repl-output {
|
|
140
|
-
flex: 1;
|
|
141
|
-
overflow-y: auto;
|
|
142
|
-
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
143
|
-
font-size: 14px;
|
|
144
|
-
line-height: 1.6;
|
|
145
|
-
padding: 10px;
|
|
146
|
-
background: #1e1e1e;
|
|
147
|
-
border: 1px solid #3e3e42;
|
|
148
|
-
border-radius: 4px;
|
|
149
|
-
margin-bottom: 10px;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.repl-line {
|
|
153
|
-
margin-bottom: 8px;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.prompt {
|
|
157
|
-
color: #4ec9b0;
|
|
158
|
-
font-weight: 600;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
.prompt-continuation {
|
|
162
|
-
color: #666;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.result {
|
|
166
|
-
color: #ce9178;
|
|
167
|
-
margin-left: 20px;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.error {
|
|
171
|
-
color: #f48771;
|
|
172
|
-
margin-left: 20px;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.command-output {
|
|
176
|
-
color: #858585;
|
|
177
|
-
margin-left: 20px;
|
|
178
|
-
white-space: pre-wrap;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.repl-input-area {
|
|
182
|
-
display: flex;
|
|
183
|
-
align-items: center;
|
|
184
|
-
gap: 10px;
|
|
185
|
-
background: #252526;
|
|
186
|
-
padding: 10px;
|
|
187
|
-
border: 1px solid #3e3e42;
|
|
188
|
-
border-radius: 4px;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.repl-prompt-text {
|
|
192
|
-
color: #4ec9b0;
|
|
193
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
194
|
-
font-weight: 600;
|
|
195
|
-
font-size: 14px;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.repl-input {
|
|
199
|
-
flex: 1;
|
|
200
|
-
background: transparent;
|
|
201
|
-
border: none;
|
|
202
|
-
outline: none;
|
|
203
|
-
color: #d4d4d4;
|
|
204
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
205
|
-
font-size: 14px;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/* Compiler Styles */
|
|
209
|
-
.compiler-container {
|
|
210
|
-
flex: 1;
|
|
211
|
-
min-height: 0;
|
|
212
|
-
display: flex;
|
|
213
|
-
gap: 0;
|
|
214
|
-
background: #3e3e42;
|
|
215
|
-
padding: 20px;
|
|
216
|
-
position: relative;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.resizer {
|
|
220
|
-
width: 6px;
|
|
221
|
-
background: #3e3e42;
|
|
222
|
-
cursor: col-resize;
|
|
223
|
-
position: relative;
|
|
224
|
-
flex-shrink: 0;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.resizer:hover {
|
|
228
|
-
background: #007acc;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.resizer::after {
|
|
232
|
-
content: '';
|
|
233
|
-
position: absolute;
|
|
234
|
-
top: 50%;
|
|
235
|
-
left: 50%;
|
|
236
|
-
transform: translate(-50%, -50%);
|
|
237
|
-
width: 2px;
|
|
238
|
-
height: 40px;
|
|
239
|
-
background: #858585;
|
|
240
|
-
border-radius: 1px;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.editor-pane {
|
|
244
|
-
display: flex;
|
|
245
|
-
flex-direction: column;
|
|
246
|
-
background: #1e1e1e;
|
|
247
|
-
border-radius: 4px;
|
|
248
|
-
overflow: hidden;
|
|
249
|
-
min-height: 0;
|
|
250
|
-
flex: 1;
|
|
251
|
-
min-width: 200px;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
#editor-left {
|
|
255
|
-
flex-basis: 50%;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
#editor-right {
|
|
259
|
-
flex-basis: 50%;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.pane-header {
|
|
263
|
-
background: #2d2d30;
|
|
264
|
-
padding: 10px 15px;
|
|
265
|
-
border-bottom: 1px solid #3e3e42;
|
|
266
|
-
font-size: 13px;
|
|
267
|
-
font-weight: 600;
|
|
268
|
-
color: #cccccc;
|
|
269
|
-
display: flex;
|
|
270
|
-
justify-content: space-between;
|
|
271
|
-
align-items: center;
|
|
272
|
-
min-height: 49px;
|
|
273
|
-
box-sizing: border-box;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.pane-header-buttons {
|
|
277
|
-
display: flex;
|
|
278
|
-
gap: 8px;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.pane-header button {
|
|
282
|
-
background: #5a5a5d;
|
|
283
|
-
color: #ffffff;
|
|
284
|
-
border: none;
|
|
285
|
-
padding: 6px 12px;
|
|
286
|
-
border-radius: 3px;
|
|
287
|
-
font-size: 12px;
|
|
288
|
-
font-weight: 500;
|
|
289
|
-
cursor: pointer;
|
|
290
|
-
transition: background 0.2s;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
.pane-header button:hover {
|
|
294
|
-
background: #6e6e71;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
.pane-header button.active {
|
|
298
|
-
background: #007acc;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
.pane-header button.active:hover {
|
|
302
|
-
background: #005a9e;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.editor, .output-code {
|
|
306
|
-
flex: 1;
|
|
307
|
-
min-height: 0;
|
|
308
|
-
padding: 15px;
|
|
309
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
310
|
-
font-size: 14px;
|
|
311
|
-
line-height: 1.6;
|
|
312
|
-
background: #1e1e1e;
|
|
313
|
-
color: #d4d4d4;
|
|
314
|
-
border: none;
|
|
315
|
-
outline: none;
|
|
316
|
-
resize: none;
|
|
317
|
-
overflow: auto;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
.output-code {
|
|
321
|
-
background: #1e1e1e;
|
|
322
|
-
white-space: pre;
|
|
323
|
-
overflow: auto;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
/* Scrollbar styling */
|
|
328
|
-
::-webkit-scrollbar {
|
|
329
|
-
width: 10px;
|
|
330
|
-
height: 10px;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
::-webkit-scrollbar-track {
|
|
334
|
-
background: #1e1e1e;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
::-webkit-scrollbar-thumb {
|
|
338
|
-
background: #424242;
|
|
339
|
-
border-radius: 5px;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
::-webkit-scrollbar-thumb:hover {
|
|
343
|
-
background: #4e4e4e;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/* Welcome message */
|
|
347
|
-
.welcome {
|
|
348
|
-
color: #858585;
|
|
349
|
-
font-style: italic;
|
|
350
|
-
margin-bottom: 15px;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
.help-text {
|
|
354
|
-
color: #6a9955;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
.command {
|
|
358
|
-
color: #4fc1ff;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
.var-name {
|
|
362
|
-
color: #9cdcfe;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
.var-value {
|
|
366
|
-
color: #ce9178;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/* Syntax highlighting for JavaScript output */
|
|
370
|
-
.output-code .hl-keyword { color: #569cd6; }
|
|
371
|
-
.output-code .hl-string { color: #ce9178; }
|
|
372
|
-
.output-code .hl-number { color: #b5cea8; }
|
|
373
|
-
.output-code .hl-comment { color: #6a9955; font-style: italic; }
|
|
374
|
-
.output-code .hl-function { color: #dcdcaa; }
|
|
375
|
-
.output-code .hl-operator { color: #d4d4d4; }
|
|
376
|
-
</style>
|
|
377
|
-
</head>
|
|
378
|
-
<body>
|
|
379
|
-
<div class="header">
|
|
380
|
-
<div class="header-left">
|
|
381
|
-
<h1>
|
|
382
|
-
<img src="rip.svg" alt="Rip" class="logo" style="background-color: #fff; border-radius: 5px; padding: 4px;">
|
|
383
|
-
Rip Browser REPL
|
|
384
|
-
</h1>
|
|
385
|
-
<div class="subtitle">Interactive environment with REPL console and live compiler</div>
|
|
386
|
-
</div>
|
|
387
|
-
<a href="https://github.com/shreeve/rip-lang" target="_blank" class="github-link" title="View on GitHub">
|
|
388
|
-
<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>
|
|
389
|
-
</a>
|
|
390
|
-
</div>
|
|
391
|
-
|
|
392
|
-
<div class="tabs">
|
|
393
|
-
<button class="tab active" data-tab="compiler">Live Compiler</button>
|
|
394
|
-
<button class="tab" data-tab="repl">REPL Console</button>
|
|
395
|
-
</div>
|
|
396
|
-
|
|
397
|
-
<div class="content">
|
|
398
|
-
<!-- REPL Pane -->
|
|
399
|
-
<div class="pane" id="repl-pane">
|
|
400
|
-
<div class="repl-container">
|
|
401
|
-
<div class="repl-output" id="repl-output">
|
|
402
|
-
<div class="welcome">
|
|
403
|
-
<strong>Rip REPL</strong> - Interactive Browser Environment<br>
|
|
404
|
-
Type <span class="command">.help</span> for commands, try Rip expressions!
|
|
405
|
-
</div>
|
|
406
|
-
</div>
|
|
407
|
-
<div class="repl-input-area">
|
|
408
|
-
<span class="repl-prompt-text" id="prompt">rip></span>
|
|
409
|
-
<input type="text" class="repl-input" id="repl-input" placeholder="Enter Rip code..." autofocus>
|
|
410
|
-
</div>
|
|
411
|
-
</div>
|
|
412
|
-
</div>
|
|
413
|
-
|
|
414
|
-
<!-- Compiler Pane -->
|
|
415
|
-
<div class="pane active" id="compiler-pane">
|
|
416
|
-
<div class="compiler-container">
|
|
417
|
-
<div class="editor-pane" id="editor-left">
|
|
418
|
-
<div class="pane-header">
|
|
419
|
-
<span>Rip Source</span>
|
|
420
|
-
<div class="pane-header-buttons">
|
|
421
|
-
<button id="clear-btn" title="Clear the editor">Clear</button>
|
|
422
|
-
<button class="active" id="run-btn" title="Execute code and output to browser console (F5, Cmd+Enter, Ctrl+Enter)">Run</button>
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
425
|
-
<textarea class="editor" id="editor" spellcheck="false"># Rip code - edit me!
|
|
426
|
-
def fibonacci(n)
|
|
427
|
-
if n <= 1
|
|
428
|
-
n
|
|
429
|
-
else
|
|
430
|
-
fibonacci(n - 1) + fibonacci(n - 2)
|
|
431
|
-
|
|
432
|
-
# Try heregex
|
|
433
|
-
pattern = ///
|
|
434
|
-
^ \\d+ # digits
|
|
435
|
-
\\s* # space
|
|
436
|
-
[a-z]+ # letters
|
|
437
|
-
$
|
|
438
|
-
///i
|
|
439
|
-
|
|
440
|
-
# Try regex features
|
|
441
|
-
email = "user@example.com"
|
|
442
|
-
domain = email[/@(.+)$/, 1]
|
|
443
|
-
|
|
444
|
-
console.log "Fib(10):", fibonacci(10)
|
|
445
|
-
console.log "Domain:", domain</textarea>
|
|
446
|
-
</div>
|
|
447
|
-
|
|
448
|
-
<div class="resizer" id="resizer"></div>
|
|
449
|
-
|
|
450
|
-
<div class="editor-pane" id="editor-right">
|
|
451
|
-
<div class="pane-header">
|
|
452
|
-
<span>Generated JavaScript</span>
|
|
453
|
-
<div class="pane-header-buttons">
|
|
454
|
-
<button id="show-sexp" title="Toggle S-expression display">S-Expressions</button>
|
|
455
|
-
<button id="show-tokens" title="Toggle token stream display">Tokens</button>
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
|
-
<pre class="output-code" id="output"></pre>
|
|
459
|
-
</div>
|
|
460
|
-
</div>
|
|
461
|
-
</div>
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
<script type="module">
|
|
465
|
-
// Add timestamp to bust browser cache for ES modules
|
|
466
|
-
const timestamp = Date.now();
|
|
467
|
-
const module = await import(`./dist/rip.browser.min.js?v=${timestamp}`);
|
|
468
|
-
const { compile, formatSExpr, VERSION, BUILD_DATE } = module;
|
|
469
|
-
|
|
470
|
-
// Make compile and formatSExpr available globally for the script
|
|
471
|
-
window.compile = compile;
|
|
472
|
-
window.toSexpr = formatSExpr;
|
|
473
|
-
|
|
474
|
-
// Update subtitle with version and build time
|
|
475
|
-
// Convert GMT timestamp to local time for display
|
|
476
|
-
const buildDate = BUILD_DATE; // GMT timestamp from build
|
|
477
|
-
const localBuildDate = new Date(buildDate.replace('@', 'T').replace('GMT', 'Z'))
|
|
478
|
-
.toLocaleString(undefined, {
|
|
479
|
-
year: 'numeric', month: 'short', day: 'numeric',
|
|
480
|
-
hour: '2-digit', minute: '2-digit', timeZoneName: 'short'
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
document.querySelector('.subtitle').textContent =
|
|
484
|
-
`Interactive environment • Version ${VERSION} • Built ${localBuildDate}`;
|
|
485
|
-
|
|
486
|
-
// ========================================================================
|
|
487
|
-
// DOM Element References (must be before functions that use them)
|
|
488
|
-
// ========================================================================
|
|
489
|
-
|
|
490
|
-
const editor = document.getElementById('editor');
|
|
491
|
-
const output = document.getElementById('output');
|
|
492
|
-
const showSexpBtn = document.getElementById('show-sexp');
|
|
493
|
-
const showTokensBtn = document.getElementById('show-tokens');
|
|
494
|
-
const replOutput = document.getElementById('repl-output');
|
|
495
|
-
const replInput = document.getElementById('repl-input');
|
|
496
|
-
const promptSpan = document.getElementById('prompt');
|
|
497
|
-
const resizer = document.getElementById('resizer');
|
|
498
|
-
const leftPane = document.getElementById('editor-left');
|
|
499
|
-
const rightPane = document.getElementById('editor-right');
|
|
500
|
-
|
|
501
|
-
// ========================================================================
|
|
502
|
-
// Compiler Implementation (must be before tab switching)
|
|
503
|
-
// ========================================================================
|
|
504
|
-
|
|
505
|
-
// Track toggle state
|
|
506
|
-
let showSexp = false;
|
|
507
|
-
let showTokens = false;
|
|
508
|
-
|
|
509
|
-
// Strip utility helper functions for cleaner display
|
|
510
|
-
function stripHelpers(code) {
|
|
511
|
-
// Remove helper function definitions (slice, modulo, toSearchable)
|
|
512
|
-
code = code.replace(/^const slice = \[\]\.slice;\n/m, '');
|
|
513
|
-
code = code.replace(/^const modulo = \(n, d\) => \{[^}]+\};\n/m, '');
|
|
514
|
-
code = code.replace(/^const toSearchable = \(v, allowNewlines\) => \{[\s\S]+?\n\};\n/m, '');
|
|
515
|
-
|
|
516
|
-
// Remove empty lines at the start
|
|
517
|
-
code = code.replace(/^\n+/, '');
|
|
518
|
-
|
|
519
|
-
return code;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Simple syntax highlighter for JavaScript
|
|
523
|
-
function highlightJS(code) {
|
|
524
|
-
return code
|
|
525
|
-
// Keywords
|
|
526
|
-
.replace(/\\b(def|function|async|const|let|var|if|else|return|while|for|class|extends|super|new|this|try|catch|finally|throw|switch|case|default|break|continue)\\b/g,
|
|
527
|
-
'<span class="hl-keyword">$1</span>')
|
|
528
|
-
// Strings
|
|
529
|
-
.replace(/("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*'|`(?:[^`\\\\]|\\\\.)*`)/g,
|
|
530
|
-
'<span class="hl-string">$1</span>')
|
|
531
|
-
// Numbers
|
|
532
|
-
.replace(/\\b(\\d+\\.?\\d*|0x[0-9a-fA-F]+|0b[01]+|0o[0-7]+)\\b/g,
|
|
533
|
-
'<span class="hl-number">$1</span>')
|
|
534
|
-
// Comments
|
|
535
|
-
.replace(/(\/\/.*$|\/\*[\s\S]*?\*\/)/gm,
|
|
536
|
-
'<span class="hl-comment">$1</span>')
|
|
537
|
-
// Function names (simple: word before parenthesis)
|
|
538
|
-
.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)(?=\s*\()/g,
|
|
539
|
-
'<span class="hl-function">$1</span>');
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function compileCode() {
|
|
543
|
-
try {
|
|
544
|
-
const source = editor.value;
|
|
545
|
-
const result = compile(source);
|
|
546
|
-
|
|
547
|
-
let outputText = '';
|
|
548
|
-
|
|
549
|
-
if (showTokens) {
|
|
550
|
-
result.tokens.forEach(t => {
|
|
551
|
-
outputText += `${t[0].padEnd(12)} ${JSON.stringify(t[1])}\n`;
|
|
552
|
-
});
|
|
553
|
-
outputText += '\n';
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (showSexp) {
|
|
557
|
-
outputText += window.toSexpr ? window.toSexpr(result.sexpr, 0, true) : JSON.stringify(result.sexpr, null, 1);
|
|
558
|
-
outputText += '\n\n';
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// JavaScript output with syntax highlighting (strip helpers for cleaner display)
|
|
562
|
-
const cleanCode = stripHelpers(result.code);
|
|
563
|
-
const highlightedCode = highlightJS(cleanCode);
|
|
564
|
-
output.innerHTML = outputText.replace(/</g, '<').replace(/>/g, '>') + highlightedCode;
|
|
565
|
-
} catch (error) {
|
|
566
|
-
output.textContent = `Error: ${error.message}`;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// ========================================================================
|
|
571
|
-
// Tab Switching
|
|
572
|
-
// ========================================================================
|
|
573
|
-
|
|
574
|
-
function switchToTab(tabName) {
|
|
575
|
-
// Update URL hash
|
|
576
|
-
window.location.hash = tabName;
|
|
577
|
-
|
|
578
|
-
// Update active tab
|
|
579
|
-
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
580
|
-
document.querySelector(`.tab[data-tab="${tabName}"]`).classList.add('active');
|
|
581
|
-
|
|
582
|
-
// Update active pane
|
|
583
|
-
document.querySelectorAll('.pane').forEach(p => p.classList.remove('active'));
|
|
584
|
-
document.getElementById(`${tabName}-pane`).classList.add('active');
|
|
585
|
-
|
|
586
|
-
// Focus appropriate input and run setup
|
|
587
|
-
if (tabName === 'repl') {
|
|
588
|
-
replInput.focus();
|
|
589
|
-
} else if (tabName === 'compiler') {
|
|
590
|
-
compileCode(); // Update compiler output
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
document.querySelectorAll('.tab').forEach(tab => {
|
|
595
|
-
tab.addEventListener('click', () => switchToTab(tab.dataset.tab));
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
// Handle browser back/forward
|
|
599
|
-
window.addEventListener('hashchange', () => {
|
|
600
|
-
const hash = window.location.hash.slice(1);
|
|
601
|
-
if (['compiler', 'repl'].includes(hash)) {
|
|
602
|
-
switchToTab(hash);
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
// Initialize from URL hash on page load (default to compiler)
|
|
607
|
-
const initialTab = window.location.hash.slice(1);
|
|
608
|
-
if (['compiler', 'repl'].includes(initialTab)) {
|
|
609
|
-
switchToTab(initialTab);
|
|
610
|
-
} else {
|
|
611
|
-
// No hash - compiler is default
|
|
612
|
-
compileCode();
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Show page now that everything is initialized (prevents flicker)
|
|
616
|
-
document.body.classList.add('ready');
|
|
617
|
-
|
|
618
|
-
// ========================================================================
|
|
619
|
-
// Resizable Panes (Live Compiler)
|
|
620
|
-
// ========================================================================
|
|
621
|
-
|
|
622
|
-
if (resizer && leftPane && rightPane) {
|
|
623
|
-
let isResizing = false;
|
|
624
|
-
|
|
625
|
-
resizer.addEventListener('mousedown', (e) => {
|
|
626
|
-
isResizing = true;
|
|
627
|
-
document.body.style.cursor = 'col-resize';
|
|
628
|
-
e.preventDefault();
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
document.addEventListener('mousemove', (e) => {
|
|
632
|
-
if (!isResizing) return;
|
|
633
|
-
|
|
634
|
-
const container = document.querySelector('.compiler-container');
|
|
635
|
-
const containerRect = container.getBoundingClientRect();
|
|
636
|
-
const offsetX = e.clientX - containerRect.left;
|
|
637
|
-
const containerWidth = containerRect.width;
|
|
638
|
-
|
|
639
|
-
// Calculate percentage (20% min, 80% max for each side)
|
|
640
|
-
let leftPercent = (offsetX / containerWidth) * 100;
|
|
641
|
-
leftPercent = Math.max(20, Math.min(80, leftPercent));
|
|
642
|
-
|
|
643
|
-
leftPane.style.flexBasis = `${leftPercent}%`;
|
|
644
|
-
rightPane.style.flexBasis = `${100 - leftPercent}%`;
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
document.addEventListener('mouseup', () => {
|
|
648
|
-
if (isResizing) {
|
|
649
|
-
isResizing = false;
|
|
650
|
-
document.body.style.cursor = 'default';
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// ========================================================================
|
|
656
|
-
// REPL Implementation
|
|
657
|
-
// ========================================================================
|
|
658
|
-
|
|
659
|
-
let replHistory = [];
|
|
660
|
-
let historyIndex = -1;
|
|
661
|
-
let replBuffer = '';
|
|
662
|
-
let reactiveVars = new Set(); // Track reactive variables across evaluations
|
|
663
|
-
|
|
664
|
-
// Create isolated iframe context for REPL (like vm.createContext in Node)
|
|
665
|
-
const iframe = document.createElement('iframe');
|
|
666
|
-
iframe.style.display = 'none';
|
|
667
|
-
document.body.appendChild(iframe);
|
|
668
|
-
const replContext = iframe.contentWindow;
|
|
669
|
-
|
|
670
|
-
// Add builtins to iframe context
|
|
671
|
-
replContext.console = console;
|
|
672
|
-
replContext.showSexp = false;
|
|
673
|
-
replContext.showTokens = false;
|
|
674
|
-
replContext.__reactiveVars = {}; // Store reactive objects persistently
|
|
675
|
-
|
|
676
|
-
// Inject reactive runtime into iframe context
|
|
677
|
-
(function injectReactiveRuntime() {
|
|
678
|
-
const ctx = replContext;
|
|
679
|
-
ctx.__currentEffect = null;
|
|
680
|
-
ctx.__pendingEffects = new Set();
|
|
681
|
-
|
|
682
|
-
ctx.__state = function(v) {
|
|
683
|
-
const subs = new Set();
|
|
684
|
-
let notifying = false, locked = false, dead = false;
|
|
685
|
-
const s = {
|
|
686
|
-
get value() { if (dead) return v; if (ctx.__currentEffect) { subs.add(ctx.__currentEffect); ctx.__currentEffect.dependencies.add(subs); } return v; },
|
|
687
|
-
set value(n) {
|
|
688
|
-
if (dead || locked || n === v || notifying) return;
|
|
689
|
-
v = n;
|
|
690
|
-
notifying = true;
|
|
691
|
-
for (const sub of subs) if (sub.markDirty) sub.markDirty();
|
|
692
|
-
for (const sub of subs) if (!sub.markDirty) ctx.__pendingEffects.add(sub);
|
|
693
|
-
const fx = [...ctx.__pendingEffects]; ctx.__pendingEffects.clear();
|
|
694
|
-
for (const e of fx) e.run();
|
|
695
|
-
notifying = false;
|
|
696
|
-
},
|
|
697
|
-
read() { return v; },
|
|
698
|
-
lock() { locked = true; return s; },
|
|
699
|
-
free() { subs.clear(); return s; },
|
|
700
|
-
kill() { dead = true; subs.clear(); return v; },
|
|
701
|
-
valueOf() { return this.value; },
|
|
702
|
-
toString() { return String(this.value); },
|
|
703
|
-
[Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
|
|
704
|
-
};
|
|
705
|
-
return s;
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
ctx.__computed = function(fn) {
|
|
709
|
-
let v, dirty = true, locked = false, dead = false;
|
|
710
|
-
const subs = new Set();
|
|
711
|
-
const c = {
|
|
712
|
-
dependencies: new Set(),
|
|
713
|
-
markDirty() {
|
|
714
|
-
if (dead || locked || dirty) return;
|
|
715
|
-
dirty = true;
|
|
716
|
-
for (const s of subs) if (s.markDirty) s.markDirty();
|
|
717
|
-
for (const s of subs) if (!s.markDirty) ctx.__pendingEffects.add(s);
|
|
718
|
-
},
|
|
719
|
-
get value() {
|
|
720
|
-
if (dead) return v;
|
|
721
|
-
if (ctx.__currentEffect) { subs.add(ctx.__currentEffect); ctx.__currentEffect.dependencies.add(subs); }
|
|
722
|
-
if (dirty && !locked) {
|
|
723
|
-
for (const d of c.dependencies) d.delete(c); c.dependencies.clear();
|
|
724
|
-
const prev = ctx.__currentEffect; ctx.__currentEffect = c;
|
|
725
|
-
try { v = fn(); } finally { ctx.__currentEffect = prev; }
|
|
726
|
-
dirty = false;
|
|
727
|
-
}
|
|
728
|
-
return v;
|
|
729
|
-
},
|
|
730
|
-
read() { return dead ? v : c.value; },
|
|
731
|
-
lock() { locked = true; c.value; return c; },
|
|
732
|
-
free() { for (const d of c.dependencies) d.delete(c); c.dependencies.clear(); subs.clear(); return c; },
|
|
733
|
-
kill() { dead = true; const result = v; c.free(); return result; },
|
|
734
|
-
valueOf() { return this.value; },
|
|
735
|
-
toString() { return String(this.value); },
|
|
736
|
-
[Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
|
|
737
|
-
};
|
|
738
|
-
return c;
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
ctx.__effect = function(fn) {
|
|
742
|
-
const e = {
|
|
743
|
-
dependencies: new Set(),
|
|
744
|
-
run() {
|
|
745
|
-
for (const d of e.dependencies) d.delete(e); e.dependencies.clear();
|
|
746
|
-
const prev = ctx.__currentEffect; ctx.__currentEffect = e;
|
|
747
|
-
try { fn(); } finally { ctx.__currentEffect = prev; }
|
|
748
|
-
},
|
|
749
|
-
free() { for (const d of e.dependencies) d.delete(e); e.dependencies.clear(); }
|
|
750
|
-
};
|
|
751
|
-
e.run();
|
|
752
|
-
return () => e.free();
|
|
753
|
-
};
|
|
754
|
-
|
|
755
|
-
ctx.__batch = function(fn) { fn(); };
|
|
756
|
-
ctx.__readonly = function(v) { return Object.freeze({ value: v }); };
|
|
757
|
-
})();
|
|
758
|
-
|
|
759
|
-
function addOutput(content, className = '') {
|
|
760
|
-
const line = document.createElement('div');
|
|
761
|
-
line.className = `repl-line ${className}`;
|
|
762
|
-
line.innerHTML = content;
|
|
763
|
-
replOutput.appendChild(line);
|
|
764
|
-
replOutput.scrollTop = replOutput.scrollHeight;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
function evaluateRip(code) {
|
|
768
|
-
try {
|
|
769
|
-
// Pass reactiveVars to compiler so it knows which vars need .value access
|
|
770
|
-
const result = compile(code, { reactiveVars, skipReactiveRuntime: true });
|
|
771
|
-
let js = result.code;
|
|
772
|
-
|
|
773
|
-
// Track new reactive variables
|
|
774
|
-
if (result.reactiveVars) {
|
|
775
|
-
for (const v of result.reactiveVars) {
|
|
776
|
-
reactiveVars.add(v);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// REPL strategy: Strip let/const declarations entirely
|
|
781
|
-
// Assignments will create properties on iframe's window (global scope)
|
|
782
|
-
// This allows variables to persist between eval() calls
|
|
783
|
-
|
|
784
|
-
// Remove: let x, y, z;\n
|
|
785
|
-
js = js.replace(/^let\s+[^;]+;\s*\n+/m, '');
|
|
786
|
-
|
|
787
|
-
// Transform reactive declarations to persist in __reactiveVars
|
|
788
|
-
// const x = __state(...) → x = __reactiveVars.x ?? (__reactiveVars.x = __state(...))
|
|
789
|
-
js = js.replace(
|
|
790
|
-
/^const\s+(\w+)\s*=\s*((?:__state|__computed|__effect)\(.+\));?$/gm,
|
|
791
|
-
(match, varName, rhs) => {
|
|
792
|
-
return `${varName} = __reactiveVars['${varName}'] ?? (__reactiveVars['${varName}'] = ${rhs});`;
|
|
793
|
-
}
|
|
794
|
-
);
|
|
795
|
-
|
|
796
|
-
// Remove remaining const (but keep the assignment) for non-reactive
|
|
797
|
-
js = js.replace(/^const\s+(\w+)\s*=/gm, '$1 =');
|
|
798
|
-
|
|
799
|
-
// Evaluate in iframe context - assignments create globals
|
|
800
|
-
const evalResult = replContext.eval(js);
|
|
801
|
-
|
|
802
|
-
// Store in _ (unwrap reactive values)
|
|
803
|
-
if (evalResult !== undefined) {
|
|
804
|
-
replContext._ = evalResult;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
return { success: true, value: evalResult, result };
|
|
808
|
-
} catch (error) {
|
|
809
|
-
return { success: false, error: error.message };
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
function handleCommand(cmd) {
|
|
814
|
-
switch (cmd) {
|
|
815
|
-
case '.help':
|
|
816
|
-
addOutput(`
|
|
817
|
-
<span class="help-text">Rip Browser REPL Commands:</span>
|
|
818
|
-
|
|
819
|
-
<span class="command">.help</span> - Show this help
|
|
820
|
-
<span class="command">.clear</span> - Clear output
|
|
821
|
-
<span class="command">.vars</span> - Show variables
|
|
822
|
-
<span class="command">.sexp</span> - Toggle s-expression display
|
|
823
|
-
<span class="command">.tokens</span> - Toggle token display
|
|
824
|
-
|
|
825
|
-
<span class="help-text">Features:</span>
|
|
826
|
-
- Variables persist across evaluations
|
|
827
|
-
- Previous result in <span class="var-name">_</span> variable
|
|
828
|
-
- Multi-line input (type incomplete expression)
|
|
829
|
-
- All Rip features: heregex, regex+, classes, etc.
|
|
830
|
-
`, 'command-output');
|
|
831
|
-
break;
|
|
832
|
-
|
|
833
|
-
case '.clear':
|
|
834
|
-
replOutput.innerHTML = '';
|
|
835
|
-
// Reset reactive tracking
|
|
836
|
-
reactiveVars.clear();
|
|
837
|
-
replContext.__reactiveVars = {};
|
|
838
|
-
// Clear all user-defined globals in iframe
|
|
839
|
-
const builtinsToKeep = ['console', 'showSexp', 'showTokens', 'eval', 'window', 'document',
|
|
840
|
-
'location', 'navigator', 'self', 'top', 'parent', 'frames', '__state', '__computed',
|
|
841
|
-
'__effect', '__batch', '__readonly', '__currentEffect', '__pendingEffects', '__reactiveVars'];
|
|
842
|
-
for (const key of Object.keys(replContext)) {
|
|
843
|
-
if (!builtinsToKeep.includes(key) && !key.startsWith('__')) {
|
|
844
|
-
try { delete replContext[key]; } catch {}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
addOutput('<div class="welcome">Output and context cleared.</div>');
|
|
848
|
-
break;
|
|
849
|
-
|
|
850
|
-
case '.vars':
|
|
851
|
-
const builtins = ['console', 'showSexp', 'showTokens', 'eval', 'window', 'document',
|
|
852
|
-
'location', 'navigator', 'self', 'top', 'parent', 'frames', '__state', '__computed',
|
|
853
|
-
'__effect', '__batch', '__readonly', '__currentEffect', '__pendingEffects', '__reactiveVars'];
|
|
854
|
-
const vars = Object.keys(replContext).filter(k =>
|
|
855
|
-
!builtins.includes(k) && !k.startsWith('__') || k === '_'
|
|
856
|
-
);
|
|
857
|
-
if (vars.length === 0 || (vars.length === 1 && vars[0] === '_' && replContext._ === undefined)) {
|
|
858
|
-
addOutput('<span class="help-text">No variables defined</span>', 'command-output');
|
|
859
|
-
} else {
|
|
860
|
-
let output = '<span class="help-text">Variables:</span>\\n';
|
|
861
|
-
vars.forEach(v => {
|
|
862
|
-
try {
|
|
863
|
-
let val = replContext[v];
|
|
864
|
-
let typeIndicator = '=';
|
|
865
|
-
// Check if it's a reactive value
|
|
866
|
-
if (val && typeof val === 'object' && 'value' in val) {
|
|
867
|
-
if (typeof val.markDirty === 'function') {
|
|
868
|
-
typeIndicator = '<span style="color:#c586c0">~=</span>'; // computed
|
|
869
|
-
} else if (typeof val === 'function') {
|
|
870
|
-
typeIndicator = '<span style="color:#c586c0">~></span>'; // effect
|
|
871
|
-
} else {
|
|
872
|
-
typeIndicator = '<span style="color:#c586c0">:=</span>'; // state
|
|
873
|
-
}
|
|
874
|
-
val = val.value;
|
|
875
|
-
}
|
|
876
|
-
const formatted = formatValue(val);
|
|
877
|
-
output += ` <span class="var-name">${v}</span> ${typeIndicator} <span class="var-value">${escapeHtml(formatted)}</span>\\n`;
|
|
878
|
-
} catch {
|
|
879
|
-
output += ` <span class="var-name">${v}</span> = <span class="var-value">[object]</span>\\n`;
|
|
880
|
-
}
|
|
881
|
-
});
|
|
882
|
-
addOutput(output, 'command-output');
|
|
883
|
-
}
|
|
884
|
-
break;
|
|
885
|
-
|
|
886
|
-
case '.sexp':
|
|
887
|
-
replContext.showSexp = !replContext.showSexp;
|
|
888
|
-
addOutput(`S-expression display: ${replContext.showSexp ? 'ON' : 'OFF'}`, 'command-output');
|
|
889
|
-
break;
|
|
890
|
-
|
|
891
|
-
case '.tokens':
|
|
892
|
-
replContext.showTokens = !replContext.showTokens;
|
|
893
|
-
addOutput(`Token display: ${replContext.showTokens ? 'ON' : 'OFF'}`, 'command-output');
|
|
894
|
-
break;
|
|
895
|
-
|
|
896
|
-
default:
|
|
897
|
-
addOutput(`Unknown command: ${cmd}`, 'error');
|
|
898
|
-
addOutput('Type .help for available commands', 'help-text');
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
function handleLine(line) {
|
|
903
|
-
// Add to history
|
|
904
|
-
if (line.trim() && (replHistory.length === 0 || replHistory[replHistory.length - 1] !== line)) {
|
|
905
|
-
replHistory.push(line);
|
|
906
|
-
}
|
|
907
|
-
historyIndex = replHistory.length;
|
|
908
|
-
|
|
909
|
-
// Show input
|
|
910
|
-
const promptText = replBuffer ? '....>' : 'rip>';
|
|
911
|
-
addOutput(`<span class="prompt">${promptText}</span> ${escapeHtml(line)}`);
|
|
912
|
-
|
|
913
|
-
// Handle commands
|
|
914
|
-
if (line.startsWith('.')) {
|
|
915
|
-
handleCommand(line);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Add to buffer
|
|
920
|
-
replBuffer = replBuffer ? replBuffer + '\\n' + line : line;
|
|
921
|
-
|
|
922
|
-
// Try to evaluate
|
|
923
|
-
const evalResult = evaluateRip(replBuffer);
|
|
924
|
-
|
|
925
|
-
if (evalResult.success) {
|
|
926
|
-
// Show debug info if enabled
|
|
927
|
-
if (replContext.showTokens) {
|
|
928
|
-
let tokenOutput = '';
|
|
929
|
-
evalResult.result.tokens.forEach(t => {
|
|
930
|
-
tokenOutput += `${t[0].padEnd(12)} ${JSON.stringify(t[1])}<br>`;
|
|
931
|
-
});
|
|
932
|
-
addOutput(tokenOutput, 'command-output');
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
if (replContext.showSexp) {
|
|
936
|
-
const sexp = window.toSexpr ? window.toSexpr(evalResult.result.sexpr, 0, true) : JSON.stringify(evalResult.result.sexpr, null, 1);
|
|
937
|
-
const formatted = sexp.replace(/\n/g, '<br>').replace(/ /g, ' ');
|
|
938
|
-
addOutput(`${formatted}`, 'command-output');
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// Show result (unwrap reactive values)
|
|
942
|
-
if (evalResult.value !== undefined) {
|
|
943
|
-
let displayValue = evalResult.value;
|
|
944
|
-
// Unwrap reactive objects to show their .value
|
|
945
|
-
if (displayValue && typeof displayValue === 'object' && 'value' in displayValue) {
|
|
946
|
-
displayValue = displayValue.value;
|
|
947
|
-
}
|
|
948
|
-
const formatted = formatValue(displayValue);
|
|
949
|
-
addOutput(`<span class="result">→ ${escapeHtml(formatted)}</span>`);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
replBuffer = '';
|
|
953
|
-
promptSpan.textContent = 'rip>';
|
|
954
|
-
} else if (evalResult.error.includes('Unexpected end')) {
|
|
955
|
-
// Incomplete - wait for more input
|
|
956
|
-
promptSpan.textContent = '....>';
|
|
957
|
-
} else {
|
|
958
|
-
// Real error
|
|
959
|
-
addOutput(`<span class="error">✗ ${escapeHtml(evalResult.error)}</span>`);
|
|
960
|
-
replBuffer = '';
|
|
961
|
-
promptSpan.textContent = 'rip>';
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
function escapeHtml(str) {
|
|
966
|
-
return String(str)
|
|
967
|
-
.replace(/&/g, '&')
|
|
968
|
-
.replace(/</g, '<')
|
|
969
|
-
.replace(/>/g, '>')
|
|
970
|
-
.replace(/"/g, '"');
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// Format values for display (like Node's util.inspect)
|
|
974
|
-
function formatValue(val) {
|
|
975
|
-
if (val === null) return 'null';
|
|
976
|
-
if (val === undefined) return 'undefined';
|
|
977
|
-
if (val instanceof RegExp) return val.toString();
|
|
978
|
-
if (typeof val === 'function') return val.toString();
|
|
979
|
-
if (typeof val === 'string') return JSON.stringify(val);
|
|
980
|
-
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
|
981
|
-
if (Array.isArray(val)) {
|
|
982
|
-
try { return JSON.stringify(val); } catch { return '[Array]'; }
|
|
983
|
-
}
|
|
984
|
-
if (typeof val === 'object') {
|
|
985
|
-
try { return JSON.stringify(val, null, 2); } catch { return '[Object]'; }
|
|
986
|
-
}
|
|
987
|
-
return String(val);
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// REPL input handling
|
|
991
|
-
replInput.addEventListener('keydown', (e) => {
|
|
992
|
-
if (e.key === 'Enter') {
|
|
993
|
-
const line = e.target.value;
|
|
994
|
-
e.target.value = '';
|
|
995
|
-
handleLine(line);
|
|
996
|
-
} else if (e.key === 'ArrowUp') {
|
|
997
|
-
e.preventDefault();
|
|
998
|
-
if (historyIndex > 0) {
|
|
999
|
-
historyIndex--;
|
|
1000
|
-
e.target.value = replHistory[historyIndex] || '';
|
|
1001
|
-
}
|
|
1002
|
-
} else if (e.key === 'ArrowDown') {
|
|
1003
|
-
e.preventDefault();
|
|
1004
|
-
if (historyIndex < replHistory.length) {
|
|
1005
|
-
historyIndex++;
|
|
1006
|
-
e.target.value = replHistory[historyIndex] || '';
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
// ========================================================================
|
|
1012
|
-
// Compiler Event Handlers
|
|
1013
|
-
// ========================================================================
|
|
1014
|
-
|
|
1015
|
-
// Auto-compile on changes
|
|
1016
|
-
editor.addEventListener('input', compileCode);
|
|
1017
|
-
|
|
1018
|
-
// Toggle buttons for S-Expressions and Tokens
|
|
1019
|
-
showSexpBtn.addEventListener('click', () => {
|
|
1020
|
-
showSexp = !showSexp;
|
|
1021
|
-
showSexpBtn.classList.toggle('active', showSexp);
|
|
1022
|
-
compileCode();
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
showTokensBtn.addEventListener('click', () => {
|
|
1026
|
-
showTokens = !showTokens;
|
|
1027
|
-
showTokensBtn.classList.toggle('active', showTokens);
|
|
1028
|
-
compileCode();
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
// Clear button - clears the Rip source editor
|
|
1032
|
-
document.getElementById('clear-btn').addEventListener('click', () => {
|
|
1033
|
-
editor.value = '';
|
|
1034
|
-
compileCode();
|
|
1035
|
-
editor.focus();
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
// Run button - executes the Rip code and outputs to console
|
|
1039
|
-
function runCode() {
|
|
1040
|
-
try {
|
|
1041
|
-
const source = editor.value;
|
|
1042
|
-
const result = compile(source);
|
|
1043
|
-
const js = result.code;
|
|
1044
|
-
|
|
1045
|
-
// Execute the compiled JavaScript and log to console
|
|
1046
|
-
console.clear();
|
|
1047
|
-
console.log('%c=== Rip Code Execution ===', 'color: #007acc; font-weight: bold');
|
|
1048
|
-
eval(js);
|
|
1049
|
-
console.log('%c=== Execution Complete ===', 'color: #4ec9b0; font-weight: bold');
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
console.error('Execution Error:', error);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
document.getElementById('run-btn').addEventListener('click', runCode);
|
|
1056
|
-
|
|
1057
|
-
// Keyboard shortcuts for Run button
|
|
1058
|
-
// F5, Cmd+Enter (Mac), Ctrl+Enter (Windows/Linux)
|
|
1059
|
-
document.addEventListener('keydown', (e) => {
|
|
1060
|
-
// F5 - Run code
|
|
1061
|
-
if (e.key === 'F5') {
|
|
1062
|
-
e.preventDefault();
|
|
1063
|
-
runCode();
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
// Cmd+Enter (Mac) or Ctrl+Enter (Windows/Linux) - Run code
|
|
1068
|
-
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
1069
|
-
e.preventDefault();
|
|
1070
|
-
runCode();
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1075
|
-
// Initial compile
|
|
1076
|
-
compileCode();
|
|
1077
|
-
</script>
|
|
1078
|
-
</body>
|
|
1079
|
-
</html>
|