[feature] 只是能跑

This commit is contained in:
lichx 2024-11-19 21:09:20 +08:00
commit ad2916b018
34 changed files with 3428 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -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?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

5
README.md Normal file
View File

@ -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).

13
index.html Normal file
View File

@ -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>

1606
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
package.json Normal file
View File

@ -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"
}
}

1
public/vite.svg Normal file
View File

@ -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>

After

Width:  |  Height:  |  Size: 1.5 KiB

32
src/App.vue Normal file
View File

@ -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>

BIN
src/assets/compile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
src/assets/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src/assets/refresh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/assets/step.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
src/assets/stop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -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>

View File

@ -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>

147
src/components/Editors.vue Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

5
src/main.ts Normal file
View File

@ -0,0 +1,5 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')

62
src/my-asm-grammar Normal file
View File

@ -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" }
}

28
src/style.css Normal file
View File

@ -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;
}

50
src/utils/CodeChecker.ts Normal file
View File

@ -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: 如果栈为空7k条指令
*/
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;
}
}

217
src/utils/Complier.ts Normal file
View File

@ -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: 如果栈为空7k条指令
*/
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");
}
}

166
src/utils/MemoryManager.ts Normal file
View File

@ -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];
}
}

374
src/utils/Runner.ts Normal file
View File

@ -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");
}
}

16
src/utils/parser.js Normal file
View File

@ -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
})

20
src/utils/parser.terms.js Normal file
View File

@ -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

6
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { ComponentOptions } from 'vue'
const componentOptions: ComponentOptions
export default componentOptions
}

27
tsconfig.app.json Normal file
View File

@ -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"]
}

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

24
tsconfig.node.json Normal file
View File

@ -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"]
}

7
vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
})