From ad2916b018bfb8957ae0e54782f9f453d7cc3687 Mon Sep 17 00:00:00 2001
From: lichx <lchx751176501@gmail.com>
Date: Tue, 19 Nov 2024 21:09:20 +0800
Subject: [PATCH] =?UTF-8?q?[feature]=20=E5=8F=AA=E6=98=AF=E8=83=BD?=
 =?UTF-8?q?=E8=B7=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                           |   24 +
 .vscode/extensions.json              |    3 +
 README.md                            |    5 +
 index.html                           |   13 +
 package-lock.json                    | 1606 ++++++++++++++++++++++++++
 package.json                         |   35 +
 public/vite.svg                      |    1 +
 src/App.vue                          |   32 +
 src/assets/compile.png               |  Bin 0 -> 2512 bytes
 src/assets/play.png                  |  Bin 0 -> 4848 bytes
 src/assets/refresh.png               |  Bin 0 -> 6882 bytes
 src/assets/step.png                  |  Bin 0 -> 3839 bytes
 src/assets/stop.png                  |  Bin 0 -> 6994 bytes
 src/components/CodeInput.vue         |  121 ++
 src/components/ControlPanel.vue      |   87 ++
 src/components/Editors.vue           |  147 +++
 src/components/LEDScreen.vue         |   38 +
 src/components/MachineCodeLayout.vue |   90 ++
 src/components/MemoryLayout.vue      |  163 +++
 src/components/SingleLED.vue         |   54 +
 src/main.ts                          |    5 +
 src/my-asm-grammar                   |   62 +
 src/style.css                        |   28 +
 src/utils/CodeChecker.ts             |   50 +
 src/utils/Complier.ts                |  217 ++++
 src/utils/MemoryManager.ts           |  166 +++
 src/utils/Runner.ts                  |  374 ++++++
 src/utils/parser.js                  |   16 +
 src/utils/parser.terms.js            |   20 +
 src/vite-env.d.ts                    |    6 +
 tsconfig.app.json                    |   27 +
 tsconfig.json                        |    7 +
 tsconfig.node.json                   |   24 +
 vite.config.ts                       |    7 +
 34 files changed, 3428 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .vscode/extensions.json
 create mode 100644 README.md
 create mode 100644 index.html
 create mode 100644 package-lock.json
 create mode 100644 package.json
 create mode 100644 public/vite.svg
 create mode 100644 src/App.vue
 create mode 100644 src/assets/compile.png
 create mode 100644 src/assets/play.png
 create mode 100644 src/assets/refresh.png
 create mode 100644 src/assets/step.png
 create mode 100644 src/assets/stop.png
 create mode 100644 src/components/CodeInput.vue
 create mode 100644 src/components/ControlPanel.vue
 create mode 100644 src/components/Editors.vue
 create mode 100644 src/components/LEDScreen.vue
 create mode 100644 src/components/MachineCodeLayout.vue
 create mode 100644 src/components/MemoryLayout.vue
 create mode 100644 src/components/SingleLED.vue
 create mode 100644 src/main.ts
 create mode 100644 src/my-asm-grammar
 create mode 100644 src/style.css
 create mode 100644 src/utils/CodeChecker.ts
 create mode 100644 src/utils/Complier.ts
 create mode 100644 src/utils/MemoryManager.ts
 create mode 100644 src/utils/Runner.ts
 create mode 100644 src/utils/parser.js
 create mode 100644 src/utils/parser.terms.js
 create mode 100644 src/vite-env.d.ts
 create mode 100644 tsconfig.app.json
 create mode 100644 tsconfig.json
 create mode 100644 tsconfig.node.json
 create mode 100644 vite.config.ts

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..a7cea0b
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..33895ab
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# Vue 3 + TypeScript + Vite
+
+This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..dde16aa
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue + TS</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..0cc3904
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1606 @@
+{
+  "name": "led_simulator",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "led_simulator",
+      "version": "0.0.0",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.18.2",
+        "@codemirror/lang-css": "^6.3.0",
+        "@codemirror/lang-html": "^6.4.9",
+        "@codemirror/lang-javascript": "^6.2.2",
+        "@codemirror/lint": "^6.8.2",
+        "@codemirror/theme-one-dark": "^6.1.2",
+        "@codemirror/view": "^6.34.2",
+        "@lezer/lr": "^1.0.0",
+        "codemirror": "^6.0.1",
+        "codemirror-one-dark-theme": "^1.1.1",
+        "lezer": "^0.13.5",
+        "vue": "^3.5.12",
+        "vue-codemirror": "^6.1.1"
+      },
+      "devDependencies": {
+        "@lezer/generator": "^1.0.0",
+        "@types/node": "^22.9.0",
+        "@vitejs/plugin-vue": "^5.1.4",
+        "typescript": "~5.6.2",
+        "vite": "^5.4.10",
+        "vue-tsc": "^2.1.8"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.26.2",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz",
+      "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.26.0"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.26.0",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz",
+      "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.25.9",
+        "@babel/helper-validator-identifier": "^7.25.9"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@codemirror/autocomplete": {
+      "version": "6.18.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.18.2.tgz",
+      "integrity": "sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/commands": {
+      "version": "6.7.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.7.1.tgz",
+      "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.4.0",
+        "@codemirror/view": "^6.27.0",
+        "@lezer/common": "^1.1.0"
+      }
+    },
+    "node_modules/@codemirror/lang-css": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-css/-/lang-css-6.3.0.tgz",
+      "integrity": "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@lezer/common": "^1.0.2",
+        "@lezer/css": "^1.1.7"
+      }
+    },
+    "node_modules/@codemirror/lang-html": {
+      "version": "6.4.9",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
+      "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/lang-css": "^6.0.0",
+        "@codemirror/lang-javascript": "^6.0.0",
+        "@codemirror/language": "^6.4.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/css": "^1.1.0",
+        "@lezer/html": "^1.3.0"
+      }
+    },
+    "node_modules/@codemirror/lang-javascript": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
+      "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.6.0",
+        "@codemirror/lint": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.17.0",
+        "@lezer/common": "^1.0.0",
+        "@lezer/javascript": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/language": {
+      "version": "6.10.3",
+      "resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.10.3.tgz",
+      "integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.23.0",
+        "@lezer/common": "^1.1.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0",
+        "style-mod": "^4.0.0"
+      }
+    },
+    "node_modules/@codemirror/lint": {
+      "version": "6.8.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.8.2.tgz",
+      "integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "crelt": "^1.0.5"
+      }
+    },
+    "node_modules/@codemirror/search": {
+      "version": "6.5.7",
+      "resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.5.7.tgz",
+      "integrity": "sha512-6+iLsXvITWKHYlkgHPCs/qiX4dNzn8N78YfhOFvPtPYCkuXqZq10rAfsUMhOq7O/1VjJqdXRflyExlfVcu/9VQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "crelt": "^1.0.5"
+      }
+    },
+    "node_modules/@codemirror/state": {
+      "version": "6.4.1",
+      "resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.4.1.tgz",
+      "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==",
+      "license": "MIT"
+    },
+    "node_modules/@codemirror/theme-one-dark": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+      "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/highlight": "^1.0.0"
+      }
+    },
+    "node_modules/@codemirror/view": {
+      "version": "6.34.2",
+      "resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.34.2.tgz",
+      "integrity": "sha512-d6n0WFvL970A9Z+l9N2dO+Hk9ev4hDYQzIx+B9tCyBP0W5wPEszi1rhuyFesNSkLZzXbQE5FPH7F/z/TMJfoPA==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/state": "^6.4.0",
+        "style-mod": "^4.1.0",
+        "w3c-keyname": "^2.2.4"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+      "license": "MIT"
+    },
+    "node_modules/@lezer/common": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz",
+      "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
+      "license": "MIT"
+    },
+    "node_modules/@lezer/css": {
+      "version": "1.1.9",
+      "resolved": "https://registry.npmmirror.com/@lezer/css/-/css-1.1.9.tgz",
+      "integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/generator": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmmirror.com/@lezer/generator/-/generator-1.7.1.tgz",
+      "integrity": "sha512-MgPJN9Si+ccxzXl3OAmCeZuUKw4XiPl4y664FX/hnnyG9CTqUPq65N3/VGPA2jD23D7QgMTtNqflta+cPN+5mQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.1.0",
+        "@lezer/lr": "^1.3.0"
+      },
+      "bin": {
+        "lezer-generator": "src/lezer-generator.cjs"
+      }
+    },
+    "node_modules/@lezer/highlight": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.1.tgz",
+      "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/html": {
+      "version": "1.3.10",
+      "resolved": "https://registry.npmmirror.com/@lezer/html/-/html-1.3.10.tgz",
+      "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
+    "node_modules/@lezer/javascript": {
+      "version": "1.4.19",
+      "resolved": "https://registry.npmmirror.com/@lezer/javascript/-/javascript-1.4.19.tgz",
+      "integrity": "sha512-j44kbR1QL26l6dMunZ1uhKBFteVGLVCBGNUD2sUaMnic+rbTviVuoK0CD1l9FTW31EueWvFFswCKMH7Z+M3JRA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.2.0",
+        "@lezer/highlight": "^1.1.3",
+        "@lezer/lr": "^1.3.0"
+      }
+    },
+    "node_modules/@lezer/lr": {
+      "version": "1.4.2",
+      "resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.2.tgz",
+      "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+      "license": "MIT",
+      "dependencies": {
+        "@lezer/common": "^1.0.0"
+      }
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz",
+      "integrity": "sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz",
+      "integrity": "sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz",
+      "integrity": "sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz",
+      "integrity": "sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz",
+      "integrity": "sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz",
+      "integrity": "sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz",
+      "integrity": "sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz",
+      "integrity": "sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz",
+      "integrity": "sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz",
+      "integrity": "sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz",
+      "integrity": "sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz",
+      "integrity": "sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz",
+      "integrity": "sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz",
+      "integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz",
+      "integrity": "sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz",
+      "integrity": "sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz",
+      "integrity": "sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz",
+      "integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "22.9.0",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.9.0.tgz",
+      "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.19.8"
+      }
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.1.4",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz",
+      "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@volar/language-core": {
+      "version": "2.4.9",
+      "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.9.tgz",
+      "integrity": "sha512-t++GIrUeQnKCieZdY9e+Uar2VmTqOE4Z9KcEcdSHKmKZPuqpbbWow1YKe1i3HpU2s1JqLRVM8y/n87WKXyxJAg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/source-map": "2.4.9"
+      }
+    },
+    "node_modules/@volar/source-map": {
+      "version": "2.4.9",
+      "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.9.tgz",
+      "integrity": "sha512-UGE+WgJwk64OcfBwBOBKIzmF+uNx4dC5GzOvaVsHbTBp/IVqeTVsGiO5CwBAt6l3vVXYbMuddG2DU8FEnBRxTg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@volar/typescript": {
+      "version": "2.4.9",
+      "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.9.tgz",
+      "integrity": "sha512-Zmh3Bq8CFD6OANKYsi4vs/l7togwfjFH0kgrT12uAsDff2AJQjbEUKTVUnxmHbnbH2B9ja7Lb6Mu/Wj9wBuJlg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "2.4.9",
+        "path-browserify": "^1.0.1",
+        "vscode-uri": "^3.0.8"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.12.tgz",
+      "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/shared": "3.5.12",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz",
+      "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.12",
+        "@vue/shared": "3.5.12"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz",
+      "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/compiler-core": "3.5.12",
+        "@vue/compiler-dom": "3.5.12",
+        "@vue/compiler-ssr": "3.5.12",
+        "@vue/shared": "3.5.12",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.11",
+        "postcss": "^8.4.47",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz",
+      "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.12",
+        "@vue/shared": "3.5.12"
+      }
+    },
+    "node_modules/@vue/compiler-vue2": {
+      "version": "2.7.16",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
+      "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "de-indent": "^1.0.2",
+        "he": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/language-core": {
+      "version": "2.1.10",
+      "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.1.10.tgz",
+      "integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/language-core": "~2.4.8",
+        "@vue/compiler-dom": "^3.5.0",
+        "@vue/compiler-vue2": "^2.7.16",
+        "@vue/shared": "^3.5.0",
+        "alien-signals": "^0.2.0",
+        "minimatch": "^9.0.3",
+        "muggle-string": "^0.4.1",
+        "path-browserify": "^1.0.1"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.12.tgz",
+      "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/shared": "3.5.12"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.12.tgz",
+      "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.12",
+        "@vue/shared": "3.5.12"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz",
+      "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/reactivity": "3.5.12",
+        "@vue/runtime-core": "3.5.12",
+        "@vue/shared": "3.5.12",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.12.tgz",
+      "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.12",
+        "@vue/shared": "3.5.12"
+      },
+      "peerDependencies": {
+        "vue": "3.5.12"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.12.tgz",
+      "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==",
+      "license": "MIT"
+    },
+    "node_modules/alien-signals": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-0.2.0.tgz",
+      "integrity": "sha512-StlonZhBBrsPPwrDjiPAiVTf/rolxffLxVPT60Qv/t88BZ81BvUVzHgGqEFvJ1ii8HXtm1+zU2Icr59tfWEcag==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/codemirror": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz",
+      "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/commands": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/lint": "^6.0.0",
+        "@codemirror/search": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0"
+      }
+    },
+    "node_modules/codemirror-one-dark-theme": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/codemirror-one-dark-theme/-/codemirror-one-dark-theme-1.1.1.tgz",
+      "integrity": "sha512-SuGz+mjIhWZcU59PQT1KQL8YDdCZOB7zWyPSKC9T8KCjiFCsNaRnaOpI9LDamHUx8X+a7tEfiogemx6L/WDZCg==",
+      "license": "MIT"
+    },
+    "node_modules/crelt": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
+      "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+      "license": "MIT"
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+      "license": "MIT"
+    },
+    "node_modules/de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.21.5",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.21.5",
+        "@esbuild/android-arm": "0.21.5",
+        "@esbuild/android-arm64": "0.21.5",
+        "@esbuild/android-x64": "0.21.5",
+        "@esbuild/darwin-arm64": "0.21.5",
+        "@esbuild/darwin-x64": "0.21.5",
+        "@esbuild/freebsd-arm64": "0.21.5",
+        "@esbuild/freebsd-x64": "0.21.5",
+        "@esbuild/linux-arm": "0.21.5",
+        "@esbuild/linux-arm64": "0.21.5",
+        "@esbuild/linux-ia32": "0.21.5",
+        "@esbuild/linux-loong64": "0.21.5",
+        "@esbuild/linux-mips64el": "0.21.5",
+        "@esbuild/linux-ppc64": "0.21.5",
+        "@esbuild/linux-riscv64": "0.21.5",
+        "@esbuild/linux-s390x": "0.21.5",
+        "@esbuild/linux-x64": "0.21.5",
+        "@esbuild/netbsd-x64": "0.21.5",
+        "@esbuild/openbsd-x64": "0.21.5",
+        "@esbuild/sunos-x64": "0.21.5",
+        "@esbuild/win32-arm64": "0.21.5",
+        "@esbuild/win32-ia32": "0.21.5",
+        "@esbuild/win32-x64": "0.21.5"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+      "license": "MIT"
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "he": "bin/he"
+      }
+    },
+    "node_modules/lezer": {
+      "version": "0.13.5",
+      "resolved": "https://registry.npmmirror.com/lezer/-/lezer-0.13.5.tgz",
+      "integrity": "sha512-cAiMQZGUo2BD8mpcz7Nv1TlKzWP7YIdIRrX41CiP5bk5t4GHxskOxWUx2iAOuHlz8dO+ivbuXr0J1bfHsWD+lQ==",
+      "deprecated": "This package has been replaced by @lezer/lr",
+      "license": "MIT",
+      "dependencies": {
+        "lezer-tree": "^0.13.2"
+      }
+    },
+    "node_modules/lezer-tree": {
+      "version": "0.13.2",
+      "resolved": "https://registry.npmmirror.com/lezer-tree/-/lezer-tree-0.13.2.tgz",
+      "integrity": "sha512-15ZxW8TxVNAOkHIo43Iouv4zbSkQQ5chQHBpwXcD2bBFz46RB4jYLEEww5l1V0xyIx9U2clSyyrLes+hAUFrGQ==",
+      "deprecated": "This package has been replaced by @lezer/common",
+      "license": "MIT"
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.12",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.12.tgz",
+      "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/muggle-string": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
+      "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.7",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/path-browserify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
+      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "license": "ISC"
+    },
+    "node_modules/postcss": {
+      "version": "8.4.47",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.47.tgz",
+      "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "nanoid": "^3.3.7",
+        "picocolors": "^1.1.0",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.24.4.tgz",
+      "integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "1.0.6"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.24.4",
+        "@rollup/rollup-android-arm64": "4.24.4",
+        "@rollup/rollup-darwin-arm64": "4.24.4",
+        "@rollup/rollup-darwin-x64": "4.24.4",
+        "@rollup/rollup-freebsd-arm64": "4.24.4",
+        "@rollup/rollup-freebsd-x64": "4.24.4",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.24.4",
+        "@rollup/rollup-linux-arm-musleabihf": "4.24.4",
+        "@rollup/rollup-linux-arm64-gnu": "4.24.4",
+        "@rollup/rollup-linux-arm64-musl": "4.24.4",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.24.4",
+        "@rollup/rollup-linux-riscv64-gnu": "4.24.4",
+        "@rollup/rollup-linux-s390x-gnu": "4.24.4",
+        "@rollup/rollup-linux-x64-gnu": "4.24.4",
+        "@rollup/rollup-linux-x64-musl": "4.24.4",
+        "@rollup/rollup-win32-arm64-msvc": "4.24.4",
+        "@rollup/rollup-win32-ia32-msvc": "4.24.4",
+        "@rollup/rollup-win32-x64-msvc": "4.24.4",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/semver": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/style-mod": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz",
+      "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
+      "license": "MIT"
+    },
+    "node_modules/typescript": {
+      "version": "5.6.3",
+      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz",
+      "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+      "devOptional": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.19.8",
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz",
+      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/vite": {
+      "version": "5.4.10",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.10.tgz",
+      "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vscode-uri": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.0.8.tgz",
+      "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/vue": {
+      "version": "3.5.12",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.12.tgz",
+      "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==",
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.12",
+        "@vue/compiler-sfc": "3.5.12",
+        "@vue/runtime-dom": "3.5.12",
+        "@vue/server-renderer": "3.5.12",
+        "@vue/shared": "3.5.12"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-codemirror": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmmirror.com/vue-codemirror/-/vue-codemirror-6.1.1.tgz",
+      "integrity": "sha512-rTAYo44owd282yVxKtJtnOi7ERAcXTeviwoPXjIc6K/IQYUsoDkzPvw/JDFtSP6T7Cz/2g3EHaEyeyaQCKoDMg==",
+      "license": "MIT",
+      "dependencies": {
+        "@codemirror/commands": "6.x",
+        "@codemirror/language": "6.x",
+        "@codemirror/state": "6.x",
+        "@codemirror/view": "6.x"
+      },
+      "peerDependencies": {
+        "codemirror": "6.x",
+        "vue": "3.x"
+      }
+    },
+    "node_modules/vue-tsc": {
+      "version": "2.1.10",
+      "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.1.10.tgz",
+      "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@volar/typescript": "~2.4.8",
+        "@vue/language-core": "2.1.10",
+        "semver": "^7.5.4"
+      },
+      "bin": {
+        "vue-tsc": "bin/vue-tsc.js"
+      },
+      "peerDependencies": {
+        "typescript": ">=5.0.0"
+      }
+    },
+    "node_modules/w3c-keyname": {
+      "version": "2.2.8",
+      "resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+      "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+      "license": "MIT"
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..556bde6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "led_simulator",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vue-tsc -b && vite build",
+    "preview": "vite preview",
+    "prepare": "lezer-generator src/my-asm-grammar -o src/parser.js"
+  },
+  "dependencies": {
+    "@codemirror/autocomplete": "^6.18.2",
+    "@codemirror/lang-css": "^6.3.0",
+    "@codemirror/lang-html": "^6.4.9",
+    "@codemirror/lang-javascript": "^6.2.2",
+    "@codemirror/lint": "^6.8.2",
+    "@codemirror/theme-one-dark": "^6.1.2",
+    "@codemirror/view": "^6.34.2",
+    "@lezer/lr": "^1.0.0",
+    "codemirror": "^6.0.1",
+    "codemirror-one-dark-theme": "^1.1.1",
+    "lezer": "^0.13.5",
+    "vue": "^3.5.12",
+    "vue-codemirror": "^6.1.1"
+  },
+  "devDependencies": {
+    "@lezer/generator": "^1.0.0",
+    "@types/node": "^22.9.0",
+    "@vitejs/plugin-vue": "^5.1.4",
+    "typescript": "~5.6.2",
+    "vite": "^5.4.10",
+    "vue-tsc": "^2.1.8"
+  }
+}
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..9d83dc4
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,32 @@
+<script setup lang="ts">
+import {provide} from "vue";
+import LEDScreen from "./components/LEDScreen.vue";
+import {MemoryManager} from "./utils/MemoryManager.ts";
+import {CodeChecker} from "./utils/CodeChecker.ts";
+import "./style.css";
+import Editors from "./components/Editors.vue";
+import {Runner} from "./utils/Runner.ts";
+import ControlPanel from "./components/ControlPanel.vue";
+import {Compiler} from "./utils/Complier.ts";
+
+let memManager = new MemoryManager();
+let compiler = new Compiler();
+provide("compiler", compiler);
+provide("memoryManager", memManager);
+provide("codeChecker", new CodeChecker());
+provide("runner", new Runner(memManager,compiler));
+</script>
+
+<template>
+    <h1>LED显示屏模拟器</h1>
+    <p>made by li_chx</p>
+    <LEDScreen/>
+    <ControlPanel/>
+    <Editors/>
+
+</template>
+
+<style scoped>
+
+
+</style>
diff --git a/src/assets/compile.png b/src/assets/compile.png
new file mode 100644
index 0000000000000000000000000000000000000000..38bde3d8a0da76134e5d80c297be7b42fd787d3f
GIT binary patch
literal 2512
zcmbVO2~ZPP7><Za@Mx=yrHZmHSZm8>a}Y>YBPaoa8c_)a9UR&0F0kg<kX=kbgrS`(
zRE^M%$5_-lT6LfvSP?6h>ZsL@mMWciHrje)6l55%fMVZrWJGBl&CKTg|Gw{i|M~ve
zqPcTohx?B46$k{w<235|U_HY>K7+ykp5(n-z#_D07O(=r&{6y|K(MLQUm);qpmoVy
zvUWB`FeVXBGI~m6H(3B$Aeb6qx8Q_<;vhYhPMcM5WA)E4M3X8wNv;)ZEm2ekt;w-c
z2|06gM2>+_l5oT{XsR6p1SX2ZA-l<FW-+@8?&8IO%)drqs0+dwRPc1(Ae5|~3q>(j
z3Q~$B2q6|rpm3!KSCFCMa#;u@6-#BPScXbvh**Ki!!TJm)boRZIV+il%~!|t7z3IL
z&fqu;hN3o`O=Oda7;8EzQ7V<FSc*!e2tXigwwc52h?x!QWl&QrVWlk`&6pvc5!W+W
zoC*f6cE@0{cxcURPo6-=P&;lxB_c5&Qx}jVJUC01)!1d6Bv8sonJ6>I0<6S?wPY|H
z!)7r5LG9V@V*un<tM%CE*B6t?V}j+Pmx3^Q64Ec4)n!{KbUwv0SyqCIUJBd{;-j%(
zQC14)7^{w9jJ=hb+v_qU4Hbn!A0^RdlCiOqUpPRiagI{KplYFrM1n}dbP_oxQ(!XL
zC!l;{aW_=UkaSvhzfdJ6?F$7xL*g9%S};jqX^ho`gMeuho=%|_b2<!p(uhSdM#c&Z
z1Mg&AUTC#goSEftGeO0vRWOKIMAIZj5E490CX*pDad<c)(T9d3dQuXKD5O#eh06$q
zK26?xUd<3$d}VvjlW%xF-b#b3g&W_Rhre)qOJEwB1;xtlxe*CeW{+f~p{_2#aDwk{
z6-@ADrAWBvHT~8Z=%%%0P=NG*%H0iPnKaIZTdC>kAX~3(Clol3^7pVi4)o;^yY^nV
zv%PRI2zb#mf`H-~Vw4$7E-M&Om-GJq6->UuIQ4X$z4Xza*XG^+)NjQF<XAT6t4<9l
zS_M6g^qzWIxjL$Tm}Xn^{vmDn>?<b<x7OURt6Si1^4o1osxJ>JJ73>kgQi~ot#05k
z`6wT6uYySE-9F3Vu?rs_F*hh{TRYA+M=jo0{oTF4mMwAx<W=WiMOCLKujni%#l`Zr
z>IaV#vKF_Ox$1s+tWC<gUp{`r7GhX={>hvp`PkD$7rMxa*DtWI+P*C1T0>KbbIAOO
zUl}SY6=h)~HS09%51j&%A1@tn0%<gm?jE`WB!6By&;q0}K-zh6MnK}&5j$oEC(sie
zWovkrib}uO#DN96A~Iypsd-KflnDQ*Ta~Gk^K`Y&B>{<R1_4+?Wk77==Kyv)dC%#2
z&I|x+Tdk;}!-_vH$yEnDr!gQLo#beg2ZD3g3vb1GwI#Vr3W<%8ox7ZxlTDI>qkk+6
zzIn!5`t3TPDhqN`!S*eMm+;V-B86j8al8>3nOPbG#a^Bnd{ePG(s8g=b24*$L22q9
zfXnq#UbqUscbf9HuBcSQj<dqsvoDP=Qv5QZcxZ|t_@@8bND*fO_ENk)IL-r_gk<on
zSlB`Pkm^~{u%q6GBw1b;l3pyY3rRQu=-EExj0B*!`H*^o*M;O@g4c!QhZ|yG2jxS?
z+ym<!A2MwhuM5cmr+HmSPS^@?Zm4+Ba=#+Q-j$J~Tc?f8OnJW`rJzbvwgh#!>sppS
zzoox~h<!s0yT7R37MPotecg4@ePUx^XQjC{{z?2w$-cO6b{>wXKBCk)JD;_uLRBs8
z=M$e$=LVSFEfr;*0nM8Z*6&@C6m(;&t7;khJkNFGr55YkaQ$iGf9B+zZQr&Xi8;R}
zS9fmV-IOU)`gCc3hx)T}jzES?_@1i0qHx~cTv_^{V!iTG?TA;q{xy8%d6Rx>?8y7l
z?RZvGrM%S9d30PqSLZb(UdPMcF(seH1$R@>w~6MnD<e$z%Q{-tZn(EKzHM_&yGwm*
zzkX3mb>7b3H#O_NURiUueWCG6sjVb7<V}<1pF8Q9!S#@Fw`4ZpsY(}ascam#LlD?h
gIOvrL!H0P~VleZ_kdKI$^50x>(R0*$XQY1iFP?#A@&Et;

literal 0
HcmV?d00001

diff --git a/src/assets/play.png b/src/assets/play.png
new file mode 100644
index 0000000000000000000000000000000000000000..dd0c18e36f99718e0657240009ae58d149ab9fe7
GIT binary patch
literal 4848
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

literal 0
HcmV?d00001

diff --git a/src/assets/refresh.png b/src/assets/refresh.png
new file mode 100644
index 0000000000000000000000000000000000000000..f203cc51cdcd7f0782e3c31b17be45e708579fb1
GIT binary patch
literal 6882
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
Jxt`mV{{m4ScxC_q

literal 0
HcmV?d00001

diff --git a/src/assets/step.png b/src/assets/step.png
new file mode 100644
index 0000000000000000000000000000000000000000..89a66851b88ea57347287881a7829940c34bf9a0
GIT binary patch
literal 3839
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

literal 0
HcmV?d00001

diff --git a/src/assets/stop.png b/src/assets/stop.png
new file mode 100644
index 0000000000000000000000000000000000000000..2a8f983dfe507f50091f24898e363ba9b5c921e0
GIT binary patch
literal 6994
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

literal 0
HcmV?d00001

diff --git a/src/components/CodeInput.vue b/src/components/CodeInput.vue
new file mode 100644
index 0000000..961378a
--- /dev/null
+++ b/src/components/CodeInput.vue
@@ -0,0 +1,121 @@
+<script setup lang="ts">
+import {inject, ref} from "vue";
+import "../style.css"
+import {CodeChecker} from "../utils/CodeChecker.ts";
+import {Codemirror} from "vue-codemirror";
+// @ts-ignore
+import {parser} from "../utils/parser.js"
+import {foldNodeProp, foldInside} from "@codemirror/language"
+import {styleTags, tags as t} from "@lezer/highlight"
+import {LRLanguage} from "@codemirror/language"
+import {completeFromList} from "@codemirror/autocomplete"
+import {LanguageSupport} from "@codemirror/language"
+import {oneDark} from "@codemirror/theme-one-dark"
+import {keymap} from "@codemirror/view"
+import {Compiler} from "../utils/Complier.ts";
+import {insertTab} from "@codemirror/commands"
+
+let compiler = inject("compiler") as Compiler;
+
+
+let parserWithMetadata = parser.configure({
+    props: [
+        styleTags({
+            Mnemonic: t.keyword,
+            Register: t.variableName,
+            ImmediateToken: t.number,
+            CommentToken: t.lineComment,
+            LabelToken: t.labelName,
+            Db: t.definitionKeyword,
+            Dw: t.definitionKeyword,
+            NumberList: t.literal,
+            StringList: t.literal
+        }),
+        foldNodeProp.add({
+            Statement: foldInside
+        })
+    ]
+})
+
+const myASMLanguage = LRLanguage.define({
+    parser: parserWithMetadata,
+    languageData: {
+        commentTokens: {line: ";"}
+    }
+})
+
+
+const myASMCompletion = myASMLanguage.data.of({
+    autocomplete: completeFromList([
+        ...CodeChecker.KeyWords.map(keyword => ({label: keyword, type: "keyword"})),
+        ...CodeChecker.Registers.map(register => ({label: register, type: "variableName"}))
+    ])
+})
+
+function languageSupport() {
+    return new LanguageSupport(myASMLanguage, [myASMCompletion])
+}
+
+let code = ref("t: dw 'Hello World'\n" +
+    "mov \tAX, t\n" +
+    "ldc \tCX, 0\n" +
+    "ldc \tDX, 8\n" +
+    "ldc \tEX, 0\n" +
+    "ldc\t\tFX, 0\n" +
+    "ldc\t\tGX, 8\n" +
+    "lp1:\n" +
+    "ldc \tBX, 1\n" +
+    "call \t0,  lp\n" +
+    "add \tAX, DX\n" +
+    "add \tCX, BX\n" +
+    "add\t\tFX, BX\n" +
+    "mov \tBX, FX\n" +
+    "ltjp\tGX, lp1\n" +
+    "\n" +
+    "ldc \tCX, 64\n" +
+    "ldc\t\tFX, 0\n" +
+    "ldc\t\tGX, 3\n" +
+    "lp2:\n" +
+    "ldc \tBX, 1\n" +
+    "call \t0,  lp\n" +
+    "add \tAX, DX\n" +
+    "add \tCX, BX\n" +
+    "add\t\tFX, BX\n" +
+    "mov \tBX, FX\n" +
+    "ltjp\tGX, lp2\n" +
+    "\n" +
+    "\n" +
+    "ret \t0,0\n" +
+    "lp:\n" +
+    "ldc\t\tBX, 1\n" +
+    "cp\t\tAX, CX\n" +
+    "add\t\tAX, BX\n" +
+    "add\t\tCX, DX\n" +
+    "add\t\tEX, BX\n" +
+    "mov\t\tBX, EX\n" +
+    "ltjp\tDX,\tlp\n" +
+    "ret\t\t0,0");
+// compiler.Code = code.value;
+compiler.Code = code.value;
+
+function onCodeChange() {
+    compiler.Code = code.value;
+    compiler.CodeChanged = true;
+}
+</script>
+
+<template>
+    <div>
+        <h2>Code Input</h2>
+        <codemirror class="CodeMirror custom-codemirror" v-model="code" style="height: 500px;"
+                    :extensions="[languageSupport(), oneDark, keymap.of([{key: 'Tab', run: (view) => insertTab(view)}])]"
+                    :options="{lineNumbers: true}"
+                    @update:modelValue="onCodeChange"/>
+    </div>
+</template>
+
+<style scoped>
+.custom-codemirror {
+    font-size: 20px;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/ControlPanel.vue b/src/components/ControlPanel.vue
new file mode 100644
index 0000000..e947463
--- /dev/null
+++ b/src/components/ControlPanel.vue
@@ -0,0 +1,87 @@
+<script setup lang="ts">
+
+import {Runner, RunnerState} from "../utils/Runner.ts";
+import {inject} from "vue";
+import {Compiler} from "../utils/Complier.ts";
+let runner:Runner = inject("runner") as Runner;
+console.log(runner);
+let compiler = inject("compiler") as Compiler;
+function compileCode() {
+    runner.Stop();
+    compiler.CompileCode();
+}
+function startOrContinue() {
+    switch (runner.State)
+    {
+        case RunnerState.Running:
+            break;
+        case RunnerState.Step:
+            runner.Resume();
+            break;
+        case RunnerState.Stop:
+            runner.Run();
+            break;
+    }
+}
+function step() {
+    if(compiler.CodeChanged)
+    {
+        compiler.CompileCode();
+    }
+    runner.Step();
+}
+function stop() {
+    runner.Stop();
+}
+function refresh() {
+    runner.Refresh();
+}
+</script>
+
+<template>
+    <div id="controlPanel">
+        <div></div>
+        <div id="buttons">
+            <img class="button" src="../assets/compile.png" alt="编译" @click="compileCode">
+            <img class="button" src="../assets/play.png" alt="运行/恢复运行" @click="startOrContinue">
+            <img class="button" src="../assets/step.png" alt="步过" @click="step">
+            <img class="button" src="../assets/stop.png" alt="停止" @click="stop">
+            <img class="button" src="../assets/refresh.png" alt="重新调试" @click="refresh">
+        </div>
+    </div>
+
+</template>
+
+<style scoped>
+#controlPanel {
+    margin-top: 20px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+}
+#controlPanel > :first-child {
+    flex: 1;
+}
+#controlPanel > :last-child {
+    flex: 0;
+}
+
+#buttons{
+    display: flex;
+}
+
+.button{
+    flex: 0;
+    width: 20px;
+    height: 20px;
+    margin: 0 3px;
+    padding: 5px;
+    cursor: pointer;
+    border-radius: 5px;
+
+}
+.button:hover{
+    background-color: #555555;
+}
+
+</style>
\ No newline at end of file
diff --git a/src/components/Editors.vue b/src/components/Editors.vue
new file mode 100644
index 0000000..cc03333
--- /dev/null
+++ b/src/components/Editors.vue
@@ -0,0 +1,147 @@
+<script setup lang="ts">
+
+import CodeInput from "./CodeInput.vue";
+import MemoryLayout from "./MemoryLayout.vue";
+import MachineCodeLayout from "./MachineCodeLayout.vue";
+import "../style.css";
+import {computed, ref} from "vue";
+
+const startX = ref(0);
+const parentWidth = ref(0);
+const codeInputFlex = ref(1);
+const machineCodeLayoutFlex = ref(1);
+const memoryLayoutFlex = ref(1);
+const startCodeInputFlex = ref(1);
+const startMachineCodeLayoutFlex = ref(1);
+const startMemoryLayoutFlex = ref(1);
+const movingSplitId = ref("");
+const cssVars = computed(() => ({
+        '--code-input-flex': codeInputFlex.value,
+        '--machine-code-layout-flex': machineCodeLayoutFlex.value,
+        '--memory-layout-flex': memoryLayoutFlex.value
+    })
+)
+
+function onMouseDown(event: MouseEvent) {
+    const htmlElement = event.target as HTMLElement;
+    parentWidth.value = htmlElement.parentElement!.offsetWidth - 12;
+
+    startX.value = event.clientX;
+    startCodeInputFlex.value = codeInputFlex.value;
+    startMachineCodeLayoutFlex.value = machineCodeLayoutFlex.value;
+    startMemoryLayoutFlex.value = memoryLayoutFlex.value;
+    if (htmlElement.id === "split1") {
+        movingSplitId.value = "split1"
+    } else if (htmlElement.id === "split2") {
+        movingSplitId.value = "split2"
+    }
+    document.body.addEventListener('mousemove', onMouseMove);
+    document.body.addEventListener('mouseup', onMouseUp);
+}
+
+function onMouseMove(event: MouseEvent) {
+    const minLimit = 500 / parentWidth.value;
+
+    function limit(t: number, maxLimit: number): number {
+        t = Math.max(t, minLimit);
+        t = Math.min(t, maxLimit);
+        return t;
+    }
+
+    if (movingSplitId.value === "split1") {
+        const maxLimit = startCodeInputFlex.value + startMachineCodeLayoutFlex.value - minLimit;
+        codeInputFlex.value = limit((startCodeInputFlex.value * parentWidth.value + 3 * (event.clientX - startX.value)) / parentWidth.value, maxLimit);
+        machineCodeLayoutFlex.value = limit((startMachineCodeLayoutFlex.value * parentWidth.value - 3 * (event.clientX - startX.value)) / parentWidth.value, maxLimit);
+    } else if (movingSplitId.value === "split2") {
+        const maxLimit = startMachineCodeLayoutFlex.value + startMemoryLayoutFlex.value - minLimit;
+        machineCodeLayoutFlex.value = limit((startMachineCodeLayoutFlex.value * parentWidth.value + 3 * (event.clientX - startX.value)) / parentWidth.value, maxLimit);
+        memoryLayoutFlex.value = limit((startMemoryLayoutFlex.value * parentWidth.value - 3 * (event.clientX - startX.value)) / parentWidth.value, maxLimit);
+    }
+
+}
+
+function onMouseUp(event: MouseEvent) {
+    document.body.removeEventListener('mousemove', onMouseMove);
+    document.body.removeEventListener('mouseup', onMouseUp);
+    onMouseMove(event)
+    startMemoryLayoutFlex.value = memoryLayoutFlex.value;
+    startMachineCodeLayoutFlex.value = machineCodeLayoutFlex.value;
+    startCodeInputFlex.value = codeInputFlex.value;
+}
+</script>
+
+<template>
+    <div id="editors">
+        <div id="code">
+            <CodeInput :style="cssVars"/>
+            <div id="split1" class="split" @mousedown="onMouseDown"></div>
+            <MachineCodeLayout :style="cssVars"/>
+            <div id="split2" class="split" @mousedown="onMouseDown"></div>
+            <MemoryLayout :style="cssVars"/>
+        </div>
+    </div>
+</template>
+
+<style scoped>
+
+#editors {
+    margin-top: 5px;
+    padding: 5px;
+    border-radius: 15px;
+    background-color: rgb(55, 55, 55);
+}
+
+@media screen and (min-width: 900px) {
+    #code {
+        display: flex;
+        margin: 0;
+        padding: 0;
+    }
+}
+
+#code > * {
+    background-color: rgb(30, 30, 30);
+    text-align: left;
+    flex: 3;
+    margin: 0;
+}
+
+#code > :nth-child(1) {
+    border-bottom-left-radius: 15px;
+    border-top-left-radius: 15px;
+    flex: var(--code-input-flex);
+    overflow: hidden;
+    min-width: 100px;
+}
+
+#code > :nth-child(3) {
+    flex: var(--machine-code-layout-flex);
+    overflow: hidden;
+    min-width: 100px;
+}
+
+#code > :nth-child(5) {
+    border-bottom-right-radius: 15px;
+    border-top-right-radius: 15px;
+
+    textarea {
+        border-bottom-right-radius: 15px;
+    }
+
+    flex: var(--memory-layout-flex);
+    overflow: hidden;
+    min-width: 100px;
+}
+
+#code .split {
+    flex: 0;
+    cursor: ew-resize;
+    background-color: rgb(55, 55, 55);
+    padding-right: 6px;
+}
+
+#code .split:hover {
+    background-color: rgb(70, 70, 70);
+
+}
+</style>
\ No newline at end of file
diff --git a/src/components/LEDScreen.vue b/src/components/LEDScreen.vue
new file mode 100644
index 0000000..5ff53ec
--- /dev/null
+++ b/src/components/LEDScreen.vue
@@ -0,0 +1,38 @@
+<script setup lang="ts">
+import {computed, inject} from "vue";
+import SingleLED from "./SingleLED.vue";
+import {MemoryManager} from "../utils/MemoryManager.ts";
+
+let memoryManager = inject("memoryManager") as MemoryManager;
+let screenMemory = computed(() => {
+    return memoryManager.screenMemory.value.map(row => row.map(byte => byte.value));
+})
+</script>
+
+<template>
+    <div class="screen">
+        <div class="screen-row" v-for="(row, rowIndex) in screenMemory" :key="rowIndex">
+            <div v-for="(col, colIndex) in row" :key="colIndex" class="led-group">
+                <SingleLED v-for="(led, ledIndex) in col.slice().reverse()" :rowIndex="rowIndex" :colIndex="colIndex"
+                           :ledIndex="ledIndex" v-bind:isActive="led"/>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped>
+.screen {
+    display: flex;
+    flex-direction: column;
+    white-space: nowrap; /* Prevent wrapping */
+    overflow-x: auto;
+    margin: 0 auto;
+    padding-top: 20px;
+}
+
+.screen-row {
+    display: flex;
+    flex-direction: row;
+    margin: 0 auto;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/MachineCodeLayout.vue b/src/components/MachineCodeLayout.vue
new file mode 100644
index 0000000..8bd552f
--- /dev/null
+++ b/src/components/MachineCodeLayout.vue
@@ -0,0 +1,90 @@
+<script setup lang="ts">
+import "../style.css"
+import {inject, Ref, ref} from "vue";
+import {Compiler} from "../utils/Complier.ts";
+import {Runner, RunnerState} from "../utils/Runner.ts";
+
+let compiler: Compiler = inject("compiler") as Compiler;
+let runner: Runner = inject("runner") as Runner;
+let machineCode: Ref<string[][], string[][]> = ref([]);
+let currentPC: Ref<number, number> = ref(-1);
+compiler.AddEventListener("CompileFinished", (_) => {
+    machineCode.value = Array.from(compiler.PCToMachineCode.values()).map((x) => {
+        x = x.slice(2);
+        return [x.slice(0, 2), x.slice(2, 4), x.slice(4, 6)];
+    });
+})
+runner.AddEventListener("PCChanged", (_) => {
+    currentPC.value = runner.State===RunnerState.Stop?-1: runner._processCounter;
+})
+</script>
+
+<template>
+    <div>
+        <h2>Machine Code Layout</h2>
+        <div id="code-area">
+            <div v-for="(instruction,index) in machineCode">
+                <div class="code" :class="{currentCode: index === currentPC}">
+                    <span class="line-number">{{ index + 1 }}</span>
+                    <div class="gutter"></div>
+                    <span class="mnemonic">{{ instruction[0] }}</span>
+                    <span class="operand">{{ instruction[1] }}</span>
+                    <span class="operand">{{ instruction[2] }}</span>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped>
+#code-area {
+    padding-top: 4px;
+    padding-bottom: 4px;
+    background-color: #282c34;
+    height: 492px;
+    overflow-x: auto;
+}
+
+.currentCode {
+    background-color: #3e4451;
+}
+
+.code {
+    /*这里的显示需要和 Assembly Code 对齐 但我不知道CodeMirror的行高是怎么算出来的
+    反正edge样式中是这个数,但是其他设备很可能会不对齐*/
+    height: 28px;
+    line-height: 28px;
+    display: flex;
+}
+
+span {
+    display: inline;
+    font-family: monospace;
+    line-height: 28px;
+    overflow-wrap: normal;
+    margin: 0;
+    font-size: 20px;
+}
+
+.line-number {
+    padding-right: 3px;
+    padding-left: 5px;
+    width: 20px;
+    text-align: right;
+    color: rgb(125, 135, 153);
+}
+
+.mnemonic {
+    color: rgb(198, 120, 221);
+    margin-right: 3px;
+}
+
+.operand {
+    color: rgb(224, 108, 117);
+}
+
+.gutter {
+    display: inline;
+    width: 9px;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/MemoryLayout.vue b/src/components/MemoryLayout.vue
new file mode 100644
index 0000000..fb2f697
--- /dev/null
+++ b/src/components/MemoryLayout.vue
@@ -0,0 +1,163 @@
+<script setup lang="ts">
+import "../style.css"
+import {MemoryManager} from "../utils/MemoryManager.ts";
+import {computed, inject, onMounted, Ref, ref} from "vue";
+import {Runner} from "../utils/Runner.ts";
+
+const looseLayout = ref(false);
+onMounted(() => {
+    const resizeObserver = new ResizeObserver(entries => {
+        looseLayout.value = entries[0].target.clientWidth > 525;
+    });
+    resizeObserver.observe(document.querySelector('#memories')!);
+});
+
+let memoryManager = inject("memoryManager") as MemoryManager;
+let runner = inject("runner") as Runner;
+let registers: Ref<string[], string[]> = ref([...runner.GetRegisters().map((x) => (x.GetValue(16) as string)) , "00"]);
+runner.AddEventListener("RegistersChanged", (_) => {
+    registers.value = [...runner.GetRegisters().map((x) => (x.GetValue(16) as string)), runner.GetPC().toString(16).padStart(2, '0')];
+})
+let screenMemory = computed(() => {
+    return memoryManager.screenMemory.value.map(row => row.map(byte => byte.GetValue(16) as string));
+})
+let memory = computed(() => {
+    let mem = memoryManager.memory;
+    let res:string[][];
+    if(looseLayout.value)
+    {
+        res = Array.from({length: 8}, () => Array.from({length: 16}, () => ""));
+        for (let i = 0; i < 8; i++) {
+            for (let j = 0; j < 16; j++) {
+                res[i][j] = mem.value[i * 16 + j].GetValue(16) as string;
+            }
+        }
+    }
+    else {
+        res = Array.from({length: 16}, () => Array.from({length: 8}, () => ""));
+        for (let i = 0; i < 16; i++) {
+            for (let j = 0; j < 8; j++) {
+                res[i][j] = mem.value[i * 8 + j].GetValue(16) as string;
+            }
+        }
+    }
+    return res;
+});
+</script>
+
+<template>
+    <div>
+        <h2>Memory Layout</h2>
+        <div id="memories">
+            <p>Screen Memory:</p>
+            <div id="screen-memory-area">
+                <div class="screen-row" v-for="(row, rowIndex) in screenMemory" :key="rowIndex">
+                    <span class="screen-line-number">{{ rowIndex }}</span>
+                    <div class="gutter"></div>
+                    <span v-for="(col, colIndex) in row" :key="colIndex" class="screen-memory">
+                        {{ col }}
+                    </span>
+                </div>
+            </div>
+            <p>Registers:</p>
+            <div id="registers-area"
+                 :class="{'register-loose-layout':looseLayout, 'register-compact-layout':!looseLayout}">
+                <div class="registers" v-for="(register,index) in registers">
+                    <span class="register-name">{{ index!=7? String.fromCharCode('A'.charCodeAt(0) + index) + 'X':'PC' }}</span>
+                    <span class="register-value">{{ register }}</span>
+                </div>
+            </div>
+            <p>Memory:</p>
+            <div id="screen-memory-area">
+                <div class="screen-row" v-for="(row, rowIndex) in memory" :key="rowIndex">
+                    <span class="screen-line-number">{{ rowIndex }}</span>
+                    <div class="gutter"></div>
+                    <span v-for="(col, colIndex) in row" :key="colIndex" class="screen-memory">
+                        {{ col }}
+                    </span>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped>
+#memories {
+    background-color: #282c34;
+    height: 492px;
+    padding-bottom: 4px;
+    padding-top: 4px;
+    overflow-x: auto;
+}
+
+#registers-area {
+    line-height: 28px;
+    display: grid;
+}
+
+.register-loose-layout {
+    grid-template-columns: repeat(7, 1fr);
+}
+
+.register-compact-layout {
+    grid-template-columns: repeat(4, 1fr);
+    grid-template-rows: 1fr 1fr;
+}
+
+p {
+    margin: 0;
+    padding: 10px;
+    color: white;
+    font-family: monospace;
+    font-size: 30px;
+    height: 36px;
+    white-space: nowrap;
+    overflow: hidden;
+}
+
+span {
+    display: inline;
+    font-family: monospace;
+    line-height: 28px;
+    overflow-wrap: normal;
+    margin: 0;
+    font-size: 20px;
+}
+
+.registers {
+    height: 28px;
+}
+
+.register-name {
+    padding-right: 3px;
+    padding-left: 5px;
+    color: rgb(198, 120, 221);
+}
+
+.register-value {
+    color: rgb(224, 108, 117);
+}
+
+.screen-line-number {
+    color: rgb(125, 135, 153);
+    padding-right: 3px;
+    padding-left: 5px;
+    display: inline-block;
+    width: 20px;
+    height: 28px;
+    text-align: right;
+}
+
+.screen-row {
+    color: rgb(224, 188, 105);
+}
+
+.screen-memory {
+    padding-left: 5px;
+}
+
+.gutter {
+    display: inline-block;
+    width: 9px;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/SingleLED.vue b/src/components/SingleLED.vue
new file mode 100644
index 0000000..059ae80
--- /dev/null
+++ b/src/components/SingleLED.vue
@@ -0,0 +1,54 @@
+<script setup lang="ts">
+import {ref} from "vue";
+const props = defineProps<{
+    isActive: boolean
+    colIndex: number
+    rowIndex: number
+    ledIndex: number
+}>();
+const showTooltip = ref(false);
+</script>
+
+<template>
+    <div class="led-container" @mouseenter="showTooltip = true" @mouseleave="showTooltip = false">
+        <div class="led" :class="{ active: isActive }"></div>
+        <div v-if="showTooltip" class="tooltip" @mouseover="showTooltip = false">{{ props.rowIndex }},{{ props.colIndex * 8 + props.ledIndex }}
+            0x{{ (props.rowIndex * 8 + props.colIndex).toString(16).toUpperCase().padStart(2, '0') }}
+        </div>
+    </div>
+</template>
+
+<style scoped>
+.led-container {
+    position: relative;
+    display: inline-block;
+}
+
+.led {
+    width: 10px;
+    height: 10px;
+    background-color: rgb(32, 42, 32);
+    border-radius: 50%;
+    box-shadow: 0 0 5px rgba(32, 42, 32, 0.5);
+    margin: 2px;
+}
+
+.active {
+    background-color: rgb(137, 221, 64);
+    box-shadow: 0 0 3px rgba(137, 221, 64, 0.5);
+}
+
+.tooltip {
+    position: absolute;
+    bottom: 100%;
+    left: 50%;
+    transform: translateX(-50%);
+    background-color: #333;
+    color: #fff;
+    padding: 5px;
+    border-radius: 3px;
+    white-space: nowrap;
+    z-index: 10;
+    opacity: 0.8;
+}
+</style>
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..2425c0f
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')
diff --git a/src/my-asm-grammar b/src/my-asm-grammar
new file mode 100644
index 0000000..5faf65f
--- /dev/null
+++ b/src/my-asm-grammar
@@ -0,0 +1,62 @@
+@top Program { Statement* }
+
+Statement {
+  Label Mnemonic Operand "," Operand Comment? Newline
+  | Mnemonic Operand "," Operand Comment? Newline
+  | Label Comment? Newline
+  | Comment Newline
+  | Label Db Comment? Newline
+  | Label Dw Comment? Newline
+}
+
+Mnemonic {
+  "nop"|"ldc"|"add"|"div"|"and"|"or"|"xor"|"not"|"lsl"|"lsr"|"ld"|"st"|"mov"|"cp"|"jp"|"ltjp"|"gtjp"|"eqjp"|"call"|"ret"
+}
+
+Operand {
+  Register | Immediate
+}
+
+Register {
+  "AX" | "BX" | "CX" | "DX" | "EX" | "FX" | "GX"
+}
+
+Immediate {
+  ImmediateToken
+}
+
+Comment {
+  CommentToken
+}
+
+Label {
+  LabelToken
+}
+
+Newline {
+  NewlineToken
+}
+
+Db {
+  "db" NumberList
+}
+
+Dw {
+  "dw" StringList
+}
+
+NumberList {
+  ImmediateToken ("," ImmediateToken)*
+}
+
+StringList {
+  StringToken ("," StringToken)*
+}
+
+@tokens {
+  ImmediateToken { $[0-9]+ }
+  CommentToken { ";" $[^\n]* }
+  LabelToken { $[a-zA-Z_]$[a-zA-Z0-9_]* ":" }
+  StringToken { "'" $[a-zA-Z0-9_]* "'" }
+  NewlineToken { "\n" }
+}
\ No newline at end of file
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000..2760e58
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,28 @@
+
+:root{
+    color-scheme: light dark;
+    background-color: rgb(30, 30, 30);
+    --code-input-flex: 1;
+    --machine-code-layout-flex: 1;
+    --memory-layout-flex: 1;
+    --current-hover-code: -1;
+}
+
+#app {
+  text-align: center;
+}
+
+
+#code>*>h2{
+    padding-left: 20px;
+    white-space: nowrap;
+    overflow: hidden;
+}
+#code>*>textarea{
+    background-color: rgb(55, 55, 55);
+    width: 100%;
+    overflow-y: scroll;
+    height: 100em;
+    max-height: 420px;
+    resize: none;
+}
\ No newline at end of file
diff --git a/src/utils/CodeChecker.ts b/src/utils/CodeChecker.ts
new file mode 100644
index 0000000..88e4f96
--- /dev/null
+++ b/src/utils/CodeChecker.ts
@@ -0,0 +1,50 @@
+
+export class Token {
+    constructor(public content: string, public type: string) {}
+}
+export class CodeChecker {
+/*  - 0x00: nop, 0, 0: 无操作
+    - 0x01: ldc, n, number: 寄存器n赋值为number
+    - 0x10: add, n, m: 寄存器n的值加通用寄存器m的值,结果存回寄存器n中
+    - 0x11: div, n, m: 寄存器n中的值除以通用寄存器m的值,结果存回寄存器n中,并将余数存入寄存器1
+    - 0x12: and, n, m: 寄存器n中的值和寄存器m中的值按位与,结果存回寄存器n中
+    - 0x13: or, n, m: 寄存器n中的值和寄存器m中的值按位或,结果存回寄存器n中
+    - 0x14: xor, n, m: 寄存器n中的值和寄存器m中的值按位异或,结果存回寄存器n中
+    - 0x15: not, n, 0: 寄存器n中的值按位翻转
+    - 0x16: lsl, n, m: 寄存器n中的值左移,位数为寄存器m中的值
+    - 0x17: lsr, n, m: 寄存器n中的值右移,位数为寄存器m中的值
+    - 0x20: ld, n, addr: 将地址addr处的值加载到寄存器n中
+    - 0x21: st, n, addr: 将寄存器n中的值保存到地址addr处
+    - 0x23: mov, n, m: 将寄存器m中的值复制到寄存器n中
+    - 0x30: cp, src, dst: 将地址为src处的数据复制到dst处,复制的字节数为通用寄存器2的值
+    - 0x40: jp, 0, k: 无条件跳转到第k条指令
+    - 0x41: ltjp, n, k: 如果寄存器2中的值小于寄存器n中的值,则跳转到第k条指令
+    - 0x42: gtjp, n, k: 如果寄存器2中的值大于寄存器n中的值,则跳转到第k条指令
+    - 0x43: eqjp, n, k: 如果寄存器2中的值等于寄存器n中的值,则跳转到第k条指令
+    - 0x50: call, 0, k: 将7个通用寄存器及下一条指令的序号推入栈中,然后跳转到第k条指令
+    - 0x51: ret, 0, 0: 如果栈为空,则程序终止;如果栈不为空,则恢复7个通用寄存器的值,跳转到之前存入的第k条指令
+*/
+    static KeyWords = ["nop","ldc","add","div","and","or","xor","not","lsl","lsr","ld","st","mov","cp","jp","ltjp","gtjp","eqjp","call","ret"];
+    static Registers = ["AX","BX","CX","DX","EX","FX","GX"];
+    static highlightCode(code: string): Token[] {
+        if(code.length === 0)
+            return [];
+        let res:string[] = code.split(",");
+        let ans:Token[] = [];
+        for(let key of res)
+        {
+            const temp = key.trim();
+            console.log(temp);
+            if(this.KeyWords.includes(temp))
+                ans.push(new Token(key, "keyword"));
+            else if(this.Registers.includes(temp))
+                ans.push(new Token(key, "register"));
+            else
+                ans.push(new Token(key, "normal"));
+            ans.push(new Token(",", "normal"));
+        }
+        ans.pop();
+        console.log(ans);
+        return ans;
+    }
+}
\ No newline at end of file
diff --git a/src/utils/Complier.ts b/src/utils/Complier.ts
new file mode 100644
index 0000000..815217d
--- /dev/null
+++ b/src/utils/Complier.ts
@@ -0,0 +1,217 @@
+import {Byte} from "./MemoryManager.ts";
+
+export class CompileError extends Error {
+    constructor(message: string, index: number) {
+        super(message);
+        this.index = index;
+    }
+
+    index: number;
+}
+
+export class Compiler {
+    // ["CompileFinished(info)", "CompileError(info,CompileError)"]
+    // 感觉这里写的不好 类型限制不够
+    _events: Map<string, ((arg0: string, arg1?: any) => void)[]> = new Map<string, ((arg0: string, arg1?: any) => void)[]>();
+
+    AddEventListener(event: string, callback: (arg0: string, arg1?: any) => void) {
+        if (!this._events.has(event))
+            this._events.set(event, [callback]);
+        else
+            this._events.get(event)!.push(callback);
+    }
+
+    Notify(event: string, data?: any) {
+        if (this._events.has(event))
+            if (data === undefined)
+                this._events.get(event)!.forEach(callback => callback(event));
+            else
+                this._events.get(event)!.forEach(callback => callback(event, data));
+    }
+
+    /*
+    - 0x00: nop, 0, 0: 无操作
+    - 0x01: ldc, n, number: 寄存器n赋值为number
+    - 0x10: add, n, m: 寄存器n的值加通用寄存器m的值,结果存回寄存器n中
+    - 0x11: div, n, m: 寄存器n中的值除以通用寄存器m的值,结果存回寄存器n中,并将余数存入寄存器1
+    - 0x12: and, n, m: 寄存器n中的值和寄存器m中的值按位与,结果存回寄存器n中
+    - 0x13: or, n, m: 寄存器n中的值和寄存器m中的值按位或,结果存回寄存器n中
+    - 0x14: xor, n, m: 寄存器n中的值和寄存器m中的值按位异或,结果存回寄存器n中
+    - 0x15: not, n, 0: 寄存器n中的值按位翻转
+    - 0x16: lsl, n, m: 寄存器n中的值左移,位数为寄存器m中的值
+    - 0x17: lsr, n, m: 寄存器n中的值右移,位数为寄存器m中的值
+    - 0x20: ld, n, addr: 将地址addr处的值加载到寄存器n中
+    - 0x21: st, n, addr: 将寄存器n中的值保存到地址addr处
+    - 0x23: mov, n, m: 将寄存器m中的值复制到寄存器n中
+    - 0x30: cp, src, dst: 将地址为src处的数据复制到dst处,复制的字节数为通用寄存器2的值
+    - 0x40: jp, 0, k: 无条件跳转到第k条指令
+    - 0x41: ltjp, n, k: 如果寄存器2中的值小于寄存器n中的值,则跳转到第k条指令
+    - 0x42: gtjp, n, k: 如果寄存器2中的值大于寄存器n中的值,则跳转到第k条指令
+    - 0x43: eqjp, n, k: 如果寄存器2中的值等于寄存器n中的值,则跳转到第k条指令
+    - 0x50: call, 0, k: 将7个通用寄存器及下一条指令的序号推入栈中,然后跳转到第k条指令
+    - 0x51: ret, 0, 0: 如果栈为空,则程序终止;如果栈不为空,则恢复7个通用寄存器的值,跳转到之前存入的第k条指令
+    */
+    static KeyWordsToMachineCode: Map<string, string> = new Map<string, string>([
+        ["nop", "0x00"],
+        ["ldc", "0x01"],
+        ["add", "0x10"],
+        ["div", "0x11"],
+        ["and", "0x12"],
+        ["or", "0x13"],
+        ["xor", "0x14"],
+        ["not", "0x15"],
+        ["lsl", "0x16"],
+        ["lsr", "0x17"],
+        ["ld", "0x20"],
+        ["st", "0x21"],
+        ["mov", "0x23"],
+        ["cp", "0x30"],
+        ["jp", "0x40"],
+        ["ltjp", "0x41"],
+        ["gtjp", "0x42"],
+        ["eqjp", "0x43"],
+        ["call", "0x50"],
+        ["ret", "0x51"]
+    ]);
+    static Registers: Map<string, string> = new Map<string, string>([
+        ["AX", "01"],
+        ["BX", "02"],
+        ["CX", "03"],
+        ["DX", "04"],
+        ["EX", "05"],
+        ["FX", "06"],
+        ["GX", "07"]
+    ])
+    static font8x8: { [key: string]: number[] } = {
+        'A': [0x7E, 0x11, 0x11, 0x7E, 0x11, 0x11, 0x11, 0x00],
+        'B': [0x7C, 0x12, 0x12, 0x7C, 0x12, 0x12, 0x7C, 0x00],
+        'C': [0x3E, 0x41, 0x40, 0x40, 0x40, 0x41, 0x3E, 0x00],
+        'D': [0x7C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7C, 0x00],
+        'E': [0x7F, 0x40, 0x40, 0x7C, 0x40, 0x40, 0x7F, 0x00],
+        'F': [0x7F, 0x40, 0x40, 0x7C, 0x40, 0x40, 0x40, 0x00],
+        'G': [0x3E, 0x41, 0x40, 0x4F, 0x41, 0x41, 0x3E, 0x00],
+        'H': [0x41, 0x41, 0x41, 0x7F, 0x41, 0x41, 0x41, 0x00],
+        'I': [0x1C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00],
+        'J': [0x0F, 0x02, 0x02, 0x02, 0x02, 0x42, 0x3C, 0x00],
+        'K': [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00],
+        'L': [0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7F, 0x00],
+        'M': [0x41, 0x63, 0x55, 0x49, 0x41, 0x41, 0x41, 0x00],
+        'N': [0x41, 0x61, 0x51, 0x49, 0x45, 0x43, 0x41, 0x00],
+        'O': [0x3E, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3E, 0x00],
+        'P': [0x7E, 0x11, 0x11, 0x7E, 0x40, 0x40, 0x40, 0x00],
+        'Q': [0x3E, 0x41, 0x41, 0x41, 0x45, 0x42, 0x3D, 0x00],
+        'R': [0x7E, 0x41, 0x41, 0x7E, 0x44, 0x42, 0x41, 0x00],
+        'S': [0x3E, 0x41, 0x40, 0x3E, 0x01, 0x41, 0x3E, 0x00],
+        'T': [0x7F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00],
+        'U': [0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3E, 0x00],
+        'V': [0x41, 0x41, 0x41, 0x41, 0x22, 0x14, 0x08, 0x00],
+        'W': [0x41, 0x41, 0x41, 0x49, 0x55, 0x63, 0x41, 0x00],
+        'X': [0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41, 0x00],
+        'Y': [0x41, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00],
+        'Z': [0x7F, 0x02, 0x04, 0x08, 0x10, 0x20, 0x7F, 0x00],
+        '0': [0x3E, 0x41, 0x43, 0x45, 0x49, 0x51, 0x3E, 0x00],
+        '1': [0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3E, 0x00],
+        '2': [0x3E, 0x41, 0x01, 0x3E, 0x40, 0x40, 0x7F, 0x00],
+        '3': [0x3E, 0x41, 0x01, 0x1E, 0x01, 0x41, 0x3E, 0x00],
+        '4': [0x10, 0x30, 0x50, 0x90, 0x7F, 0x10, 0x10, 0x00],
+        '5': [0x7F, 0x40, 0x7E, 0x01, 0x01, 0x41, 0x3E, 0x00],
+        '6': [0x3E, 0x40, 0x7E, 0x41, 0x41, 0x41, 0x3E, 0x00],
+        '7': [0x7F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00],
+        '8': [0x3E, 0x41, 0x41, 0x3E, 0x41, 0x41, 0x3E, 0x00],
+        '9': [0x3E, 0x41, 0x41, 0x3F, 0x01, 0x41, 0x3E, 0x00],
+        '+': [0x00, 0x08, 0x08, 0x7F, 0x08, 0x08, 0x00, 0x00],
+        '-': [0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00],
+        'x': [0x00, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41],
+        '/': [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00],
+        ' ': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    };
+    Code: string = "";
+    CodeChanged: boolean = true;
+    PCToIndex: Map<number, number> = new Map<number, number>();
+    PCToCode: Map<number, string> = new Map<number, string>();
+    PCToMachineCode: Map<number, string> = new Map<number, string>();
+    Labels: Map<string, number> = new Map<string, number>();
+    MemorySet: Byte[] = [];
+
+    Clear() {
+        this.PCToIndex.clear();
+        this.PCToCode.clear();
+        this.PCToMachineCode.clear();
+        this.Labels.clear();
+        this.MemorySet = [];
+    }
+
+    CompileCode(fullCode: string = this.Code) {
+        this.Clear();
+        this.CodeChanged = false;
+        try {
+            fullCode.split("\n").forEach((line, index) => {
+                //code -> a: b ; c
+                let pc = this.PCToCode.size;
+                let labelAndCode = (line.includes(";") ? line.slice(0, line.indexOf(";")) : line).split(":");
+                let code = "";
+                if (labelAndCode.length === 2) {
+                    if (this.Labels.has(labelAndCode[0].trim()))
+                        throw new Error("Duplicate label");
+                    this.Labels.set(labelAndCode[0].trim(), pc);
+                    code = labelAndCode[1].trim();
+                } else if (labelAndCode.length === 1)
+                    code = labelAndCode[0].trim();
+                else
+                    throw new CompileError("Invalid code", index);
+                if (code === "")
+                    return;
+                if (code.startsWith("db")) {
+                    if (labelAndCode.length === 1)
+                        throw new CompileError("db must start with a label.", index);
+                    this.Labels.set(labelAndCode[0].trim(), this.MemorySet.length + 128);
+                    code.slice(2).split(",").forEach(val => {
+                        let num = val.toUpperCase().endsWith('H') ? parseInt(val, 16) : parseInt(val);
+                        if (isNaN(num) || num < 0 || num > 255)
+                            throw new CompileError("Invalid number", index);
+                        this.MemorySet.push(new Byte(num));
+                    });
+                    return;
+                } else if (code.startsWith("dw")) {
+                    if (labelAndCode.length === 1)
+                        throw new CompileError("dw must start with a label.", index);
+                    this.Labels.set(labelAndCode[0].trim(), this.MemorySet.length + 128);
+                    code.slice(3).toUpperCase().split(",").forEach(val => {
+                        let str = val.slice(1, -1);
+                        for (let c of str) {
+                            if (!(c in Compiler.font8x8))
+                                throw new CompileError("Invalid char", index);
+                            Compiler.font8x8[c].forEach(byte => this.MemorySet.push(new Byte(byte)));
+                        }
+                    });
+                    return;
+                }
+                this.PCToCode.set(pc, code);
+                this.PCToIndex.set(pc, index);
+            });
+            console.log(this.Labels);
+            this.PCToCode.forEach((code, pc) => {
+                let index = this.PCToIndex.get(pc)!;
+                let mnemonicAndVals = code.split(/[\s,]+/);
+                if (mnemonicAndVals.length !== 3)
+                    throw new CompileError("Invalid code", index);
+                if (!Compiler.KeyWordsToMachineCode.has(mnemonicAndVals[0]))
+                    throw new CompileError("Invalid mnemonic", index);
+                for (let i = 1; i <= 2; i++)
+                    if ((Compiler.Registers.has(mnemonicAndVals[i])))
+                        mnemonicAndVals[i] = Compiler.Registers.get(mnemonicAndVals[i])!;
+                    else if(this.Labels.has(mnemonicAndVals[i]))
+                        mnemonicAndVals[i] = this.Labels.get(mnemonicAndVals[i])!.toString(16).padStart(2,'0');
+                    else
+                        mnemonicAndVals[i] = parseInt(mnemonicAndVals[i]).toString(16).padStart(2,'0');
+                this.PCToMachineCode.set(pc, Compiler.KeyWordsToMachineCode.get(mnemonicAndVals[0])! + mnemonicAndVals[1] + mnemonicAndVals[2]);
+            });
+        } catch (e) {
+            if (e instanceof CompileError)
+                this.Notify("CompileError", e);
+            throw e;
+        }
+        this.Notify("CompileFinished");
+    }
+
+}
\ No newline at end of file
diff --git a/src/utils/MemoryManager.ts b/src/utils/MemoryManager.ts
new file mode 100644
index 0000000..32112e7
--- /dev/null
+++ b/src/utils/MemoryManager.ts
@@ -0,0 +1,166 @@
+import {ref, Ref} from "vue";
+
+export class Byte {
+    value: boolean[]
+
+    constructor();
+    constructor(value: boolean[]);
+    constructor(value: number);
+    constructor(value: Byte);
+    constructor(value?: boolean[] | number | Byte) {
+        if (value) {
+            if(value instanceof Byte)
+            {
+                this.value = value.value.slice();
+                return;
+            }
+            else if (typeof value === 'number') {
+                if (value < 0 || value >= 256 || !Number.isInteger(value))
+                    throw new Error("Invalid argument");
+                this.value = Array(8).fill(false);
+                for (let i = 0; i < 8; i++) {
+                    this.value[i] = (value & (1 << i)) !== 0;
+                }
+            } else if (value.length === 8)
+                this.value = value;
+            else
+                throw new Error("Invalid argument");
+        } else
+            this.value = Array(8).fill(false);
+    }
+
+    SetValue(num: number | string | Byte) {
+        if(num instanceof Byte)
+        {
+            this.value = num.value.slice();
+            return;
+        }
+        if (typeof num === 'string') {
+            num = parseInt(num);
+        }
+        if (num < 0 || num >= 256)
+            throw new Error("Invalid argument");
+        this.value = Array(8).fill(false);
+        for (let i = 0; i < 8; i++) {
+            this.value[i] = (num & (1 << i)) !== 0;
+        }
+    }
+
+    GetValue(format: number = 10): number | string {
+        if (format === 10)
+            return this.value.reduce((acc, val, index) => acc + (val ? 2 ** index : 0), 0);
+        if (format === 16)
+            return this.value.reduce((acc, val, index) => acc + (val ? 2 ** index : 0), 0).toString(16).padStart(2, '0');
+        throw new Error("Invalid argument");
+    }
+
+    Copy(){
+        return new Byte(this);
+    }
+
+    //ignore carry
+    Add(b: Byte) {
+        return new Byte(((this.GetValue() as number) + (b.GetValue() as number)) % 256);
+    }
+
+    //ignore carry
+    AddAndAssign(b: Byte) {
+        this.SetValue(((this.GetValue() as number) + (b.GetValue() as number)) % 256);
+        return this;
+    }
+
+    Sub(b: Byte) {
+        if (this.GetValue() < b.GetValue())
+            return new Byte(((this.GetValue() as number) + 256 - (b.GetValue() as number)) % 256);
+        return new Byte(((this.GetValue() as number) - (b.GetValue() as number)) % 256);
+    }
+
+    SubAndAssign(b: Byte) {
+        if (this.GetValue() < b.GetValue()) {
+            this.SetValue(((this.GetValue() as number) + 256 - (b.GetValue() as number)) % 256);
+            return this
+        }
+        this.SetValue(((this.GetValue() as number) - (b.GetValue() as number)) % 256);
+        return this;
+    }
+    And(b: Byte) {
+        return new Byte(this.value.map((val, index) => val && b.value[index]));
+    }
+    AndAndAssign(b: Byte) {
+        this.value = this.value.map((val, index) => val && b.value[index]);
+        return this;
+    }
+    Or(b: Byte) {
+        return new Byte(this.value.map((val, index) => val || b.value[index]));
+    }
+    OrAndAssign(b: Byte) {
+        this.value = this.value.map((val, index) => val || b.value[index]);
+        return this;
+    }
+    Xor(b: Byte) {
+        return new Byte(this.value.map((val, index) => val !== b.value[index]));
+    }
+    XorAndAssign(b: Byte) {
+        this.value = this.value.map((val, index) => val !== b.value[index]);
+        return this;
+    }
+    Not() {
+        return new Byte(this.value.map(val => !val));
+    }
+    NotAndAssign() {
+        this.value = this.value.map(val => !val);
+        return this;
+    }
+    Lsl(b: Byte) {
+        return new Byte(this.value.slice(b.GetValue() as number).concat(Array(b.GetValue() as number).fill(false)));
+    }
+    LslAndAssign(b: Byte) {
+        this.value = this.value.slice(b.GetValue() as number).concat(Array(b.GetValue() as number).fill(false));
+        return this;
+    }
+    Lsr(b: Byte) {
+        return new Byte(Array(b.GetValue() as number).fill(false).concat(this.value.slice(0, 8 - (b.GetValue() as number))));
+    }
+    LsrAndAssign(b: Byte) {
+        this.value = Array(b.GetValue() as number).fill(false).concat(this.value.slice(0, 8 - (b.GetValue() as number)));
+        return this;
+    }
+}
+
+export class MemoryManager {
+    screenMemory: Ref<Byte[][]>;
+    memory: Ref<Byte[]>;
+
+    constructor() {
+        this.screenMemory = ref(Array.from({length: 16}, () => Array.from({length: 8}, () => new Byte(255))));
+        this.memory = ref(Array(128).fill(new Byte(0)));
+    }
+
+    clear() {
+        for (let t of this.screenMemory.value)
+            t.forEach(val => val.SetValue(0));
+        this.memory.value.forEach(val => val.SetValue(0));
+    }
+
+    GetVal(t:number)
+    {
+        if(t>=128)
+            return this.memory.value[t-128];
+        else
+            return this.screenMemory.value[Math.floor(t/8)][t%8];
+    }
+    SetVal(t:number, val:Byte)
+    {
+        if(t>=128)
+            this.memory.value[t-128] = new Byte(val);
+        else
+            this.screenMemory.value[Math.floor(t/8)][t%8] = new Byte(val);
+    }
+
+    SetMemSet(memorySet: Byte[]) {
+        if(memorySet.length > 128)
+            throw new Error("No enough space");
+        for(let i = 0;i<memorySet.length;i++)
+            this.memory.value[i] = memorySet[i];
+    }
+}
\ No newline at end of file
diff --git a/src/utils/Runner.ts b/src/utils/Runner.ts
new file mode 100644
index 0000000..1f74a5a
--- /dev/null
+++ b/src/utils/Runner.ts
@@ -0,0 +1,374 @@
+import {MemoryManager, Byte} from "./MemoryManager";
+import {Compiler} from "./Complier.ts";
+
+export enum RunnerState {
+    Stop,
+    Running,
+    Step
+}
+
+export class Runner {
+    // 与Compiler 类似的事件机制 要是再来一个我就把这段逻辑单独抽出来
+    //[PCChanged,RegistersChanged]
+    _events: Map<string, ((arg0: string, arg1?: any) => void)[]> = new Map<string, ((arg0: string, arg1?: any) => void)[]>();
+
+    AddEventListener(event: string, callback: (arg0: string, arg1?: any) => void) {
+        if (!this._events.has(event))
+            this._events.set(event, [callback]);
+        else
+            this._events.get(event)!.push(callback);
+    }
+
+    Notify(event: string, data?: any) {
+        if (this._events.has(event))
+            if (data === undefined)
+                this._events.get(event)!.forEach(callback => callback(event));
+            else
+                this._events.get(event)!.forEach(callback => callback(event, data));
+    }
+
+    State: RunnerState = RunnerState.Stop;
+    _processor: Processor;
+    _memory: MemoryManager;
+    _processCounter: number = 0;
+    _compiler: Compiler;
+    _stack: [Byte[], number][] = [];
+    _intervalId: NodeJS.Timeout | null = null;
+
+    StartInterval() {
+        if (this._intervalId === null)
+            this._intervalId = setInterval(() => {
+                if (this._processCounter >= this._compiler.PCToCode.size) {
+                    this.Stop();
+                    return;
+                }
+                this.RunOneLineCode();
+            }, 20);
+    }
+
+    StopInterval() {
+        if (this._intervalId !== null) {
+            clearInterval(this._intervalId);
+            this._intervalId = null;
+        }
+    }
+
+    constructor(memoryManager: MemoryManager, compiler: Compiler) {
+        this._memory = memoryManager
+        this._processor = new Processor();
+        this._compiler = compiler;
+        compiler.AddEventListener("CompileFinished", (_) => {
+            this._memory.SetMemSet(compiler.MemorySet)
+        });
+        // Bind methods to the instance
+        this.GetMemory = this.GetMemory.bind(this);
+        this.SetMemory = this.SetMemory.bind(this);
+        this.SetPC = this.SetPC.bind(this);
+        this.SaveEnv = this.SaveEnv.bind(this);
+        this.RestoreEnv = this.RestoreEnv.bind(this);
+    }
+
+    CommandParsing(): [string, number | string, number | string] {
+        let code = "";
+        if (this._compiler.PCToCode.has(this._processCounter))
+            code = this._compiler.PCToCode.get(this._processCounter) as string;
+        else
+            throw new Error("PC out of range");
+        this._processCounter++;
+        let mnemonicAndVals = code.split(/[\s,]+/);
+        // 如果指令不合法 Compiler应该会有异常抛出 则不能执行到这里
+        if (mnemonicAndVals.length !== 3)
+            throw new Error("Invalid code");
+        let mnemonic = mnemonicAndVals[0];
+        function getVal(runner:Runner, val: string) {
+            if (runner._processor._registers.has(val))
+                return val;
+            else if(runner._compiler.Labels.has(val))
+                return runner._compiler.Labels.get(val) as number;
+            else {
+                let t = parseInt(val);
+                if(isNaN(t))
+                    throw new Error("Invalid argument");
+                return t;
+            }
+        }
+        return [mnemonic, getVal(this,mnemonicAndVals[1]), getVal(this,mnemonicAndVals[2])];
+    }
+
+    RunOneLineCode() {
+        this._processor.RunCode(...this.CommandParsing(), this.GetMemory, this.SetMemory, this.SetPC, this.SaveEnv, this.RestoreEnv);
+        this.Notify("PCChanged");
+        this.Notify("RegistersChanged");
+    }
+
+    Init() {
+        this._memory.clear();
+        this._compiler.CompileCode();
+        this._processCounter = 0;
+    }
+
+    Run() {
+        this.State = RunnerState.Running;
+        this.Init();
+        this.StartInterval();
+    }
+
+    Resume() {
+        this.State = RunnerState.Running;
+        this.StartInterval();
+    }
+
+    Step() {
+        if (this._processCounter >= this._compiler.PCToCode.size)
+            return;
+        if (this.State === RunnerState.Running) {
+            this.Stop();
+        }
+        this.State = RunnerState.Step;
+        this.RunOneLineCode()
+    }
+
+    Stop() {
+        this.State = RunnerState.Stop;
+        this.StopInterval()
+    }
+
+    Refresh() {
+        this.Run();
+    }
+
+    GetMemory(t: number): Byte {
+        console.log(this._memory)
+        return this._memory.GetVal(t);
+    }
+
+    SetMemory(t: number, val: Byte): void {
+        console.log("mem Test:")
+        console.log(this)
+        console.log(this._memory)
+
+        this._memory.SetVal(t, val);
+    }
+
+    SaveEnv() {
+        if (this._stack.length > 8)
+            throw new Error("Stack overflow");
+        this._stack.push([this._processor.GetRegisters().map(x=>x.Copy()), this._processCounter]);
+    }
+
+    RestoreEnv() {
+        if (this._stack.length === 0) {
+            this.Stop();
+            return;
+        }
+        let [regs, pc] = this._stack.pop()!;
+        console.log('restore',regs);
+        this._processor.SetRegisters(regs);
+        this._processCounter = pc;
+    }
+
+    SetPC(addr: number) {
+        this._processCounter = addr;
+        console.log("PC set to", addr);
+    }
+
+    GetRegisters(): Byte[] {
+        return this._processor.GetRegisters();
+    }
+
+    GetPC(): number {
+        return this._processCounter;
+    }
+}
+
+class Processor {
+    _registers: Map<string, Byte>;
+
+    constructor() {
+        this._registers = new Map<string, Byte>([
+            ["AX", new Byte()],
+            ["BX", new Byte()],
+            ["CX", new Byte()],
+            ["DX", new Byte()],
+            ["EX", new Byte()],
+            ["FX", new Byte()],
+            ["GX", new Byte()]
+        ]);
+    }
+
+// static KeyWords = ["nop","ldc","add","div","and","or","xor","not","lsl","lsr","ld","st","mov","cp","jp","ltjp","gtjp","eqjp","call","ret"];
+// static Registers = ["AX","BX","CX","DX","EX","FX","GX"];
+    GetRegister(register: string | number): Byte {
+        if (this._registers.has(register as string))
+            return this._registers.get(register as string)!;
+        else
+            throw new Error("Invalid register");
+    }
+
+    GetRegisters(): Byte[] {
+        console.log("Get", this._registers.values());
+        return Array.from(this._registers.values());
+    }
+
+    SetRegisters(registers: Byte[]) {
+        for (let i = 0; i < 7; i++) {
+            console.log("Set", String.fromCharCode('A'.charCodeAt(0) + i) + 'X',registers[i].value);
+            this._registers.get(String.fromCharCode('A'.charCodeAt(0) + i) + 'X')!.SetValue(registers[i]);
+        }
+    }
+
+    GetRegisterValue(register: string | number): number {
+        return this.GetRegister(register).GetValue() as number;
+    }
+
+    SetRegisterValue(register: string | number, value: number) {
+        this.GetRegister(register).SetValue(value);
+    }
+
+    InRegister(register: string | number): boolean {
+        return typeof register === 'string' && this._registers.has(register);
+    }
+
+    RunCode(mnemonic: string, operandA: number | string, operandB: number | string, getMemory: (addr: number) => Byte, setMemory: (addr: number, value: Byte) => void, setPC: (addr: number) => void, saveEnv: () => void, restoreEnv: () => void) {
+        console.log(mnemonic, operandA, operandB);
+        if(this.InRegister(operandA))
+            console.log(this.GetRegisterValue(operandA));
+        if(this.InRegister(operandB))
+            console.log(this.GetRegisterValue(operandB));
+        switch (mnemonic) {
+            case "nop":
+                break;
+            case "ldc":
+                if (this.InRegister(operandA) && typeof operandB === 'number') {
+                    this.GetRegister(operandA).SetValue(operandB);
+                    return;
+                }
+                break;
+            case "add":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    this.GetRegister(operandA).AddAndAssign(this._registers.get(operandB as string)!);
+                    return;
+                }
+                break;
+            case "div":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    let a = this.GetRegisterValue(operandA), b = this.GetRegisterValue(operandB);
+                    this.SetRegisterValue(operandA, Math.floor(a / b));
+                    this.SetRegisterValue("AX", a % b);
+                    return;
+                }
+                break;
+            case "and":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    this.GetRegister(operandA).AndAndAssign(this.GetRegister(operandB));
+                    return;
+                }
+                break;
+            case "or":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    this.GetRegister(operandA).OrAndAssign(this.GetRegister(operandB));
+                    return;
+                }
+                break;
+            case "xor":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    this.GetRegister(operandA).XorAndAssign(this.GetRegister(operandB));
+                    return;
+                }
+                break;
+            case "not":
+                if (this.InRegister(operandA) && operandB === 0) {
+                    this.GetRegister(operandA).NotAndAssign();
+                    return;
+                }
+                break;
+            case "lsl":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    this.GetRegister(operandA).LslAndAssign(this.GetRegister(operandB));
+                    return;
+                }
+                break;
+            case "lsr":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    this.GetRegister(operandA).LsrAndAssign(this.GetRegister(operandB));
+                    return;
+                }
+                break;
+            case "ld":
+                if (this.InRegister(operandA) && typeof operandB === 'number') {
+                    this.GetRegister(operandA).SetValue(getMemory(operandB as number));
+                    return;
+                }
+                break;
+            case "st":
+                console.log(operandA, operandB);
+                if (this.InRegister(operandA) && typeof operandB === 'number') {
+                    setMemory(operandB as number, this.GetRegister(operandA));
+                    return;
+                }
+                break;
+            case "mov":
+                if (this.InRegister(operandA) && this.InRegister(operandB)) {
+                    this.GetRegister(operandA).SetValue(this.GetRegister(operandB));
+                    return;
+                }
+                else if(this.InRegister(operandA) && typeof operandB === 'number') {
+                    this.GetRegister(operandA).SetValue(operandB);
+                    return;
+                }
+                break;
+            case "cp":
+                console.log(operandA, operandB);
+                operandA = this.InRegister(operandA) ? this.GetRegisterValue(operandA) : operandA;
+                operandB = this.InRegister(operandB) ? this.GetRegisterValue(operandB) : operandB;
+                if (typeof operandA === 'number' && typeof operandB === 'number') {
+                    console.log(operandA, operandB);
+                    for (let i = 0; i < (this._registers.get("BX")!.GetValue() as number); i++)
+                        setMemory(operandB + i, getMemory(operandA + i));
+                    return;
+                }
+                break;
+            case "jp":
+                if (typeof operandA === 'number' && typeof operandB === 'number' && operandA === 0) {
+                    setPC(operandB as number);
+                    return;
+                }
+                break;
+            case "ltjp":
+                if (this.InRegister(operandA) && typeof operandB === 'number') {
+                    if (this.GetRegisterValue("BX") < this.GetRegisterValue(operandA))
+                        setPC(operandB as number);
+                    return;
+                }
+                break;
+            case "gtjp":
+                if (this.InRegister(operandA) && typeof operandB === 'number') {
+                    if (this.GetRegisterValue("BX") > this.GetRegisterValue(operandA))
+                        setPC(operandB as number);
+                    return;
+                }
+                break;
+            case "eqjp":
+                if (this.InRegister(operandA) && typeof operandB === 'number') {
+                    if (this.GetRegisterValue("BX") === this.GetRegisterValue(operandA))
+                        setPC(operandB as number);
+                    return;
+                }
+                break;
+            case "call":
+                if (typeof operandA === 'number' && typeof operandB === 'number' && operandA === 0) {
+                    saveEnv();
+                    setPC(operandB as number);
+                    return;
+                }
+                break;
+            case "ret":
+                if (typeof operandA === 'number' && typeof operandB === 'number' && operandA === 0 && operandB === 0) {
+                    restoreEnv();
+                    return;
+                }
+                break
+        }
+        throw new Error("Invalid argument");
+    }
+}
diff --git a/src/utils/parser.js b/src/utils/parser.js
new file mode 100644
index 0000000..04710e5
--- /dev/null
+++ b/src/utils/parser.js
@@ -0,0 +1,16 @@
+// This file was generated by lezer-generator. You probably shouldn't edit it.
+import {LRParser} from "@lezer/lr"
+export const parser = LRParser.deserialize({
+  version: 14,
+  states: "'OQQOPOOOOOO'#C_'#C_OOOO'#Ca'#CaOOOO'#Cf'#CfO!hOQO'#C^O!uOQO'#C^O#aOPO'#C^OOOO'#Co'#CoQQOPOOOOOO'#Ch'#ChO#fOPO'#CjO#kOPO'#ClO!uOQO,58xOOOO,58x,58xO#aOPO,58xO#pOPO,58xOOOO'#Cc'#CcOOOO'#Cd'#CdOOOO'#Cb'#CbO#xOPO,58xOOOO-E6m-E6mO#}OPO'#CkOOOO,59U,59UO$YOPO'#CmOOOO,59W,59WO$eOPO1G.dOOOO1G.d1G.dO#aOPO1G.dO!uOQO1G.dO$jOPO'#CpO$oOPO,59VO$zOPO'#CqO%POPO,59XO!uOQO7+$OOOOO7+$O7+$OO#pOPO7+$OOOOO,59[,59[OOOO-E6n-E6nOOOO,59],59]OOOO-E6o-E6oO#pOPO<<GjOOOO<<Gj<<GjO#aOPO<<GjOOOOAN=UAN=UO#aOPOAN=UOOOOG22pG22p",
+  stateData: "%[~OSPOZROgQOhQOiQOjQOkQOlQOmQOnQOoQOpQOqQOrQOsQOtQOuQOvQOwQOxQOyQOzQO~O]XO!TYO!UZO~PTOXaO{`O|`O}`O!O`O!P`O!Q`O!R`O~O]XO~OXeO~ObgO~OZRO]XO~O!SlO~O!SmOZ_X]_X~O!SoOZaX]aX~O!SqO~OXtO~O!SmOZ_a]_a~ObvO~O!SoOZaa]aa~O",
+  goto: "#lfPPgkPov!S!SP!YP!jP#P#S#P#VP#Y#`#fTVOWTSOWSTOWR[SQcTQi[QslRxqXbT[lqSUOWQ^SQk_QzsR|xS]SUSj^_QrkQysS{xzR}|R_SRfYRhZQWORdWQneRunQpgRwp",
+  nodeNames: "⚠ Program Statement Label LabelToken Mnemonic Operand Register Immediate ImmediateToken Comment CommentToken Newline NewlineToken Db NumberList Dw StringList StringToken",
+  maxTerm: 52,
+  skippedNodes: [0],
+  repeatNodeCount: 3,
+  tokenData: "E`~RrYZ#]wx#b|}#y!Q![$O!]!^$W!c!d$c!d!e%x!e!f&v!f!g't!g!h(r!h!i)p!i!j*n!j!}${#R#S${#T#U+l#U#V${#V#W.W#W#X0o#X#Y3]#Y#Z${#Z#[5]#[#^${#^#_7]#_#`${#`#a8Z#a#b<|#b#c>d#c#d@f#d#f${#f#gAd#g#hBz#h#l${#l#mCx#m#o${~#bO]~~#eTwx#t!Q![#b!c!}#b#R#S#b#T#o#b~#yOb~~$OO!S~~$TPX~!Q![$O~$]QZ~YZ$W#Q#R$WR$fV!Q![${![!]%_!c!z${!z!{%d!{!}${#R#S${#T#o${P%OT!Q![${![!]%_!c!}${#R#S${#T#o${P%dOSPR%iT{Q!Q![${![!]%_!c!}${#R#S${#T#o${R%{V!Q![${![!]%_!c!z${!z!{&b!{!}${#R#S${#T#o${R&gT|Q!Q![${![!]%_!c!}${#R#S${#T#o${R&yV!Q![${![!]%_!c!z${!z!{'`!{!}${#R#S${#T#o${R'eT}Q!Q![${![!]%_!c!}${#R#S${#T#o${R'wV!Q![${![!]%_!c!z${!z!{(^!{!}${#R#S${#T#o${R(cT!OQ!Q![${![!]%_!c!}${#R#S${#T#o${R(uV!Q![${![!]%_!c!z${!z!{)[!{!}${#R#S${#T#o${R)aT!PQ!Q![${![!]%_!c!}${#R#S${#T#o${R)sV!Q![${![!]%_!c!z${!z!{*Y!{!}${#R#S${#T#o${R*_T!QQ!Q![${![!]%_!c!}${#R#S${#T#o${R*qV!Q![${![!]%_!c!z${!z!{+W!{!}${#R#S${#T#o${R+]T!RQ!Q![${![!]%_!c!}${#R#S${#T#o${~+oX!Q![${![!]%_!c!}${#R#S${#T#W${#W#X,[#X#b${#b#c-Y#c#o${~,_V!Q![${![!]%_!c!}${#R#S${#T#W${#W#X,t#X#o${~,yTi~!Q![${![!]%_!c!}${#R#S${#T#o${~-]V!Q![${![!]%_!c!}${#R#S${#T#W${#W#X-r#X#o${~-wTk~!Q![${![!]%_!c!}${#R#S${#T#o${~.ZW!Q![${![!]%_!c!}${#R#S${#T#U.s#U#d${#d#e0Z#e#o${~.vV!Q![${![!]%_!c!}${#R#S${#T#`${#`#a/]#a#o${~/`V!Q![${![!]%_!c!}${#R#S${#T#`${#`#a/u#a#o${~/zTy~!Q![${![!]%_!c!}${#R#S${#T#o${~0`Tt~!Q![${![!]%_!c!}${#R#S${#T#o${~0rZ!Q![${![!]%_!c!}${#R#S${#T#U${#U#V1e#V#]${#]#^1y#^#k${#k#l2w#l#o${R1jT!TQ!Q![${![!]%_!c!}${#R#S${#T#o${~1|V!Q![${![!]%_!c!}${#R#S${#T#j${#j#k2c#k#o${~2hTj~!Q![${![!]%_!c!}${#R#S${#T#o${R2|T!UQ!Q![${![!]%_!c!}${#R#S${#T#o${~3`V!Q![${![!]%_!c!}${#R#S${#T#e${#e#f3u#f#o${~3xV!Q![${![!]%_!c!}${#R#S${#T#^${#^#_4_#_#o${~4bV!Q![${![!]%_!c!}${#R#S${#T#d${#d#e4w#e#o${~4|Tx~!Q![${![!]%_!c!}${#R#S${#T#o${~5`V!Q![${![!]%_!c!}${#R#S${#T#h${#h#i5u#i#o${~5xV!Q![${![!]%_!c!}${#R#S${#T#^${#^#_6_#_#o${~6bV!Q![${![!]%_!c!}${#R#S${#T#d${#d#e6w#e#o${~6|Tw~!Q![${![!]%_!c!}${#R#S${#T#o${~7`V!Q![${![!]%_!c!}${#R#S${#T#d${#d#e7u#e#o${~7zTu~!Q![${![!]%_!c!}${#R#S${#T#o${~8^Y!Q![${![!]%_!c!}${#R#S${#T#W${#W#X8|#X#g${#g#h9|#h#i;f#i#o${~9RVq~!Q![${![!]%_!c!}${#R#S${#T#V${#V#W9h#W#o${~9mTh~!Q![${![!]%_!c!}${#R#S${#T#o${~:PX!Q![${![!]%_!c!}${#R#S${#T#`${#`#a:l#a#f${#f#g;Q#g#o${~:qTo~!Q![${![!]%_!c!}${#R#S${#T#o${~;VTp~!Q![${![!]%_!c!}${#R#S${#T#o${~;iV!Q![${![!]%_!c!}${#R#S${#T#^${#^#_<O#_#o${~<RV!Q![${![!]%_!c!}${#R#S${#T#d${#d#e<h#e#o${~<mTv~!Q![${![!]%_!c!}${#R#S${#T#o${~=PV!Q![${![!]%_!c!}${#R#S${#T#c${#c#d=f#d#o${~=iV!Q![${![!]%_!c!}${#R#S${#T#j${#j#k>O#k#o${~>TTs~!Q![${![!]%_!c!}${#R#S${#T#o${~>gV!Q![${![!]%_!c!}${#R#S${#T#c${#c#d>|#d#o${~?PX!Q![${![!]%_!c!}${#R#S${#T#d${#d#e?l#e#h${#h#i@Q#i#o${~?qTg~!Q![${![!]%_!c!}${#R#S${#T#o${~@VTn~!Q![${![!]%_!c!}${#R#S${#T#o${~@iV!Q![${![!]%_!c!}${#R#S${#T#f${#f#gAO#g#o${~ATTl~!Q![${![!]%_!c!}${#R#S${#T#o${~AgV!Q![${![!]%_!c!}${#R#S${#T#X${#X#YA|#Y#o${~BPV!Q![${![!]%_!c!}${#R#S${#T#h${#h#iBf#i#o${~BkTz~!Q![${![!]%_!c!}${#R#S${#T#o${~B}V!Q![${![!]%_!c!}${#R#S${#T#h${#h#iCd#i#o${~CiTr~!Q![${![!]%_!c!}${#R#S${#T#o${~C{V!Q![${![!]%_!c!}${#R#S${#T#c${#c#dDb#d#o${~DeV!Q![${![!]%_!c!}${#R#S${#T#f${#f#gDz#g#o${~EPTm~!Q![${![!]%_!c!}${#R#S${#T#o${",
+  tokenizers: [0, 1],
+  topRules: {"Program":[0,1]},
+  tokenPrec: 0
+})
diff --git a/src/utils/parser.terms.js b/src/utils/parser.terms.js
new file mode 100644
index 0000000..0f7b867
--- /dev/null
+++ b/src/utils/parser.terms.js
@@ -0,0 +1,20 @@
+// This file was generated by lezer-generator. You probably shouldn't edit it.
+export const
+  Program = 1,
+  Statement = 2,
+  Label = 3,
+  LabelToken = 4,
+  Mnemonic = 5,
+  Operand = 6,
+  Register = 7,
+  Immediate = 8,
+  ImmediateToken = 9,
+  Comment = 10,
+  CommentToken = 11,
+  Newline = 12,
+  NewlineToken = 13,
+  Db = 14,
+  NumberList = 15,
+  Dw = 16,
+  StringList = 17,
+  StringToken = 18
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..6af53ad
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,6 @@
+/// <reference types="vite/client" />
+declare module '*.vue' {
+  import { ComponentOptions } from 'vue'
+  const componentOptions: ComponentOptions
+  export default componentOptions
+}
\ No newline at end of file
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 0000000..c692b24
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,27 @@
+{
+  "compilerOptions": {
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "Bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "preserve",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+    "noUncheckedSideEffectImports": true,
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+  "files": [],
+  "references": [
+    { "path": "./tsconfig.app.json" },
+    { "path": "./tsconfig.node.json" }
+  ]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..abcd7f0
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+  "compilerOptions": {
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+    "target": "ES2022",
+    "lib": ["ES2023"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "Bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+    "noUncheckedSideEffectImports": true
+  },
+  "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..bbcf80c
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+})