bfbf 0.1.0 → 0.1.1

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/AGENTS.md ADDED
@@ -0,0 +1,113 @@
1
+ # Agent Guidelines for bfbf Project
2
+
3
+ ## Project Overview
4
+ bfbf is a Brainfuck interpreter implemented in pure JavaScript. The project demonstrates Turing completeness through a Towers of Hanoi implementation in Brainfuck code.
5
+
6
+ - **Language**: JavaScript (Node.js)
7
+ - **Type System**: Plain JavaScript (no TypeScript)
8
+ - **Test Framework**: Mocha
9
+ - **Package Manager**: npm (pnpm lock file present)
10
+ - **Module System**: CommonJS
11
+ - **Commit Convention**: Commitizen with cz-jt adapter
12
+
13
+ ## Build, Lint, and Test Commands
14
+
15
+ ### Core Commands
16
+ ```bash
17
+ npm start # Start development server with nodemon (auto-restarts on changes)
18
+ npm test # Run all tests with mocha
19
+ npm run cz # Run commitizen for interactive conventional commits
20
+ ```
21
+
22
+ ### Running Specific Tests
23
+ ```bash
24
+ # Run a single test file
25
+ npx mocha tests/filename.js
26
+
27
+ # Run all test files matching a pattern
28
+ npx mocha --grep "pattern" ./tests/*.js
29
+
30
+ # Run a specific test within a file
31
+ npx mocha tests/filename.js --grep "test name"
32
+
33
+ # Run tests with reporter options
34
+ npx mocha tests/*.js --reporter spec
35
+ ```
36
+
37
+ ## Code Style Guidelines
38
+
39
+ ### Formatting
40
+ - Use **tabs** for indentation (2 spaces per tab) - not spaces
41
+ - **No semicolons** at statement ends
42
+ - Maximum line length: 100 characters
43
+ - Use template literals for string interpolation (backticks)
44
+ - Use consistent spacing around operators and after commas
45
+ - Break long lines at logical points with proper indentation
46
+
47
+ ### Naming Conventions
48
+ - **Functions**: camelCase with descriptive names (e.g., `js2BF`, `parseInput`, `executeBF`)
49
+ - **Variables**: camelCase with descriptive names (e.g., `memoryPtr`, `inputPtr`, `outputBuffer`)
50
+ - **Constants**: UPPER_SNAKE_CASE for true constants (e.g., `MEMORY_SIZE`, `MAX_INPUT_LENGTH`)
51
+ - **Files**: kebab-case for JavaScript files (e.g., `brainfuck-interpreter.js`, `hanoi-solver.js`)
52
+ - **Classes**: PascalCase if used (e.g., `BFInterpreter`, `MemoryManager`)
53
+
54
+ ### Type Handling
55
+ - Use `Uint8Array` for memory buffers (fixed-size, typed arrays)
56
+ - Use `String.fromCharCode()` for character conversions
57
+ - Use `charCodeAt()` for string to character code conversion
58
+ - Avoid implicit type coercion; use explicit comparisons (`===` not `==`)
59
+ - Validate input types at function boundaries with early returns
60
+ - Use `typeof` and `instanceof` for type checking
61
+
62
+ ### Import Patterns
63
+ - Use CommonJS `require()` for module imports (not ES6 imports)
64
+ - Use `module.exports` or `exports` for module exports
65
+ - Import modules at the top of files (no inline requires)
66
+ - Group related imports together
67
+ - Avoid circular dependencies
68
+
69
+ ### Error Handling
70
+ - Use `try/catch` for synchronous operations that may throw
71
+ - Return error objects with descriptive messages instead of throwing when appropriate
72
+ - Handle edge cases explicitly (empty input, null values, undefined values)
73
+ - Validate array bounds before access operations to prevent errors
74
+ - Use defensive programming techniques for external inputs
75
+ - Log errors with sufficient context for debugging
76
+
77
+ ### Code Structure
78
+ - Keep functions focused and small (target under 50 lines when possible)
79
+ - Use early returns to reduce nesting and improve readability
80
+ - Group related logic together in the same function
81
+ - Extract complex logic into separate, well-named helper functions
82
+ - Add Chinese comments for complex algorithms (per project convention)
83
+ - Document public APIs with JSDoc-style comments
84
+
85
+ ### BF Interpreter Specific Guidelines
86
+ - Memory size: 10,000 bytes (fixed, `Uint8Array(10000)`)
87
+ - Input handling: character-by-character with fallback to null/0
88
+ - Loop matching: Use stack-based approach for bracket pairs (`[` and `]`)
89
+ - Pointer movement: Validate pointer bounds (0 to 9999)
90
+ - Output: Support both array collection and stream output modes
91
+ - Command filtering: Skip non-BF commands silently (optimization)
92
+
93
+ ### Git Workflow
94
+ - Use `npm run cz` for commits to follow conventional changelog
95
+ - Write meaningful commit messages describing the "why" not just the "what"
96
+ - Keep commits atomic and focused on a single change
97
+ - Review changes before committing
98
+ - Push changes regularly to avoid data loss
99
+
100
+ ## Testing Guidelines
101
+ - Write tests for new functionality in the `tests/` directory
102
+ - Use descriptive test names that explain the behavior being tested
103
+ - Test edge cases: empty input, maximum input, invalid characters
104
+ - Test both success and error paths
105
+ - Mock external dependencies when necessary
106
+
107
+ ## Development Workflow
108
+ 1. Create a feature branch for changes
109
+ 2. Write code following the style guidelines
110
+ 3. Add or update tests for the functionality
111
+ 4. Run `npm test` to verify all tests pass
112
+ 5. Use `npm run cz` to create a proper commit message
113
+ 6. Push changes and create a pull request when ready
package/README.md CHANGED
@@ -3,7 +3,21 @@
3
3
  Turing Complete Towers of Hanoi
4
4
 
5
5
  ```javascript
6
+ npm i bfbf
7
+ ```
8
+
9
+ ```javascript
10
+ visualize.html 可视化html
11
+ ```
12
+
13
+ ```javascript
14
+ const bf = require('bfbf)
6
15
  const input = ''
7
- // console.log(js2BF(hanoiCode, input, 1)) // 第三个参数是是否使用流模式输出
8
- console.log(js2BF(helloCode, input).output) // 输出hello world
16
+ console.log(bf(hanoiCode, input, 1)) // 第三个参数是是否使用流模式输出
17
+ // console.log(bf(helloCode, input).output) // 输出hello world
9
18
  ```
19
+
20
+ <video width="640" height="480" controls>
21
+ <source src="https://github.com/kongnet/bf/raw/main/hanoi.mp4" type="video/mp4">
22
+ Your browser does not support the video tag.
23
+ </video>
package/hanoi.mp4 ADDED
Binary file
package/index.js ADDED
@@ -0,0 +1,74 @@
1
+ // 实现图灵完备语言BF
2
+ // 浏览器兼容版本
3
+ (function(root, factory) {
4
+ if (typeof module === 'object' && module.exports) {
5
+ module.exports = factory()
6
+ } else {
7
+ root.js2BF = factory()
8
+ }
9
+ }(typeof self !== 'undefined' ? self : this, function() {
10
+ function js2BF (code, input, stream = false) {
11
+ const memory = new Uint8Array(10000)
12
+ let ptr = 0
13
+ let inputPtr = 0
14
+ let output = []
15
+
16
+ const stack = []
17
+
18
+ for (let i = 0; i < code.length; i++) {
19
+ const char = code[i]
20
+ switch (char) {
21
+ case '>':
22
+ ptr++
23
+ break
24
+ case '<':
25
+ ptr--
26
+ break
27
+ case '+':
28
+ memory[ptr]++
29
+ break
30
+ case '-':
31
+ memory[ptr]--
32
+ break
33
+ case '.':
34
+ output.push(String.fromCharCode(memory[ptr]))
35
+ stream && process.stdout.write(String.fromCharCode(memory[ptr]))
36
+ break
37
+ case ',':
38
+ if (inputPtr < input.length) {
39
+ memory[ptr] = input.charCodeAt(inputPtr)
40
+ inputPtr++
41
+ } else {
42
+ memory[ptr] = 0
43
+ }
44
+ break
45
+ case '[':
46
+ if (memory[ptr] === 0) {
47
+ let count = 1
48
+ while (count > 0) {
49
+ i++
50
+ if (code[i] === '[') count++
51
+ else if (code[i] === ']') count--
52
+ }
53
+ } else {
54
+ stack.push(i)
55
+ }
56
+ break
57
+ case ']':
58
+ if (memory[ptr] !== 0) {
59
+ i = stack[stack.length - 1]
60
+ } else {
61
+ stack.pop()
62
+ }
63
+ break
64
+ }
65
+ }
66
+
67
+ return {
68
+ output: output.map(x => String.fromCharCode(x)).join(''),
69
+ ptr,
70
+ memory
71
+ }
72
+ }
73
+ return js2BF
74
+ }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bfbf",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "bfbf",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,70 +1,4 @@
1
- //实现图灵完备语言BF
2
- function js2BF (code, input, stream = false) {
3
- const memory = new Uint8Array(10000) // bf使用的内存空间大小
4
- let ptr = 0 // 内存指针
5
- let inputPtr = 0 // 输入指针
6
- let output = [] // 输出
7
-
8
- const stack = [] // 栈,'[' , ']' 匹配
9
-
10
- for (let i = 0; i < code.length; i++) {
11
- const char = code[i]
12
- switch (char) {
13
- case '>':
14
- ptr++
15
- break
16
- case '<':
17
- ptr--
18
- break
19
- case '+':
20
- memory[ptr]++
21
- break
22
- case '-':
23
- memory[ptr]--
24
- break
25
- case '.':
26
- output.push(memory[ptr]) // += String.fromCharCode(memory[ptr]) 压入数组为了可以输出观察内存模式
27
- stream && process.stdout.write(String.fromCharCode(memory[ptr])) // 要兼容网页输出,改这里
28
- break
29
- case ',':
30
- if (inputPtr < input.length) {
31
- memory[ptr] = input.charCodeAt(inputPtr)
32
- inputPtr++
33
- } else {
34
- memory[ptr] = 0
35
- }
36
- break
37
- case '[':
38
- if (memory[ptr] === 0) {
39
- let count = 1
40
- while (count > 0) {
41
- i++
42
- if (code[i] === '[') count++
43
- else if (code[i] === ']') count--
44
- }
45
- } else {
46
- stack.push(i)
47
- }
48
- break
49
- case ']':
50
- if (memory[ptr] !== 0) {
51
- i = stack[stack.length - 1]
52
- } else {
53
- stack.pop()
54
- }
55
- break
56
- default:
57
- break
58
- }
59
- }
60
-
61
- return {
62
- output: output.map(x => String.fromCharCode(x)).join(''),
63
- ptr,
64
- memory
65
- }
66
- }
67
-
1
+ const bf = require('./index.js')
68
2
  // 以下是例子
69
3
  let iloveyou = `
70
4
  +++++ +++++
@@ -767,6 +701,6 @@ let helloCode = `>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.
767
701
 
768
702
  // 3个例子
769
703
  const input = ''
770
- // console.log(js2BF(hanoiCode, input, 1)) // 汉诺塔 命令行动态演示 第三个参数是是否使用流模式输出
771
- console.log(js2BF(helloCode, input).output) // 输出Hello World!\n 返回{ output, ptr, memory} 3个对象
704
+ console.log(bf(hanoiCode, input, 1)) // 汉诺塔 命令行动态演示 第三个参数是是否使用流模式输出
705
+ // console.log(js2BF(helloCode, input).output) // 输出Hello World!\n 返回{ output, ptr, memory} 3个对象
772
706
  // console.log(js2BF(iloveyou, input).output)
package/visualize.html ADDED
@@ -0,0 +1,632 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Brainfuck 可视化解释器</title>
7
+ <style>
8
+ * {
9
+ box-sizing: border-box;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: #1a1a2e;
17
+ color: #eee;
18
+ min-height: 100vh;
19
+ padding: 20px;
20
+ }
21
+
22
+ h1 {
23
+ text-align: center;
24
+ margin-bottom: 20px;
25
+ color: #00d4ff;
26
+ }
27
+
28
+ .container {
29
+ max-width: 1200px;
30
+ margin: 0 auto;
31
+ display: grid;
32
+ grid-template-columns: 1fr 1fr;
33
+ gap: 20px;
34
+ }
35
+
36
+ .panel {
37
+ background: #16213e;
38
+ border-radius: 10px;
39
+ padding: 20px;
40
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
41
+ }
42
+
43
+ .panel h2 {
44
+ color: #00d4ff;
45
+ margin-bottom: 15px;
46
+ font-size: 1.2rem;
47
+ border-bottom: 2px solid #0f3460;
48
+ padding-bottom: 10px;
49
+ }
50
+
51
+ .code-display {
52
+ font-family: 'Courier New', monospace;
53
+ font-size: 14px;
54
+ line-height: 1.8;
55
+ background: #0f3460;
56
+ padding: 15px;
57
+ border-radius: 8px;
58
+ white-space: pre-wrap;
59
+ word-break: break-all;
60
+ max-height: 300px;
61
+ overflow-y: auto;
62
+ }
63
+
64
+ #codeInput {
65
+ width: 100%;
66
+ height: 120px;
67
+ background: #0f3460;
68
+ border: 1px solid #00d4ff;
69
+ border-radius: 8px;
70
+ color: #eee;
71
+ padding: 15px;
72
+ font-family: 'Courier New', monospace;
73
+ font-size: 14px;
74
+ resize: vertical;
75
+ }
76
+
77
+ .code-display .char {
78
+ display: inline-block;
79
+ padding: 2px 4px;
80
+ margin: 1px;
81
+ border-radius: 3px;
82
+ transition: all 0.1s ease;
83
+ }
84
+
85
+ .code-display .current {
86
+ background: #ffd700;
87
+ color: #000;
88
+ font-weight: bold;
89
+ box-shadow: 0 0 10px #ffd700;
90
+ }
91
+
92
+ .memory-grid {
93
+ display: grid;
94
+ grid-template-columns: repeat(10, 1fr);
95
+ gap: 5px;
96
+ }
97
+
98
+ .cell {
99
+ background: #0f3460;
100
+ padding: 10px 5px;
101
+ text-align: center;
102
+ border-radius: 5px;
103
+ font-family: 'Courier New', monospace;
104
+ font-size: 12px;
105
+ transition: all 0.3s ease;
106
+ border: 2px solid transparent;
107
+ }
108
+
109
+ .cell .addr {
110
+ font-size: 10px;
111
+ color: #666;
112
+ margin-bottom: 3px;
113
+ }
114
+
115
+ .cell .value {
116
+ font-size: 16px;
117
+ font-weight: bold;
118
+ color: #00d4ff;
119
+ }
120
+
121
+ .cell.active {
122
+ background: #ffd700;
123
+ border-color: #ff9500;
124
+ box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
125
+ }
126
+
127
+ .cell.active .addr,
128
+ .cell.active .value {
129
+ color: #000;
130
+ }
131
+
132
+ .cell.changed {
133
+ animation: pulse 0.3s ease;
134
+ }
135
+
136
+ @keyframes pulse {
137
+ 0% {
138
+ transform: scale(1);
139
+ }
140
+ 50% {
141
+ transform: scale(1.1);
142
+ background: #4ade80;
143
+ }
144
+ 100% {
145
+ transform: scale(1);
146
+ }
147
+ }
148
+
149
+ .io-section {
150
+ grid-column: 1 / -1;
151
+ display: grid;
152
+ grid-template-columns: 1fr 1fr;
153
+ gap: 20px;
154
+ }
155
+
156
+ .input-area,
157
+ .output-area {
158
+ background: #0f3460;
159
+ padding: 15px;
160
+ border-radius: 8px;
161
+ }
162
+
163
+ .input-area textarea {
164
+ width: 100%;
165
+ height: 80px;
166
+ background: #16213e;
167
+ border: 1px solid #00d4ff;
168
+ border-radius: 5px;
169
+ color: #eee;
170
+ padding: 10px;
171
+ font-family: 'Courier New', monospace;
172
+ resize: none;
173
+ }
174
+
175
+ .output-area pre {
176
+ background: #16213e;
177
+ padding: 10px;
178
+ border-radius: 5px;
179
+ min-height: 60px;
180
+ font-family: 'Courier New', monospace;
181
+ white-space: pre-wrap;
182
+ word-break: break-all;
183
+ }
184
+
185
+ .controls {
186
+ grid-column: 1 / -1;
187
+ display: flex;
188
+ gap: 15px;
189
+ justify-content: center;
190
+ align-items: center;
191
+ flex-wrap: wrap;
192
+ }
193
+
194
+ button {
195
+ padding: 12px 30px;
196
+ font-size: 16px;
197
+ border: none;
198
+ border-radius: 8px;
199
+ cursor: pointer;
200
+ transition: all 0.3s ease;
201
+ font-weight: bold;
202
+ }
203
+
204
+ .btn-start {
205
+ background: #4ade80;
206
+ color: #000;
207
+ }
208
+
209
+ .btn-start:hover {
210
+ background: #22c55e;
211
+ transform: translateY(-2px);
212
+ }
213
+
214
+ .btn-step {
215
+ background: #00d4ff;
216
+ color: #000;
217
+ }
218
+
219
+ .btn-step:hover {
220
+ background: #0ea5e9;
221
+ transform: translateY(-2px);
222
+ }
223
+
224
+ .btn-reset {
225
+ background: #ef4444;
226
+ color: #fff;
227
+ }
228
+
229
+ .btn-reset:hover {
230
+ background: #dc2626;
231
+ transform: translateY(-2px);
232
+ }
233
+
234
+ .speed-control {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 10px;
238
+ }
239
+
240
+ .speed-control input {
241
+ width: 150px;
242
+ }
243
+
244
+ .status {
245
+ grid-column: 1 / -1;
246
+ text-align: center;
247
+ padding: 15px;
248
+ background: #0f3460;
249
+ border-radius: 8px;
250
+ font-family: 'Courier New', monospace;
251
+ }
252
+
253
+ .status .highlight {
254
+ color: #ffd700;
255
+ font-weight: bold;
256
+ }
257
+
258
+ .legend {
259
+ grid-column: 1 / -1;
260
+ display: flex;
261
+ gap: 20px;
262
+ justify-content: center;
263
+ flex-wrap: wrap;
264
+ font-size: 14px;
265
+ }
266
+
267
+ .legend span {
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 8px;
271
+ }
272
+
273
+ .legend .dot {
274
+ width: 20px;
275
+ height: 20px;
276
+ border-radius: 4px;
277
+ }
278
+
279
+ .legend .dot.code {
280
+ background: #ffd700;
281
+ }
282
+
283
+ .legend .dot.cell {
284
+ background: #ffd700;
285
+ border: 2px solid #ff9500;
286
+ }
287
+
288
+ .legend .dot.changed {
289
+ background: #4ade80;
290
+ }
291
+
292
+ .presets {
293
+ margin-bottom: 10px;
294
+ display: flex;
295
+ gap: 8px;
296
+ flex-wrap: wrap;
297
+ }
298
+
299
+ .presets button {
300
+ padding: 6px 12px;
301
+ font-size: 12px;
302
+ }
303
+ </style>
304
+ </head>
305
+ <body>
306
+ <h1>🧠 Brainfuck 可视化解释器</h1>
307
+
308
+ <div class="container">
309
+ <div class="panel">
310
+ <h2>📝 代码</h2>
311
+ <div class="presets">
312
+ <button onclick="loadPreset('hello')">Hello World</button>
313
+ <button onclick="loadPreset('love')">I love your</button>
314
+ </div>
315
+ <textarea id="codeInput" placeholder="输入 Brainfuck 代码..."></textarea>
316
+ <div class="code-display" id="codeDisplay" style="margin-top: 10px"></div>
317
+ </div>
318
+
319
+ <div class="panel">
320
+ <h2>💾 内存 (0-99)</h2>
321
+ <div class="memory-grid" id="memoryGrid"></div>
322
+ </div>
323
+
324
+ <div class="panel io-section">
325
+ <div class="input-area">
326
+ <h2>📥 输入</h2>
327
+ <textarea id="inputArea" placeholder="在此输入字符..."></textarea>
328
+ </div>
329
+ <div class="output-area">
330
+ <h2>📤 输出</h2>
331
+ <pre id="outputArea"></pre>
332
+ </div>
333
+ </div>
334
+
335
+ <div class="controls">
336
+ <button class="btn-start" onclick="run()">▶️ 运行</button>
337
+ <button class="btn-step" onclick="step()">👆 单步</button>
338
+ <button class="btn-reset" onclick="reset()">🔄 重置</button>
339
+ <div class="speed-control">
340
+ <label>速度:</label>
341
+ <input type="range" id="speedSlider" min="10" max="500" value="50" />
342
+ <span id="speedValue">50ms</span>
343
+ </div>
344
+ </div>
345
+
346
+ <div class="status" id="status">
347
+ 准备就绪 - 输入 Brainfuck 代码并点击运行
348
+ </div>
349
+
350
+ <div class="legend">
351
+ <span
352
+ ><div class="dot code"></div>
353
+ 当前代码</span
354
+ >
355
+ <span
356
+ ><div class="dot cell"></div>
357
+ 当前内存</span
358
+ >
359
+ <span
360
+ ><div class="dot changed"></div>
361
+ 值已更改</span
362
+ >
363
+ </div>
364
+ </div>
365
+
366
+ <script src="index.js"></script>
367
+ <script>
368
+ const presets = {
369
+ hello: `>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.
370
+ +++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.
371
+ <<<<.+++.------.--------.>>+.>++++++++++.`,
372
+ love: `
373
+ +++++ +++++
374
+ [->+++++++>+++>++++++++++<<<]
375
+ >+++.>++.>++++++++.+++.++
376
+ +++++.---------------
377
+ --.<.>+++++++++++
378
+ +++++++++.---
379
+ -------.+
380
+ +++++
381
+ .`
382
+ }
383
+
384
+ let code = presets.hello
385
+ let input = ''
386
+ let memory = new Uint8Array(100)
387
+ let ptr = 0
388
+ let codePtr = 0
389
+ let output = []
390
+ let inputPtr = 0
391
+ let stack = []
392
+ let isRunning = false
393
+ let timer = null
394
+ let stepCount = 0
395
+
396
+ const codeInput = document.getElementById('codeInput')
397
+ const codeDisplay = document.getElementById('codeDisplay')
398
+ const memoryGrid = document.getElementById('memoryGrid')
399
+ const inputArea = document.getElementById('inputArea')
400
+ const outputArea = document.getElementById('outputArea')
401
+ const status = document.getElementById('status')
402
+ const speedSlider = document.getElementById('speedSlider')
403
+ const speedValue = document.getElementById('speedValue')
404
+
405
+ // 获取解释器函数 (index.js 暴露的全局变量)
406
+ const bf = window.js2BF
407
+
408
+ function init() {
409
+ codeInput.value = code
410
+ renderCode()
411
+ renderMemory()
412
+ inputArea.value = ''
413
+ outputArea.textContent = ''
414
+ status.innerHTML =
415
+ '准备就绪 - <span class="highlight">输入 Brainfuck 代码并点击运行</span>'
416
+ }
417
+
418
+ function loadPreset(name) {
419
+ code = presets[name]
420
+ codeInput.value = code
421
+ reset()
422
+ }
423
+
424
+ function renderCode() {
425
+ let html = ''
426
+ for (let i = 0; i < code.length; i++) {
427
+ const char = code[i]
428
+ const classes = ['char']
429
+ if (i === codePtr) classes.push('current')
430
+ const displayChar =
431
+ char === '>' ? '&gt;' : char === '<' ? '&lt;' : char
432
+ html += `<span class="${classes.join(' ')}">${displayChar}</span>`
433
+ }
434
+ codeDisplay.innerHTML = html
435
+
436
+ if (codePtr < code.length) {
437
+ codeDisplay.children[codePtr]?.scrollIntoView({
438
+ behavior: 'smooth',
439
+ block: 'nearest',
440
+ inline: 'center'
441
+ })
442
+ }
443
+ }
444
+
445
+ function renderMemory() {
446
+ let html = ''
447
+ for (let i = 0; i < 100; i++) {
448
+ const isActive = i === ptr
449
+ html += `
450
+ <div class="cell ${isActive ? 'active' : ''}" id="cell-${i}">
451
+ <div class="addr">${i.toString().padStart(3, '0')}</div>
452
+ <div class="value">${memory[i]}</div>
453
+ </div>
454
+ `
455
+ }
456
+ memoryGrid.innerHTML = html
457
+ }
458
+
459
+ function updateCell(index) {
460
+ const cell = document.getElementById(`cell-${index}`)
461
+ if (cell) {
462
+ cell.classList.add('changed')
463
+ cell.querySelector('.value').textContent = memory[index]
464
+ setTimeout(() => cell.classList.remove('changed'), 300)
465
+ }
466
+ }
467
+
468
+ function getMatchingBracket(code, pos) {
469
+ const char = code[pos]
470
+ const isForward = char === '['
471
+ const target = isForward ? ']' : '['
472
+ const direction = isForward ? 1 : -1
473
+ let count = 0
474
+
475
+ for (
476
+ let i = pos + direction;
477
+ i >= 0 && i < code.length;
478
+ i += direction
479
+ ) {
480
+ if (code[i] === char) count++
481
+ else if (code[i] === target) {
482
+ count--
483
+ if (count === 0) return i
484
+ }
485
+ }
486
+ return -1
487
+ }
488
+
489
+ function executeCommand() {
490
+ if (codePtr >= code.length) {
491
+ stopExecution()
492
+ status.innerHTML = `执行完成! 共 <span class="highlight">${stepCount}</span> 步`
493
+ return false
494
+ }
495
+
496
+ const char = code[codePtr]
497
+ let nextPtr = codePtr
498
+
499
+ switch (char) {
500
+ case '>':
501
+ ptr = Math.min(99, ptr + 1)
502
+ break
503
+ case '<':
504
+ ptr = Math.max(0, ptr - 1)
505
+ break
506
+ case '+':
507
+ memory[ptr] = (memory[ptr] + 1) % 256
508
+ updateCell(ptr)
509
+ break
510
+ case '-':
511
+ memory[ptr] = (memory[ptr] + 255) % 256
512
+ updateCell(ptr)
513
+ break
514
+ case '.':
515
+ output.push(String.fromCharCode(memory[ptr]))
516
+ outputArea.textContent = output.join('')
517
+ break
518
+ case ',':
519
+ if (inputPtr < input.length) {
520
+ memory[ptr] = input.charCodeAt(inputPtr)
521
+ inputPtr++
522
+ updateCell(ptr)
523
+ } else {
524
+ memory[ptr] = 0
525
+ updateCell(ptr)
526
+ }
527
+ break
528
+ case '[':
529
+ if (memory[ptr] === 0) {
530
+ nextPtr = getMatchingBracket(code, codePtr)
531
+ } else {
532
+ stack.push(codePtr)
533
+ }
534
+ break
535
+ case ']':
536
+ if (memory[ptr] !== 0) {
537
+ nextPtr = stack[stack.length - 1] || codePtr
538
+ } else {
539
+ stack.pop()
540
+ }
541
+ break
542
+ }
543
+
544
+ codePtr = nextPtr + 1
545
+ stepCount++
546
+ return true
547
+ }
548
+
549
+ function updateStatus() {
550
+ const cmds = {
551
+ '>': '向右移动指针',
552
+ '<': '向左移动指针',
553
+ '+': '增加当前单元格值',
554
+ '-': '减少当前单元格值',
555
+ '.': '输出字符',
556
+ ',': '输入字符',
557
+ '[': '循环开始 (值为0时跳过)',
558
+ ']': '循环结束 (值不为0时回跳)'
559
+ }
560
+ const currentCmd = code[codePtr]
561
+ const desc = cmds[currentCmd] || '未知命令'
562
+ status.innerHTML = `步骤 <span class="highlight">${stepCount}</span> | 当前命令: <span class="highlight">${currentCmd}</span> - ${desc} | 指针: <span class="highlight">${ptr}</span> | 值: <span class="highlight">${memory[ptr]}</span>`
563
+ }
564
+
565
+ function run() {
566
+ if (isRunning) return
567
+ code = codeInput.value
568
+ input = inputArea.value
569
+ reset()
570
+ isRunning = true
571
+ executeStep()
572
+ }
573
+
574
+ function executeStep() {
575
+ if (!isRunning) return
576
+ if (!executeCommand()) return
577
+ renderCode()
578
+ renderMemory()
579
+ updateStatus()
580
+
581
+ const speed = parseInt(speedSlider.value)
582
+ timer = setTimeout(executeStep, speed)
583
+ }
584
+
585
+ function step() {
586
+ if (isRunning) {
587
+ clearTimeout(timer)
588
+ isRunning = false
589
+ }
590
+ code = codeInput.value
591
+ input = inputArea.value
592
+ if (executeCommand()) {
593
+ renderCode()
594
+ renderMemory()
595
+ updateStatus()
596
+ }
597
+ }
598
+
599
+ function stopExecution() {
600
+ isRunning = false
601
+ if (timer) {
602
+ clearTimeout(timer)
603
+ timer = null
604
+ }
605
+ }
606
+
607
+ function reset() {
608
+ stopExecution()
609
+ memory = new Uint8Array(100)
610
+ ptr = 0
611
+ codePtr = 0
612
+ inputPtr = 0
613
+ output = []
614
+ stack = []
615
+ stepCount = 0
616
+ code = codeInput.value
617
+ input = inputArea.value
618
+ renderCode()
619
+ renderMemory()
620
+ outputArea.textContent = ''
621
+ status.innerHTML =
622
+ '已重置 - <span class="highlight">点击运行开始执行</span>'
623
+ }
624
+
625
+ speedSlider.addEventListener('input', e => {
626
+ speedValue.textContent = `${e.target.value}ms`
627
+ })
628
+
629
+ init()
630
+ </script>
631
+ </body>
632
+ </html>