Nachricht für neue Nutzer.
Nachricht für engagierte Nutzer.
Widget:KnowHowComputer: Unterschied zwischen den Versionen
Aus ZUM-Unterrichten
(Die Seite wurde neu angelegt: „<script type="text/javascript"> (function () { if (!customElements.get('know-how-computer')) { // Define the KnowHowComputer Web Component class KnowHowComputerElement extends HTMLElement { DEFAULT_MEMORY_CONFIGURATION = [ { name: 'Leer', program: {} }, { n…“) |
KKeine Bearbeitungszusammenfassung |
||
| (39 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
<script type="text/ | <noinclude>{{#tag:pre|{{msgnw:Widget:KnowHowComputer}}}}</noinclude><includeonly><script type="text/JavaScript"> | ||
(function () { | (function () { | ||
const STYLESHEET_HREFS = Array.from(document.styleSheets).filter(cssss => cssss.href).map(cssss => cssss.href); | |||
if (!customElements.get('know-how-computer')) { | if (!customElements.get('know-how-computer')) { | ||
// Define the KnowHowComputer Web Component | // Define the KnowHowComputer Web Component | ||
class KnowHowComputerElement extends HTMLElement { | class KnowHowComputerElement extends HTMLElement { | ||
static observedAttributes = ['no-help', 'memory-size']; | |||
/** | |||
* @typedef {object} MemoryConfiguration | |||
* @property {string} name | |||
* @property {Record<int,string>} program | |||
*/ | |||
/** @type {MemoryConfiguration[]} */ | |||
#memoryConfigurations = []; | |||
]; | |||
/** @type {?MutationObserver} */ | |||
#observer; | |||
/** @type {int} */ | |||
#selectedMemory; | |||
constructor() { | constructor() { | ||
| Zeile 30: | Zeile 28: | ||
// Add the HTML template and styles to the shadow DOM | // Add the HTML template and styles to the shadow DOM | ||
this.shadowRoot.innerHTML = `<style> | this.shadowRoot.innerHTML = `<style> | ||
:host { | :host { | ||
display: block; | display: block; | ||
margin-block: 1rem; | |||
} | |||
* { | |||
box-sizing: border-box; | |||
} | |||
.no-help .khc-help { | |||
display: none; | |||
} | |||
:host > section { | |||
display: flex; | |||
flex-direction: column; | |||
gap: 1rem; | |||
} | |||
.mw-ui-button.khc-help { | |||
min-height: auto; | |||
min-width: auto; | |||
aspect-ratio: 1; | |||
margin-inline-start: auto; | |||
} | } | ||
: | #memory-selector:not(:has(> button)) { | ||
display: | display: none; | ||
} | |||
#speicheranzeigefeld { | |||
overflow-y: scroll; | |||
overflow-block: scroll; | |||
} | } | ||
| Zeile 52: | Zeile 69: | ||
} | } | ||
label { | |||
font-weight: bold; | |||
} | |||
.steueritem { | |||
display: flex; | display: flex; | ||
gap: 0. | justify-content: space-between; | ||
gap: 0.5rem; | |||
> input { | > input { | ||
| Zeile 62: | Zeile 83: | ||
} | } | ||
figure { | |||
grid-area: main; | grid-area: main; | ||
display: flex; | display: flex; | ||
flex-direction: column; | flex-direction: column; | ||
margin: 0; | |||
fieldset { | |||
display: flex; | |||
flex-direction: column; | |||
gap: 0.5rem; | |||
} | |||
} | |||
dialog#hilfe[open] { | |||
display: flex; | |||
} | } | ||
dialog#hilfe { | |||
flex-direction: column; | |||
form { | |||
margin-block-start: 0.5rem; | |||
margin-inline-start: auto; | |||
} | |||
} | } | ||
details { | details { | ||
font-size: 0.95rem; | font-size: 0.95rem; | ||
} | |||
details, details > summary { | |||
display: revert; | |||
} | } | ||
| Zeile 82: | Zeile 124: | ||
} | } | ||
@media (max-width: | @media (max-width: 600px) { | ||
:host > div { | :host > div { | ||
display: flex; | display: flex; | ||
flex-direction: column; | flex-direction: column; | ||
grid-template-areas: | grid-template-areas: "title" "nav" "main" "side"; | ||
} | } | ||
} | } | ||
</style> | </style> | ||
< | <section> | ||
< | <div class="steueritem"> | ||
<button class="mw-ui-button" | |||
type="button" | |||
id="neustartschalter" | |||
aria-describedby="descr-neustartschalter" | |||
aria-details="details-neustartschalter">Neustart <kbd>F8</kbd></button> | |||
< | <button class="mw-ui-button" | ||
type="button" | |||
id="ausfuehrenschalter" | |||
aria-describedby="descr-ausfuehrenschalter" | |||
</ | aria-details="details-ausfuehrenschalter">Ausführen <kbd>F9</kbd></button> | ||
< | <button class="mw-ui-button khc-help" | ||
type="button" | |||
id="hilfeschalter" | |||
title="Hilfe">ℹ︎ | |||
</button> | |||
</div> | |||
<figure aria-describedby="details-khc"> | |||
<label for="programmzaehleranzeigefeld">Programmzähler</label> | <label for="programmzaehleranzeigefeld">Programmzähler</label> | ||
<input type="text" | <input type="text" | ||
id="programmzaehleranzeigefeld" | id="programmzaehleranzeigefeld" | ||
aria-describedby="descr-programmzaehleranzeigefeld" | aria-describedby="descr-programmzaehleranzeigefeld" | ||
aria-details="details-programmzaehleranzeigefeld" | |||
readonly | readonly | ||
disabled/> | disabled/> | ||
| Zeile 114: | Zeile 162: | ||
<label for="speicheranzeigefeld">Hauptspeicher</label> | <label for="speicheranzeigefeld">Hauptspeicher</label> | ||
<textarea id="speicheranzeigefeld" | <textarea id="speicheranzeigefeld" | ||
aria-describedby="descr-speicheranzeigefeld"> | aria-describedby="descr-speicheranzeigefeld" | ||
aria-details="details-speicheranzeigefeld"> | |||
</textarea> | </textarea> | ||
<fieldset id="memory-selector"> | |||
<legend>Speicherkonfiguration laden:</legend> | |||
</fieldset> | |||
</figure> | |||
<dialog id="hilfe" class="oo-ui-window-frame"> | |||
<details open> | <details open> | ||
<summary><span is="h2">Bedeutung der KHC Befehle</span></summary> | <summary><span is="h2">Bedeutung der KHC Befehle</span></summary> | ||
| Zeile 160: | Zeile 210: | ||
</p> | </p> | ||
</details> | </details> | ||
<details> | <details id="details-khc"> | ||
<summary>Kurzanleitung zu dieser JavaScript Implementierung des KHC</summary> | <summary>Kurzanleitung zu dieser JavaScript Implementierung des KHC</summary> | ||
<p>Die Bedienoberfläche des KHC besteht in dieser Implementierung aus folgenden fünf | <p>Die Bedienoberfläche des KHC besteht in dieser Implementierung aus folgenden fünf | ||
Elementen, die mit TAB bzw. UMSCHALT+TAB fokussiert (angewählt) werden können.</p> | Elementen, die mit TAB bzw. UMSCHALT+TAB fokussiert (angewählt) werden können.</p> | ||
<h3>Mehrzeiliges Textfeld Hauptspeicher</h3> | <h3 id="descr-speicheranzeigefeld">Mehrzeiliges Textfeld Hauptspeicher</h3> | ||
<p id=" | <p id="details-speicheranzeigefeld">bildet den Hauptspeicher des Know-How-Computers ab. Jede Zeile des | ||
Textfeldes enthält | Textfeldes enthält | ||
die | die | ||
| Zeile 188: | Zeile 238: | ||
ein Neustart mit dem Neustart-Schalter (F8) durchgeführt werden. | ein Neustart mit dem Neustart-Schalter (F8) durchgeführt werden. | ||
</p> | </p> | ||
<h3>Einzeiliges Textfeld Programmzähler</h3> | <h3 id="descr-programmzaehleranzeigefeld">Einzeiliges Textfeld Programmzähler</h3> | ||
<p id=" | <p id="details-programmzaehleranzeigefeld">enthält eine Kombination aus Programmzähler und | ||
Befehlsregister. Im | Befehlsregister. Im | ||
Befehlsregister | Befehlsregister | ||
| Zeile 197: | Zeile 247: | ||
dieser Befehl ins Befehlsregister geladen wurde. | dieser Befehl ins Befehlsregister geladen wurde. | ||
</p> | </p> | ||
<h3>Neustart-Schalter | <h3 id="descr-neustartschalter">Neustart-Schalter <kbd>F8</kbd></h3> | ||
<p id=" | <p id="details-neustartschalter">setzt den Programmzähler auf den Wert 1 zurück und lädt | ||
den Befehl aus | den Befehl aus | ||
der Zelle | der Zelle | ||
| Zeile 205: | Zeile 255: | ||
verändert. Der Neustart-Schalter kann auch mit der F8-Taste aktiviert werden. | verändert. Der Neustart-Schalter kann auch mit der F8-Taste aktiviert werden. | ||
</p> | </p> | ||
<h3>Ausführen-Schalter | <h3 id="descr-ausfuehrenschalter">Ausführen-Schalter <kbd>F9</kbd></h3> | ||
<p id=" | <p id="details-ausfuehrenschalter">führt den aktuellen Befehl im Befehlsregisters aus und lädt | ||
anschließend | anschließend | ||
den nächsten | den nächsten | ||
| Zeile 214: | Zeile 264: | ||
</p> | </p> | ||
</details> | </details> | ||
</ | <form method="dialog"> | ||
</ | <button autofocus>Hilfe schließen</button> | ||
</form> | |||
</dialog> | |||
</section>`; | |||
STYLESHEET_HREFS.map(function (href) { | |||
const styleElem = document.createElement("link"); | |||
styleElem.setAttribute("href", href); | |||
styleElem.setAttribute("rel", "stylesheet"); | |||
return styleElem; | |||
}).forEach(linkElem => this.shadowRoot.prepend(linkElem)); | |||
this.shadowRoot.getElementById("hilfeschalter").addEventListener("click", (event) => { | |||
const dialog = this.shadowRoot.getElementById("hilfe"); | |||
if (dialog instanceof HTMLDialogElement) { | |||
if (dialog.open) { | |||
dialog.close(); | |||
} else { | |||
dialog.show(); | |||
} | |||
} | |||
}); | |||
// Create the KnowHowComputer instance | // Create the KnowHowComputer instance | ||
this.khc = new KnowHowComputer(this.shadowRoot); | this.khc = new KnowHowComputer(this.shadowRoot, {}); | ||
} | } | ||
refreshMemoryConfigurations() { | refreshMemoryConfigurations() { | ||
this.memoryConfigurations = [ | this.#memoryConfigurations = [ | ||
...this.parseMemoryElements() | ...this.parseMemoryElements() | ||
]; | ]; | ||
this.refreshMemorySelector(this.memoryConfigurations); | this.refreshMemorySelector(this.#memoryConfigurations); | ||
if (!this.#selectedMemory) { | |||
if (this.khc && this.#memoryConfigurations[0] | |||
) { | |||
this.khc.loadProgram(this.#memoryConfigurations[0].program); | |||
} | |||
} | |||
} | |||
refreshHelp() { | |||
this.shadowRoot.querySelector(':host > *') | |||
.classList | |||
.toggle('no-help', this.hasAttribute('no-help')); | |||
} | |||
refreshKhcConfiguration() { | |||
let memSize; | |||
if (this.hasAttribute('memory-size')) { | |||
memSize = Number.parseInt(this.getAttribute('memory-size'), 10); | |||
} | |||
if (!Number.isFinite(memSize) || !Number.isSafeInteger(memSize)) { | |||
memSize = 15; | |||
} | |||
this.khc.setMemorySize(Math.max(memSize, 1)); | |||
} | |||
// noinspection JSUnusedGlobalSymbols | |||
attributeChangedCallback(_name, _oldValue, _newValue) { | |||
this.refreshHelp() | |||
this.refreshKhcConfiguration() | |||
} | } | ||
// Lifecycle callbacks | // Lifecycle callbacks | ||
// noinspection JSUnusedGlobalSymbols | |||
connectedCallback() { | connectedCallback() { | ||
// Component is now in the DOM | // Component is now in the DOM | ||
| Zeile 234: | Zeile 337: | ||
// Parse khc-memory child elements | // Parse khc-memory child elements | ||
this.refreshMemoryConfigurations(); | this.refreshMemoryConfigurations(); | ||
this.observer = new MutationObserver( | this.#observer = new MutationObserver(_ => { | ||
this.refreshMemoryConfigurations(); | this.refreshMemoryConfigurations(); | ||
}); | }); | ||
this.observer.observe(this, {childList: true}); | this.#observer.observe(this, {childList: true}); | ||
// Add event listener for memory selector | // Add event listener for memory selector | ||
const memorySelector = this.shadowRoot.getElementById('memory-selector'); | const memorySelector = this.shadowRoot.getElementById('memory-selector'); | ||
memorySelector.addEventListener(' | memorySelector.addEventListener('click', (e) => { | ||
const selectedValue = | const selectedValue = e.target.value; | ||
// Load program from khc-memory elements | // Load program from khc-memory elements | ||
const index = parseInt(selectedValue); | const index = parseInt(selectedValue); | ||
if (index >= 0 && index < this.memoryConfigurations.length) { | if (index >= 0 && index < this.#memoryConfigurations.length) { | ||
this.loadMemoryConfiguration(this.memoryConfigurations[index]); | this.#selectedMemory = index; | ||
this.loadMemoryConfiguration(this.#memoryConfigurations[index]); | |||
} | } | ||
e.preventDefault(); | |||
}); | }); | ||
this.refreshMemoryConfigurations(); | |||
this.refreshHelp(); | |||
this.refreshKhcConfiguration(); | |||
} | } | ||
// noinspection JSUnusedGlobalSymbols | |||
disconnectedCallback() { | disconnectedCallback() { | ||
// Component is removed from the DOM | // Component is removed from the DOM | ||
// Clean up any event listeners if needed | // Clean up any event listeners if needed | ||
this.#observer?.disconnect(); | |||
} | } | ||
| Zeile 266: | Zeile 372: | ||
// Use Array.from to convert HTMLCollection to Array | // Use Array.from to convert HTMLCollection to Array | ||
return Array.from(this.querySelectorAll('khc-memory')) | return Array.from(this.querySelectorAll('khc-memory')) | ||
.map(element => { | .map((element, idx) => { | ||
const programText = element.textContent.trim(); | const programText = element.textContent.trim(); | ||
const program = this.parseProgram(programText); | const [name, program] = this.parseProgram(programText); | ||
return { | return { | ||
name: name, | name: name ?? `Program ${idx + 1}`, | ||
program: program | program: program | ||
}; | }; | ||
| Zeile 280: | Zeile 385: | ||
// Parse program text into a program object | // Parse program text into a program object | ||
parseProgram(programText) { | parseProgram(programText) { | ||
const lines = programText.split('\n') | |||
.map(line => line.trim()) | |||
.filter(line => line !== ''); | |||
let name = | |||
lines.slice(0, 1) | |||
.filter(l => l.startsWith('#')) | |||
.map(l => l.replace(/^#*/, '')) | |||
.map(l => l.trim()) | |||
.find(l => l !== '') ?? null; | |||
return [ | |||
name, | |||
Object.fromEntries( | |||
lines | |||
.filter(l => !l.startsWith('#')) // filter out comments | |||
.map(l => l.split(':', 2).map(s => s.trim())) // split and trim parts | |||
.filter(([address, instruction]) => (address && instruction))) // filter incomplete lines; | |||
]; | |||
} | } | ||
| Zeile 290: | Zeile 409: | ||
refreshMemorySelector(memoryConfigurations) { | refreshMemorySelector(memoryConfigurations) { | ||
const memorySelector = this.shadowRoot.getElementById('memory-selector'); | const memorySelector = this.shadowRoot.getElementById('memory-selector'); | ||
memorySelector.querySelectorAll('button').forEach(button => memorySelector.removeChild(button)); | |||
// Add options for each memory configuration | // Add options for each memory configuration | ||
memoryConfigurations.forEach((config, index) => { | memoryConfigurations.forEach((config, index) => { | ||
const | const button = document.createElement('button'); | ||
button.value = (index).toString(); | |||
button.textContent = config.name; | |||
memorySelector.appendChild( | button.type = 'button'; | ||
button.classList.add('mw-ui-button'); | |||
memorySelector.appendChild(button); | |||
}); | }); | ||
} | } | ||
| Zeile 317: | Zeile 436: | ||
// Configuration variables | // Configuration variables | ||
this.speicheranzahl = | this.speicheranzahl = 15; // Standardwert ist 15 für Adr. 1-15, kann aber geändert werden. Adr. 0 wird nicht benutzt | ||
this.maxwert = 99; | this.maxwert = 99; | ||
this.spaltenanzahlinspeicheranzeigefeld = 10; | this.spaltenanzahlinspeicheranzeigefeld = 10; | ||
| Zeile 323: | Zeile 442: | ||
// State variables | // State variables | ||
this.mem = | /** @type {string[]} */ | ||
this.mem = this.getMemForProgramm({}, this.speicheranzahl); | |||
this.pz = 1; // Programmzähler | this.pz = 1; // Programmzähler | ||
this.br = ""; // Befehlsregister | this.br = ""; // Befehlsregister | ||
// Constants and utility variables | // Constants and utility variables | ||
this.ziffern = "0123456789"; | this.ziffern = new Set("0123456789".split('')); | ||
this.lueckenzeichen = " \r\n\t" | this.lueckenzeichen = new Set(" \r\n\t".split('')); | ||
this.doppelpunkt = new Set(":".split('')); | |||
this.doppelpunkt = ":"; | this.kommentarzeichen = new Set(":".split('')); | ||
this.kommentarzeichen = " | this.khcbefehle = new Set(["isz", "jmp", "inc", "dec", "stp"]); | ||
this.khcbefehle = new | this.khcbuchstaben = new Set(this.khcbefehle.values().flatMap(s => s.split(''))); | ||
// Lexer/parser state | // Lexer/parser state | ||
this.symtypen = | /** @type {string[]} */ | ||
this.symwerte = | this.symtypen = []; | ||
/** @type {string[]} */ | |||
this.symwerte = []; | |||
this.symindex = -1; | this.symindex = -1; | ||
// Error messages | // Error messages | ||
this.fehlermeldungen = | this.fehlermeldungen = []; | ||
this.fehlermeldungen[0] = "Keinen Fehler gefunden"; | this.fehlermeldungen[0] = "Keinen Fehler gefunden"; | ||
this.fehlermeldungen[1] = "Unerlaubtes Zeichen"; | this.fehlermeldungen[1] = "Unerlaubtes Zeichen"; | ||
| Zeile 374: | Zeile 497: | ||
zahltrimmen(zahlstr, min, max) { | zahltrimmen(zahlstr, min, max) { | ||
let test = parseInt(zahlstr); | |||
if (isNaN(test)) { | if (isNaN(test)) { | ||
test = min; | test = min; | ||
| Zeile 388: | Zeile 511: | ||
} | } | ||
return test; | return test; | ||
} | } | ||
| Zeile 401: | Zeile 520: | ||
// Lexer and parser | // Lexer and parser | ||
khclexer(eingabe) { | khclexer(eingabe) { | ||
let z; // einzelnes Zeichen aus eingabe | |||
let i; | |||
this.symtypen = this.arrayleeren(this.symtypen); | this.symtypen = this.arrayleeren(this.symtypen); | ||
this.symwerte = this.arrayleeren(this.symwerte); | this.symwerte = this.arrayleeren(this.symwerte); | ||
this.symindex = -1; | this.symindex = -1; | ||
let symwert = ""; | |||
let symtyp = "l"; // z =zahl, b =buchstabenkette, l =luecke, : =doppelpunkt | |||
eingabe = " " + eingabe + " "; | eingabe = " " + eingabe + " "; | ||
let ztyp = "l"; | |||
for (i = 0; i < eingabe.length; i++) { | for (i = 0; i < eingabe.length; i++) { | ||
z = eingabe.charAt(i); | z = eingabe.charAt(i); | ||
if (this. | if (this.lueckenzeichen.has(z)) ztyp = "l"; // z ist ein Lückenzeichen | ||
else if (this. | else if (this.ziffern.has(z)) ztyp = "z"; // z ist eine Ziffer | ||
else if (this. | else if (this.doppelpunkt.has(z)) ztyp = ":"; // z ist ein Doppelpunkt | ||
else if (this. | else if (this.khcbuchstaben.has(z)) ztyp = "b"; // z kommt in einem der KHC-Befehle vor | ||
else if (this. | else if (this.kommentarzeichen.has(z)) ztyp = ";"; // z ist Kommentarzeichen | ||
else return false; | else return false; | ||
if (symtyp == ztyp) { // aktueller symtyp stimmt mit dem ztyp des gerade gelesenen Zeichens z überein | if (symtyp === ztyp) { // aktueller symtyp stimmt mit dem ztyp des gerade gelesenen Zeichens z überein | ||
symwert = symwert + z; | symwert = symwert + z; | ||
} else { // neues Symbol: | } else { // neues Symbol: | ||
if (symtyp != "l") { // bisheriges Symbol in Arrays speichern | if (symtyp !== "l") { // bisheriges Symbol in Arrays speichern | ||
this.symindex++; | this.symindex++; | ||
this.symwerte[this.symindex] = symwert; | this.symwerte[this.symindex] = symwert; | ||
this.symtypen[this.symindex] = symtyp; | this.symtypen[this.symindex] = symtyp; | ||
} | } | ||
if (ztyp != ";") { | if (ztyp !== ";") { | ||
symtyp = ztyp; // Anfang des neuen Symbols in sym-Vars speichern | symtyp = ztyp; // Anfang des neuen Symbols in sym-Vars speichern | ||
symwert = z; | symwert = z; | ||
| Zeile 440: | Zeile 559: | ||
istkhcbefehl(s) { | istkhcbefehl(s) { | ||
return this.khcbefehle.has(s); | |||
} | } | ||
istgueltigezahl(zahlstr, min, max) { | istgueltigezahl(zahlstr, min, max) { | ||
let ok = true; | |||
const test = parseInt(zahlstr); | |||
if (isNaN(test)) ok = false; | if (isNaN(test)) ok = false; | ||
else { | else { | ||
| Zeile 461: | Zeile 574: | ||
khcparser(zeile) { | khcparser(zeile) { | ||
// Rückgabewert: fehlerindex | // Rückgabewert: fehlerindex | ||
const ok = this.khclexer(zeile); | |||
if (this.symwerte.join("").length == 0) return -1; // Leerzeile liefert keine Fehlermeldung | if (this.symwerte.join("").length === 0) return -1; // Leerzeile liefert keine Fehlermeldung | ||
if (!ok) return 1; // "Unerlaubtes Zeichen" | if (!ok) return 1; // "Unerlaubtes Zeichen" | ||
if (this.symtypen[0] != "z") return 2; // "Syntaxfehler - Zahl erwartet" | if (this.symtypen[0] !== "z") return 2; // "Syntaxfehler - Zahl erwartet" | ||
if (!this.istgueltigezahl(this.symwerte[0], 1, this.speicheranzahl)) return 3; // "Adresse nicht im gültigen Bereich" | if (!this.istgueltigezahl(this.symwerte[0], 1, this.speicheranzahl)) return 3; // "Adresse nicht im gültigen Bereich" | ||
if (this.symtypen[1] != ":") return 6; // "Syntaxfehler - Doppelpunkt erwartet" | if (this.symtypen[1] !== ":") return 6; // "Syntaxfehler - Doppelpunkt erwartet" | ||
if (this.symtypen[2] != "z" && this.symtypen[2] != "b") return 7; // "Syntaxfehler - Zahl oder Befehl erwartet" | if (this.symtypen[2] !== "z" && this.symtypen[2] !== "b") return 7; // "Syntaxfehler - Zahl oder Befehl erwartet" | ||
if (this.symtypen[2] == "z") { | if (this.symtypen[2] === "z") { | ||
if (!this.istgueltigezahl(this.symwerte[2], 0, this.maxwert)) return 4; // "Wert nicht im gültigen Bereich" | if (!this.istgueltigezahl(this.symwerte[2], 0, this.maxwert)) return 4; // "Wert nicht im gültigen Bereich" | ||
} | } | ||
if (this.symtypen[2] == "b") { | if (this.symtypen[2] === "b") { | ||
if (!this.istkhcbefehl(this.symwerte[2])) return 5; // "Ungültiger Befehl" | if (!this.istkhcbefehl(this.symwerte[2])) return 5; // "Ungültiger Befehl" | ||
if (this.symwerte[2] == "stp" && this.symtypen.length > 3) return 8; // "Syntaxfehler" | if (this.symwerte[2] === "stp" && this.symtypen.length > 3) return 8; // "Syntaxfehler" | ||
if (this.symwerte[2] != "stp") { | if (this.symwerte[2] !== "stp") { | ||
if (this.symtypen[3] != "z") return 9; // "Syntaxfehler" | if (this.symtypen[3] !== "z") return 9; // "Syntaxfehler" | ||
if (!this.istgueltigezahl(this.symwerte[3], 1, this.speicheranzahl)) return 3; // "Adresse nicht im gültigen Bereich" | if (!this.istgueltigezahl(this.symwerte[3], 1, this.speicheranzahl)) return 3; // "Adresse nicht im gültigen Bereich" | ||
} | } | ||
| Zeile 484: | Zeile 597: | ||
// Initialization and control | // Initialization and control | ||
init() { | init() { | ||
this.loadProgram({}); | |||
this. | |||
// Set up event listeners | // Set up event listeners | ||
| Zeile 532: | Zeile 633: | ||
} | } | ||
}); | }); | ||
} | |||
loadProgram(program) { | |||
this.mem = this.getMemForProgramm(program ?? {}, this.speicheranzahl); | |||
// Restart the emulator | |||
this.neustart(); | |||
} | } | ||
// Load a program from a program object | // Load a program from a program object | ||
getMemForProgramm(program, speicheranzahl) { | |||
const newMem = new Array(speicheranzahl + 1);//expected size | |||
newMem.fill(undefined); | |||
// Load program into memory | // Load program into memory | ||
for (const [address, instruction] of Object.entries(program)) { | for (const [address, instruction] of Object.entries(program)) { | ||
if (parseInt(address) >= 1 && parseInt(address) | if (parseInt(address) >= 1 && parseInt(address)) { | ||
newMem[address] = instruction; | |||
} | } | ||
} | } | ||
// fill all empty memory cells with 0 | |||
// | newMem.forEach((val, idx, self) => { | ||
if (val === undefined) { | |||
self[idx] = '0'; | |||
} | |||
}); | |||
return newMem; | |||
} | } | ||
| Zeile 561: | Zeile 669: | ||
registeranzeigefeldaktualisieren() { | registeranzeigefeldaktualisieren() { | ||
this.container.getElementById("programmzaehleranzeigefeld").value = this.rechtsbuendig(this.pz) + ": " + this.stringmitleerzeichenauffuellen(this.mem[this.pz], this.spaltenanzahlinspeicheranzeigefeld - 3); | |||
} | } | ||
anzeigeaktualisieren() { | anzeigeaktualisieren() { | ||
// Anzeige des gesamten Speichers aktualisieren: | // Anzeige des gesamten Speichers aktualisieren: | ||
const | /**@type HTMLTextAreaElement */ | ||
const speicheranzeigefeld = this.container.getElementById("speicheranzeigefeld"); | |||
speicheranzeigefeld.value = this.mem.slice(1).map((speicher, idx) => this.rechtsbuendig(idx + 1) + ": " + speicher).join('\n'); | |||
/**@type HTMLInputElement */ | |||
const registeranzeigefeld = this.container.getElementById("programmzaehleranzeigefeld"); | |||
speicheranzeigefeld.rows = this.mem.length - 1 | |||
speicheranzeigefeld.cols = this.spaltenanzahlinspeicheranzeigefeld; | |||
registeranzeigefeld.size = this.spaltenanzahlinspeicheranzeigefeld; | |||
this.registeranzeigefeldaktualisieren(); | this.registeranzeigefeldaktualisieren(); | ||
this.zeileinspeicheranzeigefeldmarkieren(this.pz); | this.zeileinspeicheranzeigefeldmarkieren(this.pz); | ||
| Zeile 574: | Zeile 690: | ||
befehlausfuehren() { | befehlausfuehren() { | ||
let fehlerindex = 0; | |||
this.speicherfeldanzeigeinspeicheruebernehmen(); | this.speicherfeldanzeigeinspeicheruebernehmen(); | ||
console.log("mem[pz] = " + this.mem[this.pz]); | console.log("mem[pz] = " + this.mem[this.pz]); | ||
this.br = this.mem[this.pz]; | this.br = this.mem[this.pz]; | ||
const z = parseInt(this.br); | |||
console.log("Start befehlausfuehren mit z = parseint(br): " + z); | console.log("Start befehlausfuehren mit z = parseint(br): " + z); | ||
console.log("in befehlausfuehren vor Test: pz = " + this.pz + " br = " + this.br); | console.log("in befehlausfuehren vor Test: pz = " + this.pz + " br = " + this.br); | ||
| Zeile 589: | Zeile 705: | ||
} else { | } else { | ||
// sonst gehen wir von einem Befehl aus: | // sonst gehen wir von einem Befehl aus: | ||
const befehlscode = this.br.substr(0, 3).toLowerCase(); | |||
// Bei stp ist keine Adresse erforderlich und es wird auch nichts gemacht: | // Bei stp ist keine Adresse erforderlich und es wird auch nichts gemacht: | ||
if (befehlscode == "stp") return 0; | if (befehlscode === "stp") return 0; | ||
// sonst muss eine ganze Zahl als Adresse folgen: | // sonst muss eine ganze Zahl als Adresse folgen: | ||
var adresse = parseInt(this.br.substr(4)); // ab Pos. 4 bis zum Ende ausschneiden | var adresse = parseInt(this.br.substr(4)); // ab Pos. 4 bis zum Ende ausschneiden | ||
| Zeile 601: | Zeile 717: | ||
break; | break; | ||
case "isz": | case "isz": | ||
if (this.mem[adresse] | if (this.isZero(this.mem[adresse])) this.pz = this.pz + 2; else this.pz = this.pz + 1; | ||
break; | break; | ||
case "inc": | case "inc": | ||
| Zeile 607: | Zeile 723: | ||
wert = parseInt(wert) + 1; | wert = parseInt(wert) + 1; | ||
wert = this.zahltrimmen(wert, 0, this.maxwert); | wert = this.zahltrimmen(wert, 0, this.maxwert); | ||
this.mem[adresse] = wert; | this.mem[adresse] = String(wert); | ||
this.pz = this.pz + 1; | this.pz = this.pz + 1; | ||
break; | break; | ||
| Zeile 614: | Zeile 730: | ||
wert2 = parseInt(wert2) - 1; | wert2 = parseInt(wert2) - 1; | ||
wert2 = this.zahltrimmen(wert2, 0, this.maxwert); | wert2 = this.zahltrimmen(wert2, 0, this.maxwert); | ||
this.mem[adresse] = wert2; | this.mem[adresse] = String(wert2); | ||
this.pz = this.pz + 1; | this.pz = this.pz + 1; | ||
break; | break; | ||
| Zeile 627: | Zeile 743: | ||
this.zeileinspeicheranzeigefeldmarkieren(this.pz); | this.zeileinspeicheranzeigefeldmarkieren(this.pz); | ||
return fehlerindex; | return fehlerindex; | ||
} | |||
isZero(zeile) { | |||
return 0 === parseInt(zeile); | |||
} | } | ||
einezeileinspeicheruebernehmen(zeile) { | einezeileinspeicheruebernehmen(zeile) { | ||
zeile = zeile.toLowerCase(); | zeile = zeile.toLowerCase(); | ||
const fehlerindex = this.khcparser(zeile); | |||
if (fehlerindex === 0) { | |||
if (fehlerindex == 0) { | const pz = parseInt(this.symwerte[0]); | ||
let befehlszeile = this.symwerte[2]; | |||
if (this.symwerte.length > 3) { | |||
if (this.symwerte.length > 3) befehlszeile = befehlszeile + " " + this.symwerte[3]; | befehlszeile = befehlszeile + " " + this.symwerte[3]; | ||
} | |||
this.mem[pz] = befehlszeile; | this.mem[pz] = befehlszeile; | ||
} else { | } else { | ||
| Zeile 659: | Zeile 779: | ||
domElem.selectionStart = posStart; | domElem.selectionStart = posStart; | ||
domElem.selectionEnd = posEnde; | domElem.selectionEnd = posEnde; | ||
} | } | ||
} | } | ||
| Zeile 676: | Zeile 789: | ||
} | } | ||
setMemorySize(memSize) { | |||
this.speicheranzahl = memSize; | |||
this.updateMemSize(); | |||
} | } | ||
updateMemSize() { | |||
const | const currentProgram = this.mem.reduce((p, v, idx) => { | ||
if (!this.isZero(v)) { | |||
this. | p[idx] = v; | ||
} | |||
return p; | |||
}, {}) | |||
this.mem = this.getMemForProgramm(currentProgram, this.speicheranzahl); | |||
this.anzeigeaktualisieren(); | |||
} | } | ||
} | } | ||
| Zeile 711: | Zeile 820: | ||
// Lifecycle callbacks | // Lifecycle callbacks | ||
// noinspection JSUnusedGlobalSymbols | |||
connectedCallback() { | connectedCallback() { | ||
// Hide the element by default as it's just a data container | // Hide the element by default as it's just a data container | ||
| Zeile 720: | Zeile 830: | ||
} | } | ||
<know-how-computer></know-how-computer> | // Initialize when the DOM is loaded | ||
document.addEventListener('DOMContentLoaded', function () { | |||
// For MediaWiki gadget, this would be the initialization code | |||
// No need to do anything here as the custom element will initialize itself when added to the DOM | |||
}); | |||
})(); | |||
</script> | |||
<know-how-computer | |||
<!--{if $noHelp|validate:'boolean'|default:'false' == 'true'}--> | |||
no-help | |||
<!--{/if}--> | |||
<!--{if $Speichergroesse|validate:'int'|default:'-1' != '-1'}--> | |||
memory-size="<!--{$Speichergroesse|escape:'htmlall'}-->" | |||
<!--{/if}--> | |||
> | |||
<!--{if $Speicherkonfiguration1|default:'' != ''}--> | |||
<khc-memory><!--{$Speicherkonfiguration1|escape:'htmlall'}--></khc-memory> | |||
<!--{/if}--> | |||
<!--{if $Speicherkonfiguration2|default:'' != ''}--> | |||
<khc-memory><!--{$Speicherkonfiguration2|escape:'htmlall'}--></khc-memory> | |||
<!--{/if}--> | |||
<!--{if $Speicherkonfiguration3|default:'' != ''}--> | |||
<khc-memory><!--{$Speicherkonfiguration3|escape:'htmlall'}--></khc-memory> | |||
<!--{/if}--> | |||
<!--{if $Speicherkonfiguration4|default:'' != ''}--> | |||
<khc-memory><!--{$Speicherkonfiguration4|escape:'htmlall'}--></khc-memory> | |||
<!--{/if}--> | |||
<!--{if $Speicherkonfiguration5|default:'' != ''}--> | |||
<khc-memory><!--{$Speicherkonfiguration5|escape:'htmlall'}--></khc-memory> | |||
<!--{/if}--> | |||
</know-how-computer> | |||
</includeonly> | |||
Aktuelle Version vom 29. Juni 2025, 20:16 Uhr
<noinclude>{{#tag:pre|{{msgnw:Widget:KnowHowComputer}}}}</noinclude><includeonly><script type="text/JavaScript">
(function () {
const STYLESHEET_HREFS = Array.from(document.styleSheets).filter(cssss => cssss.href).map(cssss => cssss.href);
if (!customElements.get('know-how-computer')) {
// Define the KnowHowComputer Web Component
class KnowHowComputerElement extends HTMLElement {
static observedAttributes = ['no-help', 'memory-size'];
/**
* @typedef {object} MemoryConfiguration
* @property {string} name
* @property {Record<int,string>} program
*/
/** @type {MemoryConfiguration[]} */
#memoryConfigurations = [];
/** @type {?MutationObserver} */
#observer;
/** @type {int} */
#selectedMemory;
constructor() {
super();
this.attachShadow({mode: 'open'});
// Add the HTML template and styles to the shadow DOM
this.shadowRoot.innerHTML = `<style>
:host {
display: block;
margin-block: 1rem;
}
* {
box-sizing: border-box;
}
.no-help .khc-help {
display: none;
}
:host > section {
display: flex;
flex-direction: column;
gap: 1rem;
}
.mw-ui-button.khc-help {
min-height: auto;
min-width: auto;
aspect-ratio: 1;
margin-inline-start: auto;
}
#memory-selector:not(:has(> button)) {
display: none;
}
#speicheranzeigefeld {
overflow-y: scroll;
overflow-block: scroll;
}
code {
font-weight: bolder;
}
label {
font-weight: bold;
}
.steueritem {
display: flex;
justify-content: space-between;
gap: 0.5rem;
> input {
max-height: 2rem;
}
}
figure {
grid-area: main;
display: flex;
flex-direction: column;
margin: 0;
fieldset {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}
dialog#hilfe[open] {
display: flex;
}
dialog#hilfe {
flex-direction: column;
form {
margin-block-start: 0.5rem;
margin-inline-start: auto;
}
}
details {
font-size: 0.95rem;
}
details, details > summary {
display: revert;
}
details > summary {
font-weight: bolder;
font-size: 1.15rem;
}
@media (max-width: 600px) {
:host > div {
display: flex;
flex-direction: column;
grid-template-areas: "title" "nav" "main" "side";
}
}
</style>
<section>
<div class="steueritem">
<button class="mw-ui-button"
type="button"
id="neustartschalter"
aria-describedby="descr-neustartschalter"
aria-details="details-neustartschalter">Neustart <kbd>F8</kbd></button>
<button class="mw-ui-button"
type="button"
id="ausfuehrenschalter"
aria-describedby="descr-ausfuehrenschalter"
aria-details="details-ausfuehrenschalter">Ausführen <kbd>F9</kbd></button>
<button class="mw-ui-button khc-help"
type="button"
id="hilfeschalter"
title="Hilfe">ℹ︎
</button>
</div>
<figure aria-describedby="details-khc">
<label for="programmzaehleranzeigefeld">Programmzähler</label>
<input type="text"
id="programmzaehleranzeigefeld"
aria-describedby="descr-programmzaehleranzeigefeld"
aria-details="details-programmzaehleranzeigefeld"
readonly
disabled/>
<label for="speicheranzeigefeld">Hauptspeicher</label>
<textarea id="speicheranzeigefeld"
aria-describedby="descr-speicheranzeigefeld"
aria-details="details-speicheranzeigefeld">
</textarea>
<fieldset id="memory-selector">
<legend>Speicherkonfiguration laden:</legend>
</fieldset>
</figure>
<dialog id="hilfe" class="oo-ui-window-frame">
<details open>
<summary><span is="h2">Bedeutung der KHC Befehle</span></summary>
<dl>
<dt><code>inc x</code></dt>
<dd>Erhöhe den Wert in Zelle x um 1 <br/>
und erhöhe den Programmzähler um 1
</dd>
<dt><code>dec x</code></dt>
<dd>Verringere den Wert in Zelle x um 1 <br/>
und erhöhe den Programmzähler um 1
</dd>
<dt><code> isz x</code></dt>
<dd>Wenn Zelle x den Wert 0 (<span lang="en">zero</span>) enthält,<br/>
dann erhöhe den Programmzähler um 2,<br/>
sonst erhöhe den Programmzähler um 1
</dd>
<dt><code>jmp x</code></dt>
<dd>Setze den Programmzähler auf den Wert x</dd>
<dt><code>stp </code></dt>
<dd>Beende das Programm</dd>
</dl>
</details>
<details>
<summary>Über den Know-How-Computer</summary>
<p>Die Idee zum Know-How-Computer wurde im Jahr 1983 von Wolfgang Back (WDR) und Ulrich Rohde (PC-Magazin)
entwickelt. Veröffentlicht wurde das Konzept des KHC u.a. in den Computerzeitschriften MC und
PC-Magazin. Weitere Informationen findet man auf <a
href="https://de.wikipedia.org/wiki/Know-how-Computer"
target="_blank">de.wikipedia.org/wiki/Know-how-Computer</a>
</p>
<p>Eine leicht abgewandelte Form des Know-How-Computers ist der so genannte Murmelrechner, der auf der Seite
<a
href="https://www.inf-schule.de/rechner/bonsai/murmelrechner" target="_blank">
www.inf-schule.de/rechner/bonsai/murmelrechner</a> vorgestellt wird.
</p>
<p>Diese JavaScript-Implementierung des Know-How-Computers ist auch für Menschen mit Seheinschränkungen
barrierefrei zugänglich. Sie wurde 2025 von Ulrich Kalina erstellt.
</p>
</details>
<details id="details-khc">
<summary>Kurzanleitung zu dieser JavaScript Implementierung des KHC</summary>
<p>Die Bedienoberfläche des KHC besteht in dieser Implementierung aus folgenden fünf
Elementen, die mit TAB bzw. UMSCHALT+TAB fokussiert (angewählt) werden können.</p>
<h3 id="descr-speicheranzeigefeld">Mehrzeiliges Textfeld Hauptspeicher</h3>
<p id="details-speicheranzeigefeld">bildet den Hauptspeicher des Know-How-Computers ab. Jede Zeile des
Textfeldes enthält
die
Nummer (Adresse) einer Speicherzelle und deren Inhalt. Dabei gehört die Adresse (Zahl vor dem
Doppelpunkt) selbst nicht zum Inhalt der Speicherzelle, sondern dient lediglich ihrer eindeutigen
Bezeichnung. Die Zelleninhalte (hinter dem Doppelpunkt) können entweder KHC-Befehle oder Daten
(Zahlenwerte von 0 bis 99) sein.<br>
Der gesamte Inhalt des Textfeldes kann über Tastatureingaben verändert werden. Dabei muss
in jeder
Zeile die Abfolge Adresse, Doppelpunkt, Befehl oder Datenwert eingehalten werden. Da es sich um ein
normales
Textfeld handelt, kann sein gesamter Inhalt, also ein ganzes KHC-Programm einfach durch Kopieren und
Einfügen
("Drag and Drop") aus einem Texteditor in den KHC-Speicher importiert oder umgekehrt in einen Editor
exportiert und in einer Textdatei gespeichert werden.<br>Der importierte Programmtext darf
Kommentare
enthalten. Diese beginnen mit einem Semikolon ; und reichen bis zum Zeilenende. Die Kommentare
werden
automatisch entfernt, wenn der Fokus das mehrzeilige Textfeld verlässt.<br>
Wenn in diesem Textfeld Programmzeilen verändert wurden, sollte vor der nächsten
Programmausführung
ein Neustart mit dem Neustart-Schalter (F8) durchgeführt werden.
</p>
<h3 id="descr-programmzaehleranzeigefeld">Einzeiliges Textfeld Programmzähler</h3>
<p id="details-programmzaehleranzeigefeld">enthält eine Kombination aus Programmzähler und
Befehlsregister. Im
Befehlsregister
(hinter dem Doppelpunkt) steht immer der aktuelle Befehl, der im nächsten Schritt ausgeführt
werden soll. Der Programmzähler (vor dem Doppelpunkt) gibt die Adresse der Speicherzelle an,
von der
dieser Befehl ins Befehlsregister geladen wurde.
</p>
<h3 id="descr-neustartschalter">Neustart-Schalter <kbd>F8</kbd></h3>
<p id="details-neustartschalter">setzt den Programmzähler auf den Wert 1 zurück und lädt
den Befehl aus
der Zelle
1 des Hauptspeichers in das Befehlsregister. Der Inhalt des Hauptspeichers wird durch einen Neustart
nicht
verändert. Der Neustart-Schalter kann auch mit der F8-Taste aktiviert werden.
</p>
<h3 id="descr-ausfuehrenschalter">Ausführen-Schalter <kbd>F9</kbd></h3>
<p id="details-ausfuehrenschalter">führt den aktuellen Befehl im Befehlsregisters aus und lädt
anschließend
den nächsten
Befehl aus dem Hauptspeicher dorthin. Der Ausführen-Schalter kann auch mit der F9-Taste
aktiviert
werden.
</p>
</details>
<form method="dialog">
<button autofocus>Hilfe schließen</button>
</form>
</dialog>
</section>`;
STYLESHEET_HREFS.map(function (href) {
const styleElem = document.createElement("link");
styleElem.setAttribute("href", href);
styleElem.setAttribute("rel", "stylesheet");
return styleElem;
}).forEach(linkElem => this.shadowRoot.prepend(linkElem));
this.shadowRoot.getElementById("hilfeschalter").addEventListener("click", (event) => {
const dialog = this.shadowRoot.getElementById("hilfe");
if (dialog instanceof HTMLDialogElement) {
if (dialog.open) {
dialog.close();
} else {
dialog.show();
}
}
});
// Create the KnowHowComputer instance
this.khc = new KnowHowComputer(this.shadowRoot, {});
}
refreshMemoryConfigurations() {
this.#memoryConfigurations = [
...this.parseMemoryElements()
];
this.refreshMemorySelector(this.#memoryConfigurations);
if (!this.#selectedMemory) {
if (this.khc && this.#memoryConfigurations[0]
) {
this.khc.loadProgram(this.#memoryConfigurations[0].program);
}
}
}
refreshHelp() {
this.shadowRoot.querySelector(':host > *')
.classList
.toggle('no-help', this.hasAttribute('no-help'));
}
refreshKhcConfiguration() {
let memSize;
if (this.hasAttribute('memory-size')) {
memSize = Number.parseInt(this.getAttribute('memory-size'), 10);
}
if (!Number.isFinite(memSize) || !Number.isSafeInteger(memSize)) {
memSize = 15;
}
this.khc.setMemorySize(Math.max(memSize, 1));
}
// noinspection JSUnusedGlobalSymbols
attributeChangedCallback(_name, _oldValue, _newValue) {
this.refreshHelp()
this.refreshKhcConfiguration()
}
// Lifecycle callbacks
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
// Component is now in the DOM
// Parse khc-memory child elements
this.refreshMemoryConfigurations();
this.#observer = new MutationObserver(_ => {
this.refreshMemoryConfigurations();
});
this.#observer.observe(this, {childList: true});
// Add event listener for memory selector
const memorySelector = this.shadowRoot.getElementById('memory-selector');
memorySelector.addEventListener('click', (e) => {
const selectedValue = e.target.value;
// Load program from khc-memory elements
const index = parseInt(selectedValue);
if (index >= 0 && index < this.#memoryConfigurations.length) {
this.#selectedMemory = index;
this.loadMemoryConfiguration(this.#memoryConfigurations[index]);
}
e.preventDefault();
});
this.refreshMemoryConfigurations();
this.refreshHelp();
this.refreshKhcConfiguration();
}
// noinspection JSUnusedGlobalSymbols
disconnectedCallback() {
// Component is removed from the DOM
// Clean up any event listeners if needed
this.#observer?.disconnect();
}
// Parse khc-memory child elements
parseMemoryElements() {
// Use Array.from to convert HTMLCollection to Array
return Array.from(this.querySelectorAll('khc-memory'))
.map((element, idx) => {
const programText = element.textContent.trim();
const [name, program] = this.parseProgram(programText);
return {
name: name ?? `Program ${idx + 1}`,
program: program
};
});
}
// Parse program text into a program object
parseProgram(programText) {
const lines = programText.split('\n')
.map(line => line.trim())
.filter(line => line !== '');
let name =
lines.slice(0, 1)
.filter(l => l.startsWith('#'))
.map(l => l.replace(/^#*/, ''))
.map(l => l.trim())
.find(l => l !== '') ?? null;
return [
name,
Object.fromEntries(
lines
.filter(l => !l.startsWith('#')) // filter out comments
.map(l => l.split(':', 2).map(s => s.trim())) // split and trim parts
.filter(([address, instruction]) => (address && instruction))) // filter incomplete lines;
];
}
// Populate memory selector dropdown
refreshMemorySelector(memoryConfigurations) {
const memorySelector = this.shadowRoot.getElementById('memory-selector');
memorySelector.querySelectorAll('button').forEach(button => memorySelector.removeChild(button));
// Add options for each memory configuration
memoryConfigurations.forEach((config, index) => {
const button = document.createElement('button');
button.value = (index).toString();
button.textContent = config.name;
button.type = 'button';
button.classList.add('mw-ui-button');
memorySelector.appendChild(button);
});
}
// Load memory configuration
loadMemoryConfiguration(config) {
if (this.khc) {
this.khc.loadProgram(config.program);
}
}
}
// KnowHowComputer class that handles the emulator logic
class KnowHowComputer {
constructor(containerNode) {
// Store the container node
this.container = containerNode;
// Configuration variables
this.speicheranzahl = 15; // Standardwert ist 15 für Adr. 1-15, kann aber geändert werden. Adr. 0 wird nicht benutzt
this.maxwert = 99;
this.spaltenanzahlinspeicheranzeigefeld = 10;
this.zeilenanzahlinspeicheranzeigefeld = this.speicheranzahl; // kann auch abweichend von speicheranzahl gewählt werden
// State variables
/** @type {string[]} */
this.mem = this.getMemForProgramm({}, this.speicheranzahl);
this.pz = 1; // Programmzähler
this.br = ""; // Befehlsregister
// Constants and utility variables
this.ziffern = new Set("0123456789".split(''));
this.lueckenzeichen = new Set(" \r\n\t".split(''));
this.doppelpunkt = new Set(":".split(''));
this.kommentarzeichen = new Set(":".split(''));
this.khcbefehle = new Set(["isz", "jmp", "inc", "dec", "stp"]);
this.khcbuchstaben = new Set(this.khcbefehle.values().flatMap(s => s.split('')));
// Lexer/parser state
/** @type {string[]} */
this.symtypen = [];
/** @type {string[]} */
this.symwerte = [];
this.symindex = -1;
// Error messages
this.fehlermeldungen = [];
this.fehlermeldungen[0] = "Keinen Fehler gefunden";
this.fehlermeldungen[1] = "Unerlaubtes Zeichen";
this.fehlermeldungen[2] = "Zahl erwartet";
this.fehlermeldungen[3] = "Adresse nicht im gültigen Bereich";
this.fehlermeldungen[4] = "Wert nicht im gültigen Bereich";
this.fehlermeldungen[5] = "Ungültiger Befehl";
this.fehlermeldungen[6] = "Syntaxfehler: Doppelpunkt erwartet";
this.fehlermeldungen[7] = "Syntaxfehler: Zahl oder Befehl erwartet";
this.fehlermeldungen[8] = "Syntaxfehler: stp ohne Operandenadresse ";
this.fehlermeldungen[9] = "Syntaxfehler: Operandenadresse erwartet";
this.fehlermeldungen[10] = "Laufzeitfehler: Kein ausführbarer Befehl";
this.fehlermeldungen[11] = "Laufzeitfehler: Operand würde zu klein";
this.fehlermeldungen[12] = "Laufzeitfehler: Operand würde zu groß";
// Initialize the emulator
this.init();
}
// Helper methods
meldung(index) {
alert(this.fehlermeldungen[index]);
}
stringmitleerzeichenauffuellen(str, bislaenge) { // neu 24.01.2024
return str.trimEnd().padEnd(bislaenge);
}
rechtsbuendig(s) {
return String(s).trimStart().padStart(2)
}
zahltrimmen(zahlstr, min, max) {
let test = parseInt(zahlstr);
if (isNaN(test)) {
test = min;
} else {
if (test < min) {
test = min;
this.meldung(11);
}
if (test > max) {
test = max;
this.meldung(12);
}
}
return test;
}
arrayleeren(a) {
a.splice(0)
return a;
}
// Lexer and parser
khclexer(eingabe) {
let z; // einzelnes Zeichen aus eingabe
let i;
this.symtypen = this.arrayleeren(this.symtypen);
this.symwerte = this.arrayleeren(this.symwerte);
this.symindex = -1;
let symwert = "";
let symtyp = "l"; // z =zahl, b =buchstabenkette, l =luecke, : =doppelpunkt
eingabe = " " + eingabe + " ";
let ztyp = "l";
for (i = 0; i < eingabe.length; i++) {
z = eingabe.charAt(i);
if (this.lueckenzeichen.has(z)) ztyp = "l"; // z ist ein Lückenzeichen
else if (this.ziffern.has(z)) ztyp = "z"; // z ist eine Ziffer
else if (this.doppelpunkt.has(z)) ztyp = ":"; // z ist ein Doppelpunkt
else if (this.khcbuchstaben.has(z)) ztyp = "b"; // z kommt in einem der KHC-Befehle vor
else if (this.kommentarzeichen.has(z)) ztyp = ";"; // z ist Kommentarzeichen
else return false;
if (symtyp === ztyp) { // aktueller symtyp stimmt mit dem ztyp des gerade gelesenen Zeichens z überein
symwert = symwert + z;
} else { // neues Symbol:
if (symtyp !== "l") { // bisheriges Symbol in Arrays speichern
this.symindex++;
this.symwerte[this.symindex] = symwert;
this.symtypen[this.symindex] = symtyp;
}
if (ztyp !== ";") {
symtyp = ztyp; // Anfang des neuen Symbols in sym-Vars speichern
symwert = z;
} else { // Kommentarzeichen, also aufhören:
return true;
}
}
}
return true;
}
istkhcbefehl(s) {
return this.khcbefehle.has(s);
}
istgueltigezahl(zahlstr, min, max) {
let ok = true;
const test = parseInt(zahlstr);
if (isNaN(test)) ok = false;
else {
if (test < min || test > max) ok = false;
}
return ok;
}
khcparser(zeile) {
// Rückgabewert: fehlerindex
const ok = this.khclexer(zeile);
if (this.symwerte.join("").length === 0) return -1; // Leerzeile liefert keine Fehlermeldung
if (!ok) return 1; // "Unerlaubtes Zeichen"
if (this.symtypen[0] !== "z") return 2; // "Syntaxfehler - Zahl erwartet"
if (!this.istgueltigezahl(this.symwerte[0], 1, this.speicheranzahl)) return 3; // "Adresse nicht im gültigen Bereich"
if (this.symtypen[1] !== ":") return 6; // "Syntaxfehler - Doppelpunkt erwartet"
if (this.symtypen[2] !== "z" && this.symtypen[2] !== "b") return 7; // "Syntaxfehler - Zahl oder Befehl erwartet"
if (this.symtypen[2] === "z") {
if (!this.istgueltigezahl(this.symwerte[2], 0, this.maxwert)) return 4; // "Wert nicht im gültigen Bereich"
}
if (this.symtypen[2] === "b") {
if (!this.istkhcbefehl(this.symwerte[2])) return 5; // "Ungültiger Befehl"
if (this.symwerte[2] === "stp" && this.symtypen.length > 3) return 8; // "Syntaxfehler"
if (this.symwerte[2] !== "stp") {
if (this.symtypen[3] !== "z") return 9; // "Syntaxfehler"
if (!this.istgueltigezahl(this.symwerte[3], 1, this.speicheranzahl)) return 3; // "Adresse nicht im gültigen Bereich"
}
}
return 0; // keinen Fehler gefunden
}
// Initialization and control
init() {
this.loadProgram({});
// Set up event listeners
this.setupEventListeners();
}
setupEventListeners() {
const self = this;
this.container.getElementById('ausfuehrenschalter').addEventListener('click', function () {
self.befehlausfuehren();
});
this.container.getElementById('neustartschalter').addEventListener('click', function () {
self.neustart();
});
this.container.getElementById('speicheranzeigefeld').addEventListener('change', function () {
self.speicherfeldanzeigeinspeicheruebernehmen();
self.neustart();
});
this.container.getElementById('speicheranzeigefeld').addEventListener('focus', function () {
self.zeileinspeicheranzeigefeldmarkieren(self.pz);
});
this.container.addEventListener('keydown', function (event) {
if (event.keyCode === 120) { // F9
self.registeranzeigefeldaktualisieren();
self.befehlausfuehren();
} else if (event.keyCode === 119) { // F8
self.speicherfeldanzeigeinspeicheruebernehmen();
self.neustart(); // 24.01.2024
}
});
}
loadProgram(program) {
this.mem = this.getMemForProgramm(program ?? {}, this.speicheranzahl);
// Restart the emulator
this.neustart();
}
// Load a program from a program object
getMemForProgramm(program, speicheranzahl) {
const newMem = new Array(speicheranzahl + 1);//expected size
newMem.fill(undefined);
// Load program into memory
for (const [address, instruction] of Object.entries(program)) {
if (parseInt(address) >= 1 && parseInt(address)) {
newMem[address] = instruction;
}
}
// fill all empty memory cells with 0
newMem.forEach((val, idx, self) => {
if (val === undefined) {
self[idx] = '0';
}
});
return newMem;
}
neustart() {
this.pz = 1; // Programmzähler
this.br = this.mem[this.pz]; // Befehlsregister
this.anzeigeaktualisieren();
this.container.getElementById("speicheranzeigefeld").focus();
}
registeranzeigefeldaktualisieren() {
this.container.getElementById("programmzaehleranzeigefeld").value = this.rechtsbuendig(this.pz) + ": " + this.stringmitleerzeichenauffuellen(this.mem[this.pz], this.spaltenanzahlinspeicheranzeigefeld - 3);
}
anzeigeaktualisieren() {
// Anzeige des gesamten Speichers aktualisieren:
/**@type HTMLTextAreaElement */
const speicheranzeigefeld = this.container.getElementById("speicheranzeigefeld");
speicheranzeigefeld.value = this.mem.slice(1).map((speicher, idx) => this.rechtsbuendig(idx + 1) + ": " + speicher).join('\n');
/**@type HTMLInputElement */
const registeranzeigefeld = this.container.getElementById("programmzaehleranzeigefeld");
speicheranzeigefeld.rows = this.mem.length - 1
speicheranzeigefeld.cols = this.spaltenanzahlinspeicheranzeigefeld;
registeranzeigefeld.size = this.spaltenanzahlinspeicheranzeigefeld;
this.registeranzeigefeldaktualisieren();
this.zeileinspeicheranzeigefeldmarkieren(this.pz);
}
befehlausfuehren() {
let fehlerindex = 0;
this.speicherfeldanzeigeinspeicheruebernehmen();
console.log("mem[pz] = " + this.mem[this.pz]);
this.br = this.mem[this.pz];
const z = parseInt(this.br);
console.log("Start befehlausfuehren mit z = parseint(br): " + z);
console.log("in befehlausfuehren vor Test: pz = " + this.pz + " br = " + this.br);
// Test: Ist Inhalt von br nur eine ganze Zahl, also ein Operand? Dann keine Ausführung möglich ...
if (!isNaN(z)) {
fehlerindex = 10;
this.meldung(fehlerindex);
} else {
// sonst gehen wir von einem Befehl aus:
const befehlscode = this.br.substr(0, 3).toLowerCase();
// Bei stp ist keine Adresse erforderlich und es wird auch nichts gemacht:
if (befehlscode === "stp") return 0;
// sonst muss eine ganze Zahl als Adresse folgen:
var adresse = parseInt(this.br.substr(4)); // ab Pos. 4 bis zum Ende ausschneiden
if (isNaN(adresse)) return 2;
this.pz = parseInt(this.pz);
switch (befehlscode) {
case "jmp":
this.pz = adresse;
break;
case "isz":
if (this.isZero(this.mem[adresse])) this.pz = this.pz + 2; else this.pz = this.pz + 1;
break;
case "inc":
let wert = this.mem[adresse];
wert = parseInt(wert) + 1;
wert = this.zahltrimmen(wert, 0, this.maxwert);
this.mem[adresse] = String(wert);
this.pz = this.pz + 1;
break;
case "dec":
let wert2 = this.mem[adresse];
wert2 = parseInt(wert2) - 1;
wert2 = this.zahltrimmen(wert2, 0, this.maxwert);
this.mem[adresse] = String(wert2);
this.pz = this.pz + 1;
break;
default:
fehlerindex = 3;
}
this.pz = this.zahltrimmen(this.pz, 1, this.speicheranzahl);
this.br = this.mem[this.pz];
this.anzeigeaktualisieren();
}
this.container.getElementById("speicheranzeigefeld").focus();
this.zeileinspeicheranzeigefeldmarkieren(this.pz);
return fehlerindex;
}
isZero(zeile) {
return 0 === parseInt(zeile);
}
einezeileinspeicheruebernehmen(zeile) {
zeile = zeile.toLowerCase();
const fehlerindex = this.khcparser(zeile);
if (fehlerindex === 0) {
const pz = parseInt(this.symwerte[0]);
let befehlszeile = this.symwerte[2];
if (this.symwerte.length > 3) {
befehlszeile = befehlszeile + " " + this.symwerte[3];
}
this.mem[pz] = befehlszeile;
} else {
if (fehlerindex > 0) { // -1 wäre Leerzeile
alert(this.fehlermeldungen[fehlerindex] + " in\n" + zeile);
}
}
}
speicherfeldanzeigeinspeicheruebernehmen() {
var zeilen = this.container.getElementById("speicheranzeigefeld").value.split("\n");
for (let i = 0; i < zeilen.length; i++) {
this.einezeileinspeicheruebernehmen(zeilen[i]);
}
this.anzeigeaktualisieren();
}
setzeMarkierungInTextFeld(domElemSelector, posStart, posEnde) {
const domElem = this.container.querySelector(domElemSelector);
if ('selectionStart' in domElem) {
domElem.selectionStart = posStart;
domElem.selectionEnd = posEnde;
}
}
zeileinspeicheranzeigefeldmarkieren(pz) {
let pzstring = this.rechtsbuendig(pz) + ": ";
let startpos = this.container.getElementById("speicheranzeigefeld").value.indexOf(pzstring);
let endepos = startpos + this.mem[pz].length + 4;
this.setzeMarkierungInTextFeld("#speicheranzeigefeld", startpos, endepos);
}
setMemorySize(memSize) {
this.speicheranzahl = memSize;
this.updateMemSize();
}
updateMemSize() {
const currentProgram = this.mem.reduce((p, v, idx) => {
if (!this.isZero(v)) {
p[idx] = v;
}
return p;
}, {})
this.mem = this.getMemForProgramm(currentProgram, this.speicheranzahl);
this.anzeigeaktualisieren();
}
}
// Register the custom elements
customElements.define('know-how-computer', KnowHowComputerElement);
}
if (!customElements.get('khc-memory')) {
// Define the KhcMemoryElement custom element
class KhcMemoryElement extends HTMLPreElement {
constructor() {
super();
// No need for shadow DOM as this is just a data container
}
// Lifecycle callbacks
// noinspection JSUnusedGlobalSymbols
connectedCallback() {
// Hide the element by default as it's just a data container
this.style.display = 'none';
}
}
customElements.define('khc-memory', KhcMemoryElement, {extends: 'pre'});
}
// Initialize when the DOM is loaded
document.addEventListener('DOMContentLoaded', function () {
// For MediaWiki gadget, this would be the initialization code
// No need to do anything here as the custom element will initialize itself when added to the DOM
});
})();
</script>
<know-how-computer
<!--{if $noHelp|validate:'boolean'|default:'false' == 'true'}-->
no-help
<!--{/if}-->
<!--{if $Speichergroesse|validate:'int'|default:'-1' != '-1'}-->
memory-size="<!--{$Speichergroesse|escape:'htmlall'}-->"
<!--{/if}-->
>
<!--{if $Speicherkonfiguration1|default:'' != ''}-->
<khc-memory><!--{$Speicherkonfiguration1|escape:'htmlall'}--></khc-memory>
<!--{/if}-->
<!--{if $Speicherkonfiguration2|default:'' != ''}-->
<khc-memory><!--{$Speicherkonfiguration2|escape:'htmlall'}--></khc-memory>
<!--{/if}-->
<!--{if $Speicherkonfiguration3|default:'' != ''}-->
<khc-memory><!--{$Speicherkonfiguration3|escape:'htmlall'}--></khc-memory>
<!--{/if}-->
<!--{if $Speicherkonfiguration4|default:'' != ''}-->
<khc-memory><!--{$Speicherkonfiguration4|escape:'htmlall'}--></khc-memory>
<!--{/if}-->
<!--{if $Speicherkonfiguration5|default:'' != ''}-->
<khc-memory><!--{$Speicherkonfiguration5|escape:'htmlall'}--></khc-memory>
<!--{/if}-->
</know-how-computer>
</includeonly>