checkpoint

This commit is contained in:
Elizabeth Hunt 2024-03-04 16:31:27 -07:00
parent f4ad269f8b
commit d74523d15d
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
10 changed files with 312 additions and 51 deletions

136
package-lock.json generated
View File

@ -8,6 +8,8 @@
"name": "the-abstraction-engine", "name": "the-abstraction-engine",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"codemirror": "^6.0.1",
"rainbowbrackets": "github:eriknewland/rainbowbrackets",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
@ -392,6 +394,82 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@codemirror/autocomplete": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.13.0.tgz",
"integrity": "sha512-SuDrho1klTINfbcMPnyro1ZxU9xJtwDMtb62R8TjL/tOl71IoOsvBo1a9x+hDvHhIzkTcJHy2VC+rmpGgYkRSw==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
},
"peerDependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.3.tgz",
"integrity": "sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
"integrity": "sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.5.6",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
},
"node_modules/@codemirror/view": {
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.25.0.tgz",
"integrity": "sha512-XnMGOm6qXB8znzCko0N7k97qZayVdvqpA0JebxA5fHtgBjC/XlCPhH9TK92TahsoCKMPQlaTCUep06Dwj/+GXQ==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.19.12", "version": "0.19.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
@ -956,6 +1034,27 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@lezer/common": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
},
"node_modules/@lezer/highlight": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
"integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -1641,6 +1740,20 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -1668,6 +1781,11 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true "dev": true
}, },
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -2814,6 +2932,14 @@
} }
] ]
}, },
"node_modules/rainbowbrackets": {
"version": "2.0.2",
"resolved": "git+ssh://git@github.com/eriknewland/rainbowbrackets.git#9352b73c715aac548690936ac393e0cf6e50cb98",
"license": "MIT",
"dependencies": {
"@codemirror/view": "^6.9.5"
}
},
"node_modules/react": { "node_modules/react": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
@ -3039,6 +3165,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/style-mod": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -3221,6 +3352,11 @@
} }
} }
}, },
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -10,6 +10,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"codemirror": "^6.0.1",
"rainbowbrackets": "github:eriknewland/rainbowbrackets",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },

View File

@ -4,8 +4,8 @@ import { Miscellaneous } from "./engine/config";
export const App = () => { export const App = () => {
return ( return (
<div className="main"> <div className="main">
<div id="modal" className="modal"> <div id={Miscellaneous.MODAL_ID} className="modal">
<div className="modal-content"> <div id={Miscellaneous.MODAL_CONTENT_ID} className="modal-content">
<span className="close">&times;</span> <span className="close">&times;</span>
<p>Some text in the Modal..</p> <p>Some text in the Modal..</p>
</div> </div>

View File

@ -94,3 +94,26 @@ a:visited {
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 0; margin: 0;
} }
.code {
width: 100%;
}
button {
padding: 0.5rem;
border: none;
border-radius: 0.5rem;
background-color: var(--yellow);
color: var(--bg);
cursor: pointer;
transition: background 0.2s ease-in-out;
}
button:hover {
background-color: var(--blue);
}
.syntax-error {
color: var(--red);
background-color: var(--yellow);
}

View File

@ -58,7 +58,7 @@ export class TheAbstractionEngine {
const wall = new Wall({ x: 5, y: 3 }); const wall = new Wall({ x: 5, y: 3 });
this.game.addEntity(wall); this.game.addEntity(wall);
const factory = new LambdaFactory({ x: 6, y: 6 }, "(λ (x) . x)", 10); const factory = new LambdaFactory({ x: 3, y: 3 }, "(λ (x) . x)", 10);
this.game.addEntity(factory); this.game.addEntity(factory);
const lockedDoor = new LockedDoor({ x: 8, y: 8 }); const lockedDoor = new LockedDoor({ x: 8, y: 8 });

View File

@ -60,5 +60,6 @@ export namespace Miscellaneous {
export const GRID_CELL_HEIGHT = Math.floor(HEIGHT / GRID_ROWS); export const GRID_CELL_HEIGHT = Math.floor(HEIGHT / GRID_ROWS);
export const MODAL_ID = "modal"; export const MODAL_ID = "modal";
export const MODAL_CONTENT_ID = "modal-content";
export const CANVAS_ID = "canvas"; export const CANVAS_ID = "canvas";
} }

View File

@ -19,12 +19,42 @@ import {
} from "../components"; } from "../components";
import { Coord2D, Direction } from "../interfaces"; import { Coord2D, Direction } from "../interfaces";
import { openModal, closeModal } from "../utils"; import { openModal, closeModal } from "../utils";
import { EditorState, StateField, StateEffect, Range } from "@codemirror/state";
import { Decoration, EditorView, keymap } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";
import rainbowBrackets from "rainbowbrackets";
import { basicSetup } from "codemirror";
import { parse } from "../../interpreter";
interface CodeEditorState {
view: EditorView;
editorElement: HTMLElement;
}
const highlightEffect = StateEffect.define<Range<Decoration>[]>();
const highlightExtension = StateField.define({
create() {
return Decoration.none;
},
update(value, transaction) {
value = value.map(transaction.changes);
for (let effect of transaction.effects) {
if (effect.is(highlightEffect))
value = value.update({ add: effect.value, sort: true });
}
return value;
},
provide: (f) => EditorView.decorations.from(f),
});
export class LambdaFactory extends Entity { export class LambdaFactory extends Entity {
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
Sprites.LAMBDA_FACTORY, Sprites.LAMBDA_FACTORY,
) as SpriteSpec; ) as SpriteSpec;
private codeEditorState: CodeEditorState | null;
private code: string; private code: string;
private spawns: number; private spawns: number;
@ -33,6 +63,7 @@ export class LambdaFactory extends Entity {
this.code = code; this.code = code;
this.spawns = spawns; this.spawns = spawns;
this.codeEditorState = null;
this.addComponent( this.addComponent(
new BoundingBox( new BoundingBox(
@ -82,35 +113,85 @@ export class LambdaFactory extends Entity {
this.removeComponent(ComponentNames.Interactable); this.removeComponent(ComponentNames.Interactable);
} }
private onHighlight(direction: Direction) { private spawnNewLambda(direction: Direction) {
if (direction === Direction.LEFT || direction === Direction.RIGHT) { const spawner = this.getComponent<LambdaSpawn>(ComponentNames.LambdaSpawn);
const interaction = () => {
const spawner = this.getComponent<LambdaSpawn>(
ComponentNames.LambdaSpawn,
);
spawner.spawn(direction); spawner.spawn(direction);
const text = this.getComponent<Text>(ComponentNames.Text); const text = this.getComponent<Text>(ComponentNames.Text);
text.text = spawner.spawnsLeft.toString(); text.text = spawner.spawnsLeft.toString();
this.addComponent(text); this.addComponent(text);
}; }
this.addComponent(new Interactable(interaction)); private onHighlight(direction: Direction) {
if (direction === Direction.LEFT || direction === Direction.RIGHT) {
this.addComponent(new Interactable(() => this.spawnNewLambda(direction)));
return; return;
} }
let modalOpen = false; let modalOpen = false;
let editorView: EditorView | null = null;
const syntaxErrorDecoration = Decoration.mark({
class: "syntax-error",
});
const close = () => { const close = () => {
modalOpen = false; if (editorView) {
closeModal(); const text = editorView.state.doc.toString();
// remove all text from the editor
editorView.dispatch({
changes: {
from: 0,
to: text.length,
insert: "",
},
});
// add the new text to the editor
editorView.dispatch({
changes: {
from: 0,
to: 0,
insert: text,
},
});
try {
parse(text);
} catch (e: any) {
if (!e.location) {
return;
}
const {
location: {
start: { offset: start },
end: { offset: end },
},
} = e;
editorView.dispatch({
effects: highlightEffect.of([
syntaxErrorDecoration.range(
start === end ? start - 1 : start,
end,
),
]),
});
document.getElementById("syntax-error")!.innerText = e.message;
return;
}
const spawner = this.getComponent<LambdaSpawn>( const spawner = this.getComponent<LambdaSpawn>(
ComponentNames.LambdaSpawn, ComponentNames.LambdaSpawn,
); );
spawner.code = ( spawner.code = text;
document.getElementById("code") as HTMLTextAreaElement
).value;
this.addComponent(spawner); this.addComponent(spawner);
}
modalOpen = false;
closeModal();
document.getElementById(Miscellaneous.CANVAS_ID)!.focus(); document.getElementById(Miscellaneous.CANVAS_ID)!.focus();
return; return;
@ -121,22 +202,36 @@ export class LambdaFactory extends Entity {
close(); close();
return; return;
} }
modalOpen = true; modalOpen = true;
openModal(this.codeEditor(this.code));
openModal(
"<div class='code'><div id='code'></div><br><p id='syntax-error' class='error'></p><button id='close-modal'>Save</button></div>",
);
const spawner = this.getComponent<LambdaSpawn>(
ComponentNames.LambdaSpawn,
);
const startState = EditorState.create({
doc: spawner.code,
extensions: [
basicSetup,
keymap.of(defaultKeymap),
rainbowBrackets(),
highlightExtension,
],
});
const codeBox = document.getElementById("code")!;
editorView = new EditorView({
state: startState,
parent: codeBox,
});
editorView.focus();
document.getElementById("close-modal")!.addEventListener("click", close); document.getElementById("close-modal")!.addEventListener("click", close);
}; };
this.addComponent(new Interactable(interaction)); this.addComponent(new Interactable(interaction));
} }
private codeEditor(code: string) {
return `
<div>
<textarea id="code" rows="10" cols="50">${code}</textarea>
<button id="close-modal">Close</button>
</div>
`;
}
} }

View File

@ -2,34 +2,37 @@ import { Miscellaneous } from "../config";
let modalOpen = false; let modalOpen = false;
export const openModal = (content: string, id = Miscellaneous.MODAL_ID) => { export const openModal = (
content: string,
id = Miscellaneous.MODAL_ID,
contentId = Miscellaneous.MODAL_CONTENT_ID,
) => {
const modal = document.getElementById(id); const modal = document.getElementById(id);
if (modal && !modalOpen) { const modalContent = document.getElementById(contentId);
if (modal && !modalOpen && modalContent) {
modal.style.display = "flex"; modal.style.display = "flex";
modal.style.animation = "fadeIn 0.25s"; modal.style.animation = "fadeIn 0.25s";
modal.innerHTML = `<div class="modal-content">${content}</div>`; modalContent.innerHTML = content;
const modalContent = document.querySelector<HTMLElement>(".modal-content");
if (modalContent) {
modalContent.style.animation = "scaleUp 0.25s"; modalContent.style.animation = "scaleUp 0.25s";
}
modalOpen = true; modalOpen = true;
} }
}; };
export const closeModal = (id = Miscellaneous.MODAL_ID) => { export const closeModal = (
id = Miscellaneous.MODAL_ID,
contentId = Miscellaneous.MODAL_CONTENT_ID,
) => {
const modal = document.getElementById(id); const modal = document.getElementById(id);
if (modal && modalOpen) { const modalContent = document.getElementById(contentId);
modal.style.animation = "fadeOut 0.25s";
const modalContent = document.querySelector<HTMLElement>(".modal-content"); if (modal && modalOpen && modalContent) {
if (modalContent) { modal.style.animation = "fadeOut 0.25s";
modalContent.style.animation = "scaleDown 0.25s"; modalContent.style.animation = "scaleDown 0.25s";
}
setTimeout(() => { setTimeout(() => {
modal.innerHTML = ""; modalContent.innerHTML = "";
modal.style.display = "none"; modal.style.display = "none";
modalOpen = false; modalOpen = false;

View File

@ -30,6 +30,6 @@ export const isVariable = (term: LambdaTerm): term is Variable => {
return typeof term === "string"; return typeof term === "string";
}; };
export const parse = (term: string) => { export const parse = (term: string, library = false) => {
return peggyParser.parse(term, { library: true }); return peggyParser.parse(term, { peg$library: library });
}; };

View File

@ -1,4 +1,5 @@
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { App } from "./App.tsx"; import { App } from "./App.tsx";
import "./css/style.css"; import "./css/style.css";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />); ReactDOM.createRoot(document.getElementById("root")!).render(<App />);