AtlatestRepositorysigil-site
sigil-site / tree / assets / replindex.html
1
<!DOCTYPE html>2
<html>3
<head>4
<meta charset="utf-8">5
<title>Sigil Web REPL</title>6
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css">7
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">8
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap">9
<style>10
:root {11
--bg: #0a0a0a;12
--bg-secondary: #141414;13
--fg: #e0e0e0;14
--fg-muted: #888888;15
--accent: #f0c040;16
--accent-dim: #b89830;17
--code-bg: #1a1a1a;18
--border: #2a2a2a;19
--font-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;20
--font-mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;21
}22
* {23
box-sizing: border-box;24
}25
html, body {26
margin: 0;27
padding: 0;28
background: var(--bg);29
color: var(--fg);30
font-family: var(--font-sans);31
font-size: 16px;32
height: 100%;33
overflow: hidden;34
}35
#main-container {36
display: flex;37
gap: 20px;38
height: calc(100% - 57px); /* Account for header */39
padding: 20px;40
}41
#editor-panel {42
flex: 1;43
display: flex;44
flex-direction: column;45
min-width: 0;46
}47
#terminal-panel {48
flex: 1;49
display: flex;50
flex-direction: column;51
min-width: 0;52
}53
.panel-header {54
display: flex;55
align-items: center;56
gap: 10px;57
margin-bottom: 10px;58
height: 32px;59
}60
.panel-title {61
color: var(--accent);62
font-weight: bold;63
}64
.editor-controls {65
display: flex;66
align-items: center;67
gap: 10px;68
margin-left: auto;69
}70
#example-select {71
background: var(--bg-secondary);72
border: 1px solid var(--border);73
border-radius: 4px;74
color: var(--fg);75
padding: 6px 10px;76
font-family: inherit;77
font-size: 13px;78
}79
#example-select:focus {80
outline: none;81
border-color: var(--accent);82
}83
.btn {84
background: var(--accent);85
border: none;86
border-radius: 4px;87
color: var(--bg);88
padding: 6px 16px;89
font-family: inherit;90
font-size: 13px;91
font-weight: bold;92
cursor: pointer;93
transition: background 0.2s;94
}95
.btn:hover {96
background: var(--accent-dim);97
}98
.btn:disabled {99
background: #3a3a3a;100
color: var(--fg-muted);101
cursor: not-allowed;102
}103
.btn-danger {104
background: #dc2626;105
color: white;106
}107
.btn-danger:hover {108
background: #b91c1c;109
}110
.editor-wrapper {111
flex: 1;112
position: relative;113
border-radius: 8px;114
overflow: hidden;115
}116
#editor-highlight {117
position: absolute;118
top: 0;119
left: 0;120
right: 0;121
bottom: 0;122
background: var(--code-bg);123
color: var(--fg);124
padding: 15px;125
margin: 0;126
font-family: var(--font-mono);127
font-size: 15px;128
line-height: 1.5;129
tab-size: 2;130
white-space: pre-wrap;131
word-wrap: break-word;132
overflow: auto;133
pointer-events: none;134
}135
#editor {136
position: relative;137
width: 100%;138
height: 100%;139
background: transparent;140
border: none;141
border-radius: 8px;142
padding: 15px;143
color: transparent;144
caret-color: var(--fg);145
font-family: var(--font-mono);146
font-size: 15px;147
line-height: 1.5;148
resize: none;149
tab-size: 2;150
z-index: 1;151
}152
#editor:focus {153
outline: none;154
}155
#editor::selection {156
background: rgba(240, 192, 64, 0.3);157
}158
/* Syntax highlighting colors */159
.hl-comment { color: #676e95; }160
.hl-string { color: #c3e88d; }161
.hl-keyword { color: #c792ea; }162
.hl-boolean { color: #89ddff; }163
.hl-number { color: #f78c6c; }164
.hl-paren { color: #888; }165
.hl-function { color: #82aaff; }166
#terminal-container {167
flex: 1;168
border-radius: 8px;169
overflow: hidden;170
background: #1a1a1a;171
}172
#terminal-container .xterm {173
height: 100%;174
padding: 10px;175
}176
#terminal-container .xterm-screen {177
border-radius: 0 0 8px 8px;178
}179
#loading {180
position: absolute;181
top: 50%;182
left: 50%;183
transform: translate(-50%, -50%);184
color: var(--accent);185
font-style: italic;186
}188
/* Mobile responsive */189
@media (max-width: 768px) {190
#main-container {191
flex-direction: column;192
padding: 10px;193
gap: 10px;194
}195
#editor-panel {196
flex: 1 1 50%;197
min-height: 200px;198
max-height: 50%;199
}200
#terminal-panel {201
flex: 1 1 50%;202
min-height: 200px;203
}204
.panel-header {205
height: auto;206
flex-wrap: wrap;207
}208
.editor-controls {209
margin-left: 0;210
width: 100%;211
margin-top: 8px;212
}213
#example-select {214
flex: 1;215
}216
#editor,217
#editor-highlight {218
font-size: 16px; /* Prevent iOS zoom on focus */219
}220
}221
/* Header bar */222
#header {223
display: flex;224
align-items: baseline;225
justify-content: flex-start;226
gap: 2rem;227
padding: 14px 20px;228
border-bottom: 1px solid var(--border);229
background: var(--bg);230
}231
#header .brand {232
font-size: 1.75rem;233
font-weight: 700;234
color: var(--fg);235
text-decoration: none;236
letter-spacing: -0.02em;237
}238
#header .brand:hover {239
color: var(--accent);240
}241
#header nav {242
display: flex;243
gap: 1.5rem;244
}245
#header nav a {246
color: var(--fg-muted);247
text-decoration: none;248
font-size: 1rem;249
}250
#header nav a:hover {251
color: var(--accent);252
}253
</style>254
</head>255
<body>256
<div id="loading">Loading Sigil...</div>258
<header id="header" style="display: none;">259
<a href="/" class="brand">sigil</a>260
<nav>261
<a href="/docs/">Docs</a>262
<a href="https://codeberg.org/sigil/sigil">Source</a>263
</nav>264
</header>266
<div id="main-container" style="display: none;">267
<div id="editor-panel">268
<div class="panel-header">269
<span class="panel-title">Editor</span>270
<div class="editor-controls">271
<select id="example-select">272
<optgroup label="Basics">273
<option value="hello">Hello World</option>274
<option value="fibonacci">Fibonacci</option>275
<option value="factorial">Factorial</option>276
<option value="lists">List Operations</option>277
<option value="higherorder">Higher-Order Functions</option>278
<option value="recursion">Recursion Patterns</option>279
<option value="macros">Simple Macros</option>280
<option value="match">Pattern Matching</option>281
</optgroup>282
<optgroup label="Libraries">283
<option value="json">JSON</option>284
<option value="sxml">SXML (HTML/XML)</option>285
<option value="format">Strings</option>286
<option value="time">Date & Time</option>287
<option value="channels">Channels</option>288
<option value="ansi">ANSI Colors</option>289
</optgroup>290
</select>291
<button id="run-btn" class="btn">Run</button>292
<button id="stop-btn" class="btn btn-danger" disabled>Stop</button>293
</div>294
</div>295
<div class="editor-wrapper">296
<pre id="editor-highlight"></pre>297
<textarea id="editor" spellcheck="false"></textarea>298
</div>299
</div>301
<div id="terminal-panel">302
<div class="panel-header">303
<span class="panel-title">REPL</span>304
</div>305
<div id="terminal-container"></div>306
</div>307
</div>309
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.min.js"></script>310
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm-addon-fit.min.js"></script>311
<script>312
// Example code snippets313
const examples = {314
hello: `;;; Hello World315
;;; The classic first program317
(display "Hello, World!")318
(newline)320
;; Greet someone by name321
(define (greet name)322
(println "Hello, ~a!" name))324
(greet "Sigil")325
(greet "Web")`,327
fibonacci: `;;; Fibonacci Sequence328
;;; Computing Fibonacci numbers330
;; Recursive (simple but slow)331
(define (fib-recursive n)332
(if (< n 2)333
n334
(+ (fib-recursive (- n 1))335
(fib-recursive (- n 2)))))337
;; Iterative (fast)338
(define (fib n)339
(let loop ((a 0) (b 1) (count n))340
(if (= count 0)341
a342
(loop b (+ a b) (- count 1)))))344
;; Try them out345
(display "fib(10) = ")346
(display (fib 10))347
(newline)349
(display "First 15 Fibonacci numbers:\\n")350
(let loop ((i 0))351
(when (< i 15)352
(display (fib i))353
(display " ")354
(loop (+ i 1))))355
(newline)`,357
factorial: `;;; Factorial358
;;; Different ways to compute n!360
;; Recursive361
(define (factorial n)362
(if (<= n 1)363
1364
(* n (factorial (- n 1)))))366
;; Tail-recursive367
(define (factorial-tail n)368
(let loop ((n n) (acc 1))369
(if (<= n 1)370
acc371
(loop (- n 1) (* acc n)))))373
;; Using fold374
(define (factorial-fold n)375
(fold * 1 (iota n 1)))377
;; Test all three378
(display "10! = ")379
(display (factorial 10))380
(newline)382
(display "Factorials 1-10:\\n")383
(for-each384
(lambda (n)385
(display n)386
(display "! = ")387
(display (factorial n))388
(newline))389
(iota 10 1))`,391
lists: `;;; List Operations392
;;; Working with Sigil's list primitives394
;; Create some lists395
(define numbers '(1 2 3 4 5))396
(define fruits '("apple" "banana" "cherry"))398
;; Basic operations399
(display "numbers: ") (write numbers) (newline)400
(display "length: ") (display (length numbers)) (newline)401
(display "first: ") (display (car numbers)) (newline)402
(display "rest: ") (write (cdr numbers)) (newline)404
;; Building lists405
(display "\\nBuilding lists:\\n")406
(display "cons 0 onto numbers: ")407
(write (cons 0 numbers))408
(newline)410
(display "append two lists: ")411
(write (append numbers '(6 7 8)))412
(newline)414
(display "reverse: ")415
(write (reverse numbers))416
(newline)418
;; List predicates419
(display "\\nPredicates:\\n")420
(display "null? '(): ") (display (null? '())) (newline)421
(display "pair? numbers: ") (display (pair? numbers)) (newline)422
(display "member 3 numbers: ") (write (member 3 numbers)) (newline)`,424
higherorder: `;;; Higher-Order Functions425
;;; Functions that take or return functions427
(define numbers '(1 2 3 4 5 6 7 8 9 10))429
;; map - transform each element430
(display "squares: ")431
(write (map (lambda (x) (* x x)) numbers))432
(newline)434
;; filter - keep elements matching predicate435
(display "evens: ")436
(write (filter even? numbers))437
(newline)439
(display "odds: ")440
(write (filter odd? numbers))441
(newline)443
;; fold - reduce to single value444
(display "sum: ")445
(display (fold + 0 numbers))446
(newline)448
(display "product: ")449
(display (fold * 1 numbers))450
(newline)452
;; Composition453
(define (compose f g)454
(lambda (x) (f (g x))))456
(define add1 (lambda (x) (+ x 1)))457
(define double (lambda (x) (* x 2)))458
(define add1-then-double (compose double add1))460
(display "\\n(add1-then-double 5) = ")461
(display (add1-then-double 5))462
(newline)`,464
recursion: `;;; Recursion Patterns465
;;; Common recursive techniques467
;; Linear recursion468
(define (sum-list lst)469
(if (null? lst)470
0471
(+ (car lst) (sum-list (cdr lst)))))473
;; Tail recursion (more efficient)474
(define (sum-list-tail lst)475
(let loop ((lst lst) (acc 0))476
(if (null? lst)477
acc478
(loop (cdr lst) (+ acc (car lst))))))480
;; Tree recursion (nested structures)481
(define (deep-sum tree)482
(cond483
((null? tree) 0)484
((number? tree) tree)485
((pair? tree)486
(+ (deep-sum (car tree))487
(deep-sum (cdr tree))))488
(else 0)))490
;; Test them491
(define nums '(1 2 3 4 5))492
(display "sum of (1 2 3 4 5): ")493
(display (sum-list nums))494
(newline)496
(define nested '(1 (2 3) ((4 5) 6)))497
(display "deep-sum of (1 (2 3) ((4 5) 6)): ")498
(display (deep-sum nested))499
(newline)`,501
macros: `;;; Simple Macros502
;;; Extending the language with macros504
;; when - execute body if condition is true505
;; (already built-in, but here's how it works)506
(define-syntax my-when507
(syntax-rules ()508
((my-when test body ...)509
(if test (begin body ...)))))511
;; unless - opposite of when512
(define-syntax unless513
(syntax-rules ()514
((unless test body ...)515
(if (not test) (begin body ...)))))517
;; dotimes - repeat n times518
(define-syntax dotimes519
(syntax-rules ()520
((dotimes (var count) body ...)521
(let loop ((var 0))522
(when (< var count)523
body ...524
(loop (+ var 1)))))))526
;; Test the macros527
(my-when #t528
(display "my-when works!\\n"))530
(unless #f531
(display "unless works!\\n"))533
(display "Counting: ")534
(dotimes (i 5)535
(display i)536
(display " "))537
(newline)`,539
match: `;;; Pattern Matching540
;;; Destructuring data with match541
;;; Requires sigil-match (not bundled, loaded separately)543
(import (sigil match))545
;; Simple value matching (literals must be quoted)546
(define (describe-number n)547
(match n548
('0 "zero")549
('1 "one")550
('2 "two")551
(_ "many")))553
(display "describe 0: ") (display (describe-number 0)) (newline)554
(display "describe 2: ") (display (describe-number 2)) (newline)555
(display "describe 99: ") (display (describe-number 99)) (newline)557
;; List destructuring558
(define (first-two lst)559
(match lst560
((a b . _) (list a b))561
((a) (list a))562
(() '())))564
(display "\\nfirst-two of (1 2 3 4): ")565
(write (first-two '(1 2 3 4)))566
(newline)568
;; Nested patterns569
(define (sum-pairs lst)570
(match lst571
(() 0)572
(((a . b) . rest)573
(+ a b (sum-pairs rest)))))575
(display "sum-pairs ((1 . 2) (3 . 4)): ")576
(display (sum-pairs '((1 . 2) (3 . 4))))577
(newline)`,579
json: `;;; JSON Library580
;;; Parse and generate JSON data582
(import (sigil json))584
;; Parse JSON strings585
(define data (json-decode "{\\"name\\": \\"Alice\\", \\"age\\": 30, \\"active\\": true}"))587
(display "Parsed JSON:\\n")588
(write data)589
(newline)591
;; Access nested data592
(define complex (json-decode "{\\"users\\": [{\\"id\\": 1}, {\\"id\\": 2}]}"))593
(display "\\nComplex structure:\\n")594
(write complex)595
(newline)597
;; Generate JSON from Scheme data598
(define person '((name . "Bob") (scores . (95 87 92))))599
(display "\\nScheme to JSON:\\n")600
(display (json-encode person))601
(newline)603
;; Pretty print604
(display "\\nPretty printed:\\n")605
(display (json-encode person indent: #t))606
(newline)608
;; Round-trip test609
(define original '((items . (1 2 3)) (meta . ((count . 3)))))610
(display "\\nRound-trip:\\n")611
(write (json-decode (json-encode original)))612
(newline)`,614
sxml: `;;; SXML Library615
;;; Represent HTML/XML as S-expressions616
;;; Requires sigil-sxml (not bundled, loaded separately)618
(import (sigil sxml))620
;; SXML represents HTML/XML as nested lists621
;; (tag (@ attributes...) children...)623
(define page624
'(html625
(head (title "My Page"))626
(body627
(h1 "Welcome!")628
(p "This is a paragraph.")629
(ul630
(li "Item 1")631
(li "Item 2")632
(li "Item 3")))))634
(display "SXML structure:\\n")635
(write page)636
(newline)638
;; Convert SXML to XML string639
(display "\\nAs XML:\\n")640
(display (sxml->xml page))641
(newline)643
;; SXML with attributes644
(define link645
'(a (@ (href "https://usesigil.org")646
(class "btn primary"))647
"Visit Sigil"))649
(display "\\nLink with attributes:\\n")650
(display (sxml->xml link))651
(newline)653
;; Building SXML dynamically654
(define (make-list items)655
\`(ul ,@(map (lambda (item) \`(li ,item)) items)))657
(display "\\nDynamic list:\\n")658
(display (sxml->xml (make-list '("Apple" "Banana" "Cherry"))))659
(newline)`,661
format: `;;; String Operations662
;;; Building and manipulating strings664
(import (sigil string))666
;; Basic string operations667
(display "String append: ")668
(display (string-append "Hello, " "World!"))669
(newline)671
;; Building strings from parts672
(define (greet name)673
(string-append "Hello, " name "!"))675
(display (greet "Alice"))676
(newline)677
(display (greet "Bob"))678
(newline)680
;; String predicates681
(display "\\nString predicates:\\n")682
(display " string=? 'foo' 'foo': ")683
(display (string=? "foo" "foo"))684
(newline)685
(display " string<? 'abc' 'def': ")686
(display (string<? "abc" "def"))687
(newline)689
;; Converting to/from strings690
(display "\\nConversions:\\n")691
(display " number->string 42: ")692
(display (number->string 42))693
(newline)694
(display " string->number '123': ")695
(display (string->number "123"))696
(newline)697
(display " symbol->string 'hello: ")698
(display (symbol->string 'hello))699
(newline)701
;; String length and access702
(define s "Sigil")703
(display "\\nString '")704
(display s)705
(display "':\\n")706
(display " length: ")707
(display (string-length s))708
(newline)709
(display " char at 0: ")710
(display (string-ref s 0))711
(newline)`,713
time: `;;; Time Library714
;;; Date and time operations716
(import (sigil time))718
;; Get current time (seconds since epoch)719
(define now (current-second))720
(display "Current timestamp: ")721
(display now)722
(newline)724
;; Time components (pass timestamp to accessors)725
(display "\\nTime breakdown:\\n")726
(display " Year: ") (display (time-year now)) (newline)727
(display " Month: ") (display (time-month now)) (newline)728
(display " Day: ") (display (time-day now)) (newline)729
(display " Hour: ") (display (time-hour now)) (newline)730
(display " Minute: ") (display (time-minute now)) (newline)731
(display " Second: ") (display (time-second now)) (newline)733
;; Day of week (0 = Sunday)734
(define days '("Sunday" "Monday" "Tuesday" "Wednesday"735
"Thursday" "Friday" "Saturday"))736
(display " Day of week: ")737
(display (list-ref days (time-weekday now)))738
(newline)740
;; High-resolution timing741
(display "\\nHigh-res timing:\\n")742
(display " Jiffies: ") (display (current-jiffy)) (newline)743
(display " Jiffies/sec: ") (display (jiffies-per-second)) (newline)`,745
channels: `;;; Channels Library746
;;; Message passing with buffered channels748
(import (sigil channels))750
;; Create a buffered channel (capacity 3)751
(define ch (make-channel 3))752
(display "Created buffered channel\\n")754
;; Send messages (non-blocking with try-send)755
(display "\\nSending messages...\\n")756
(channel-try-send ch "hello")757
(display " Sent: hello\\n")758
(channel-try-send ch "world")759
(display " Sent: world\\n")760
(channel-try-send ch 42)761
(display " Sent: 42\\n")763
;; Try to send when full764
(display "\\nChannel full? ")765
(display (if (channel-try-send ch "overflow")766
"no, sent another"767
"yes, channel is full"))768
(newline)770
;; Receive messages (non-blocking with try-receive)771
(display "\\nReceiving messages...\\n")772
(display " Received: ") (write (channel-try-receive ch)) (newline)773
(display " Received: ") (write (channel-try-receive ch)) (newline)774
(display " Received: ") (write (channel-try-receive ch)) (newline)776
;; Try to receive when empty777
(display "\\nChannel empty? ")778
(display (if (channel-try-receive ch)779
"no, got a value"780
"yes, channel is empty"))781
(newline)783
;; Close channel784
(channel-close! ch)785
(display "\\nChannel closed.")786
(newline)`,788
ansi: `;;; ANSI Library789
;;; Terminal colors and styles791
(import (sigil ansi))793
;; Basic colors (all functions use a: prefix)794
(display (a:red "This is red"))795
(newline)796
(display (a:green "This is green"))797
(newline)798
(display (a:blue "This is blue"))799
(newline)800
(display (a:yellow "This is yellow"))801
(newline)803
;; Styles804
(display (a:bold "Bold text"))805
(newline)806
(display (a:dim "Dim text"))807
(newline)808
(display (a:underline "Underlined"))809
(newline)811
;; Combined styles812
(display (a:bold (a:red "Bold and red!")))813
(newline)815
;; Building colored output816
(define (status-message status msg)817
(display818
(cond819
((eq? status 'ok) (a:green (string-append "[OK] " msg)))820
((eq? status 'warn) (a:yellow (string-append "[WARN] " msg)))821
((eq? status 'error) (a:red (string-append "[ERROR] " msg)))822
(else msg)))823
(newline))825
(newline)826
(status-message 'ok "Operation completed")827
(status-message 'warn "Low disk space")828
(status-message 'error "Connection failed")`829
};831
const mainContainer = document.getElementById('main-container');832
const terminalContainer = document.getElementById('terminal-container');833
const loading = document.getElementById('loading');834
const editor = document.getElementById('editor');835
const editorHighlight = document.getElementById('editor-highlight');836
const exampleSelect = document.getElementById('example-select');837
const runBtn = document.getElementById('run-btn');838
const stopBtn = document.getElementById('stop-btn');840
// Scheme syntax highlighting841
const schemeKeywords = new Set([842
'define', 'define-syntax', 'define-library', 'lambda', 'let', 'let*', 'letrec',843
'if', 'cond', 'else', 'case', 'when', 'unless', 'begin', 'do',844
'and', 'or', 'not', 'quote', 'quasiquote', 'unquote', 'unquote-splicing',845
'set!', 'import', 'export', 'library', 'syntax-rules', 'match',846
'guard', 'with-exception-handler', 'raise', 'error',847
'for-each', 'map', 'filter', 'fold', 'apply', 'call/cc',848
'display', 'newline', 'write', 'read'849
]);851
function escapeHtml(text) {852
return text853
.replace(/&/g, '&')854
.replace(/</g, '<')855
.replace(/>/g, '>');856
}858
function highlightScheme(code) {859
let result = '';860
let i = 0;862
while (i < code.length) {863
// Comments864
if (code[i] === ';') {865
let end = code.indexOf('\n', i);866
if (end === -1) end = code.length;867
result += '<span class="hl-comment">' + escapeHtml(code.slice(i, end)) + '</span>';868
i = end;869
continue;870
}872
// Strings873
if (code[i] === '"') {874
let end = i + 1;875
while (end < code.length && code[end] !== '"') {876
if (code[end] === '\\') end++;877
end++;878
}879
end++; // include closing quote880
result += '<span class="hl-string">' + escapeHtml(code.slice(i, end)) + '</span>';881
i = end;882
continue;883
}885
// Character literals886
if (code[i] === '#' && code[i + 1] === '\\') {887
let end = i + 2;888
while (end < code.length && /[a-zA-Z]/.test(code[end])) end++;889
if (end === i + 2) end++; // single char like #\a890
result += '<span class="hl-string">' + escapeHtml(code.slice(i, end)) + '</span>';891
i = end;892
continue;893
}895
// Booleans896
if (code[i] === '#' && (code[i + 1] === 't' || code[i + 1] === 'f')) {897
result += '<span class="hl-boolean">' + escapeHtml(code.slice(i, i + 2)) + '</span>';898
i += 2;899
continue;900
}902
// Numbers903
if (/[0-9]/.test(code[i]) || (code[i] === '-' && /[0-9]/.test(code[i + 1]))) {904
let end = i;905
if (code[end] === '-') end++;906
while (end < code.length && /[0-9.]/.test(code[end])) end++;907
// Make sure it's not part of an identifier908
if (i === 0 || /[\s()\[\]'`,]/.test(code[i - 1])) {909
result += '<span class="hl-number">' + escapeHtml(code.slice(i, end)) + '</span>';910
i = end;911
continue;912
}913
}915
// Identifiers and keywords916
if (/[a-zA-Z_!?*+\-<>=]/.test(code[i])) {917
let end = i;918
while (end < code.length && /[a-zA-Z0-9_!?*+\-<>=\/:]/.test(code[end])) end++;919
const word = code.slice(i, end);920
if (schemeKeywords.has(word)) {921
result += '<span class="hl-keyword">' + escapeHtml(word) + '</span>';922
} else {923
result += escapeHtml(word);924
}925
i = end;926
continue;927
}929
// Parentheses930
if (code[i] === '(' || code[i] === ')' || code[i] === '[' || code[i] === ']') {931
result += '<span class="hl-paren">' + code[i] + '</span>';932
i++;933
continue;934
}936
// Default: just output the character937
result += escapeHtml(code[i]);938
i++;939
}941
return result;942
}944
function updateHighlight() {945
// Add a newline to ensure the pre matches textarea height when ending with newline946
editorHighlight.innerHTML = highlightScheme(editor.value) + '\n';947
}949
// Sync scroll between editor and highlight950
editor.addEventListener('scroll', function() {951
editorHighlight.scrollTop = editor.scrollTop;952
editorHighlight.scrollLeft = editor.scrollLeft;953
});955
// Update highlighting on input956
editor.addEventListener('input', updateHighlight);958
// Load initial example959
editor.value = examples[exampleSelect.value];960
updateHighlight();962
// Handle example selection963
exampleSelect.addEventListener('change', function() {964
editor.value = examples[this.value];965
updateHighlight();966
});968
// Handle tab key in editor969
editor.addEventListener('keydown', function(e) {970
if (e.key === 'Tab') {971
e.preventDefault();972
const start = this.selectionStart;973
const end = this.selectionEnd;974
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);975
this.selectionStart = this.selectionEnd = start + 2;976
updateHighlight();977
}978
});979
</script>980
<script type="module">981
import { WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory }982
from "https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/dist/index.js";984
const mainContainer = document.getElementById('main-container');985
const terminalContainer = document.getElementById('terminal-container');986
const loading = document.getElementById('loading');987
const editor = document.getElementById('editor');988
const runBtn = document.getElementById('run-btn');989
const stopBtn = document.getElementById('stop-btn');991
// Create xterm.js terminal992
const term = new Terminal({993
theme: {994
background: '#1a1a1a',995
foreground: '#e0e0e0',996
cursor: '#f0c040',997
cursorAccent: '#1a1a1a',998
selectionBackground: 'rgba(240, 192, 64, 0.3)'999
},1000
fontFamily: "'JetBrains Mono', 'SF Mono', Consolas, monospace",1001
fontSize: 15,1002
cursorBlink: true,1003
convertEol: true1004
});1005
const fitAddon = new FitAddon.FitAddon();1006
term.loadAddon(fitAddon);1008
// State1009
let currentLine = '';1010
let history = [];1011
let historyIndex = -1;1012
let ready = false;1013
let waitingForInput = false;1014
let running = false;1015
let sigilInput = null;1016
let sigilEval = null;1018
// Run code from editor1019
function runCode() {1020
if (!ready) return;1021
const code = editor.value.trim();1022
if (!code) return;1024
running = true;1025
runBtn.disabled = true;1026
stopBtn.disabled = false;1027
term.write('\r\n');1029
sigilEval(code);1031
running = false;1032
runBtn.disabled = false;1033
stopBtn.disabled = true;1035
if (waitingForInput) {1036
sigilInput('');1037
}1038
}1040
function stopCode() {1041
running = false;1042
runBtn.disabled = false;1043
stopBtn.disabled = true;1044
term.write('\r\n^C\r\n');1045
}1047
runBtn.addEventListener('click', runCode);1048
stopBtn.addEventListener('click', stopCode);1050
// Terminal input handling1051
function handleInput(data) {1052
if (!ready || !waitingForInput) return;1054
for (let i = 0; i < data.length; i++) {1055
const char = data[i];1056
const code = data.charCodeAt(i);1058
if (code === 13) { // Enter1059
term.write('\r\n');1060
if (currentLine.trim()) {1061
history.push(currentLine);1062
historyIndex = history.length;1063
}1064
const line = currentLine;1065
currentLine = '';1066
waitingForInput = false;1067
const status = sigilInput(line);1068
if (status === 1) {1069
waitingForInput = true;1070
} else if (status === 0) {1071
term.writeln('\r\n[REPL exited]');1072
} else {1073
waitingForInput = true;1074
}1075
} else if (code === 127 || code === 8) { // Backspace1076
if (currentLine.length > 0) {1077
currentLine = currentLine.slice(0, -1);1078
term.write('\b \b');1079
}1080
} else if (code === 27) { // Escape sequence1081
if (data.substring(i, i + 3) === '\x1b[A') { // Up arrow1082
i += 2;1083
if (historyIndex > 0) {1084
while (currentLine.length > 0) {1085
term.write('\b \b');1086
currentLine = currentLine.slice(0, -1);1087
}1088
historyIndex--;1089
currentLine = history[historyIndex];1090
term.write(currentLine);1091
}1092
} else if (data.substring(i, i + 3) === '\x1b[B') { // Down arrow1093
i += 2;1094
while (currentLine.length > 0) {1095
term.write('\b \b');1096
currentLine = currentLine.slice(0, -1);1097
}1098
if (historyIndex < history.length - 1) {1099
historyIndex++;1100
currentLine = history[historyIndex];1101
term.write(currentLine);1102
} else {1103
historyIndex = history.length;1104
currentLine = '';1105
}1106
}1107
} else if (code === 3) { // Ctrl+C1108
term.write('^C\r\n');1109
currentLine = '';1110
waitingForInput = false;1111
const status = sigilInput('');1112
if (status === 1) waitingForInput = true;1113
} else if (code >= 32) { // Printable character1114
currentLine += char;1115
term.write(char);1116
}1117
}1118
}1120
term.onData(handleInput);1121
window.addEventListener('resize', () => { if (ready) fitAddon.fit(); });1123
// Load module bundle into virtual filesystem1124
// Bundle format: [uint32 count] then per file:1125
// [uint16 path-len] [path bytes] [uint32 data-len] [data bytes]1126
async function buildFs() {1127
const { File, Directory } = await import("https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/dist/index.js");1128
const files = {};1129
try {1130
const resp = await fetch('lib.bundle');1131
if (!resp.ok) return { files, File, Directory };1132
const buf = await resp.arrayBuffer();1133
const view = new DataView(buf);1134
const decoder = new TextDecoder();1135
let offset = 0;1137
const count = view.getUint32(offset, true); offset += 4;1138
for (let i = 0; i < count; i++) {1139
const pathLen = view.getUint16(offset, true); offset += 2;1140
const path = decoder.decode(new Uint8Array(buf, offset, pathLen)); offset += pathLen;1141
const dataLen = view.getUint32(offset, true); offset += 4;1142
const data = new Uint8Array(buf, offset, dataLen); offset += dataLen;1144
const parts = path.split('/');1145
let dir = files;1146
for (let j = 0; j < parts.length - 1; j++) {1147
if (!dir[parts[j]]) dir[parts[j]] = {};1148
dir = dir[parts[j]];1149
}1150
dir[parts[parts.length - 1]] = new File(new Uint8Array(data));1151
}1152
} catch (e) {1153
console.warn('Bundle loading:', e.message);1154
}1155
return { files, File, Directory };1156
}1158
function makeDir(obj, FileClass, DirectoryClass) {1159
const entries = new Map();1160
for (const [name, value] of Object.entries(obj)) {1161
if (value instanceof FileClass) {1162
entries.set(name, value);1163
} else {1164
entries.set(name, makeDir(value, FileClass, DirectoryClass));1165
}1166
}1167
return new DirectoryClass(entries);1168
}1170
function callWithString(instance, fn, str) {1171
const encoder = new TextEncoder();1172
const bytes = encoder.encode(str + '\0');1173
const ptr = instance.exports.malloc(bytes.length);1174
new Uint8Array(instance.exports.memory.buffer, ptr, bytes.length).set(bytes);1175
const result = fn(ptr);1176
instance.exports.free(ptr);1177
return result;1178
}1180
// Custom WASI Fd that writes to xterm immediately (unbuffered)1181
function makeTermWriter(termRef, wasiModule) {1182
const decoder = new TextDecoder();1183
const fd = new wasiModule.Fd();1184
fd.fd_write = function(data) {1185
const text = decoder.decode(data, { stream: true });1186
termRef.write(text.replace(/\n/g, '\r\n'));1187
return { ret: 0, nwritten: data.byteLength };1188
};1189
fd.fd_fdstat_get = function() {1190
const { Fdstat } = wasiModule.wasi;1191
return { ret: 0, fdstat: new Fdstat(2, 0) };1192
};1193
return fd;1194
}1196
async function loadOptionalBridgeLoader() {1197
const response = await fetch('sigil-wasm-bridges.js', { method: 'HEAD' });1198
if (!response.ok) {1199
return null;1200
}1202
await new Promise((resolve, reject) => {1203
const script = document.createElement('script');1204
script.src = 'sigil-wasm-bridges.js';1205
script.onload = resolve;1206
script.onerror = () => reject(new Error('Failed to load sigil-wasm-bridges.js'));1207
document.head.appendChild(script);1208
});1210
if (!window.SigilWasmBridges) {1211
return null;1212
}1214
return window.SigilWasmBridges.load();1215
}1217
function makeWasmNetStubImports() {1218
return {1219
sigil_wasm_net: {1220
websocket_open() { return 0; },1221
websocket_send() { return 0; },1222
websocket_close() { return 0; },1223
websocket_poll() { return 0; },1224
current_event_code() { return 0; },1225
current_event_was_clean() { return 0; },1226
current_event_data_length() { return 0; },1227
current_event_reason_length() { return 0; },1228
copy_current_event_data() { return 0; },1229
copy_current_event_reason() { return 0; },1230
},1231
};1232
}1234
// Initialize WASI and load WASM1235
async function init() {1236
const wasiModule = await import("https://cdn.jsdelivr.net/npm/@bjorn3/[email protected]/dist/index.js");1237
const { WASI, File: WasiFile, Fd, Fdstat, OpenFile, PreopenDirectory } = wasiModule;1239
const { files: libFiles, File: FileClass, Directory: DirectoryClass } = await buildFs();1241
const fds = [1242
new OpenFile(new WasiFile([])), // stdin1243
makeTermWriter(term, wasiModule), // stdout1244
makeTermWriter(term, wasiModule), // stderr1245
new PreopenDirectory("/", new Map([1246
["lib", makeDir(libFiles, FileClass, DirectoryClass)]1247
])),1248
];1250
const wasi = new WASI(["sigil-web-repl"], [], fds, { debug: false });1252
try {1253
const bridgeManifest = await loadOptionalBridgeLoader();1254
const bridgeImports = {1255
...makeWasmNetStubImports(),1256
...(bridgeManifest1257
? window.SigilWasmBridges.createImports(bridgeManifest)1258
: {}),1259
};1260
const wasmModule = await WebAssembly.compileStreaming(fetch('sigil-web-repl.wasm'));1261
const instance = await WebAssembly.instantiate(wasmModule, {1262
wasi_snapshot_preview1: wasi.wasiImport,1263
...bridgeImports,1264
});1265
if (bridgeManifest) {1266
window.SigilWasmBridges.setInstance(bridgeManifest, instance);1267
}1269
sigilInput = (str) => callWithString(instance, instance.exports.sigil_wasm_input, str);1270
sigilEval = (str) => callWithString(instance, instance.exports.sigil_wasm_eval, str);1272
// Start the VM1273
wasi.start(instance);1275
// Show UI1276
loading.style.display = 'none';1277
document.getElementById('header').style.display = 'flex';1278
mainContainer.style.display = 'flex';1279
term.open(terminalContainer);1280
fitAddon.fit();1282
ready = true;1283
waitingForInput = true;1284
term.focus();1285
} catch (e) {1286
loading.textContent = 'Error: ' + e.message;1287
console.error(e);1288
}1289
}1291
init();1292
</script>1293
</body>1294
</html>