refactor interpreter to new file

This commit is contained in:
Ole Morud
2022-08-25 19:31:40 +02:00
parent d85f6bf80b
commit 2cf821729b
2 changed files with 296 additions and 192 deletions

View File

@@ -1,7 +1,10 @@
import React, { useEffect, useState } from "react"
import React, { useEffect, useRef, useState } from "react"
import { Card, CardContent, Grid, IconButton, TextField, Typography } from "@mui/material"
import instructions, { Instruction } from "../instructions"
import { Instruction } from "../instructions"
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight"
import PlayArrowIcon from "@mui/icons-material/PlayArrow"
import StopIcon from "@mui/icons-material/Stop"
import {
BOARD_HEIGHT,
BOARD_WIDTH,
@@ -10,40 +13,28 @@ import {
SIDEBAR_WIDTH,
TILE_SIZE
} from "../const"
// considering renaming this to vector
interface Point {
x: number
y: number
}
const Direction = {
Up: { x: 0, y: -1 },
Right: { x: 1, y: 0 },
Left: { x: -1, y: 0 },
Down: { x: 0, y: 1 }
}
import { iterate, newProgram, ProgramState, resetProgram } from "../interpreter"
function Board() {
let key = 0
const [output, setOutput] = useState("")
const [done, setDone] = useState(true)
const [direction, setDirection] = useState<Point>(Direction.Right)
const [programCounter, setProgramCounter] = useState<Point>({ x: -1, y: 0 })
const [board, setBoard] = useState<Instruction[][]>(
new Array(BOARD_HEIGHT)
.fill(0)
.map(() => new Array(BOARD_WIDTH).fill(0).map(() => structuredClone(instructions[0])))
)
let stringmode = false
const [stack, setStack] = useState<number[]>([])
const [program, setProgram] = useState<ProgramState>(newProgram())
const [playing, setPlaying] = useState<boolean>(false)
useEffect(() => {
if (done) {
setProgramCounter({ x: -1, y: 0 })
setDirection(Direction.Right)
if (program.done) {
const x = structuredClone(program)
resetProgram(x)
x.done = false
setProgram(x)
}
}, [done])
}, [program])
const step = () => {
const x = structuredClone(program)
iterate(x)
setProgram(x)
}
const handleDrop = (
col: number,
@@ -51,16 +42,15 @@ function Board() {
event: React.DragEvent<HTMLDivElement> | undefined
): void => {
if (event === undefined) {
console.log("invalid drop target")
return
}
const data = JSON.parse(event.dataTransfer.getData("instruction")) as Instruction
const x = [...board]
x[row][col] = data
setBoard(x)
const x = structuredClone(program)
x.board[row][col] = data
setProgram(x)
const target = event.target as HTMLInputElement
target.value = data.symbol
target.value = data.emoji
}
const handleOnDragOver = (event: React.DragEvent<HTMLDivElement> | undefined) => {
@@ -68,175 +58,41 @@ function Board() {
return false
}
const safepop = (): number => {
if (stack.length === 0) {
return 0
}
const x = [...stack]
let result = x.pop()
setStack(x)
if (result === undefined) {
result = 0
}
return result
}
const iterate = () => {
setDone(false)
stepProgramCounter()
// This is asynchronous, the next line is run before program counter
// is updated. That is bad
console.log(board[programCounter.y][programCounter.x])
const cmd = board[programCounter.y][programCounter.x].ascii
let a: number
let b: number
if (stringmode && cmd !== '"') {
setStack([...stack, cmd.charCodeAt(0)])
} else if ("0123456789".includes(cmd)) {
setStack([...stack, +cmd])
} else {
switch (cmd) {
case "+":
stack.push(safepop() + safepop())
break
case "-":
stack.push(-safepop() + safepop())
break
case "*":
stack.push(safepop() * safepop())
break
case "/":
stack.push((1 / safepop()) * safepop())
break
case "%":
a = safepop()
b = safepop()
stack.push(b % a)
break
case "!":
stack.push(safepop() === 0 ? 1 : 0)
break
case "`":
stack.push(safepop() < safepop() ? 1 : 0)
break
case ">":
setDirection(Direction.Right)
break
case "<":
setDirection(Direction.Left)
break
case "^":
setDirection(Direction.Up)
break
case "v":
setDirection(Direction.Down)
break
case "?":
setDirection(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
[Direction.Up, Direction.Down, Direction.Left, Direction.Right].at(
Math.floor(Math.random() * 4)
)!
)
break
case "_":
setDirection(safepop() === 0 ? Direction.Right : Direction.Left)
break
case "|":
setDirection(safepop() === 0 ? Direction.Down : Direction.Up)
break
case '"':
stringmode = !stringmode
break
case ":":
setStack([...stack, stack[stack.length - 1]])
break
case "\\":
swapTopStackValues()
break
case "$":
safepop()
break
case ".":
setOutput(output + safepop())
break
case ",":
setOutput(output + String.fromCharCode(safepop()))
break
case "#":
stepProgramCounter()
break
case "g":
a = safepop()
b = safepop()
setStack([...stack, board[b][a].ascii.charCodeAt(0)])
break
case "p":
// TODO
break
case "&":
// TODO
break
case "~":
// TODO
break
case "@":
setDone(true)
break
default:
console.log("unknown command:", cmd)
break
}
}
return null
}
const stepProgramCounter = () => {
const tmp: Point = {
x: programCounter.x + direction.x,
y: programCounter.y + direction.y
}
if (tmp.x >= BOARD_WIDTH || tmp.x < 0 || tmp.y >= BOARD_HEIGHT || tmp.y < 0) {
setDone(true)
return
}
setProgramCounter(tmp)
}
const swapTopStackValues = () => {
const a = safepop()
const b = safepop()
setStack([...stack, a, b])
}
const handleChange = (
const handleTextFieldChange = (
col: number,
row: number,
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const x = [...board]
x[row][col].symbol = event.target.value
setBoard(x)
const x = structuredClone(program)
x.board[row][col].emoji = event.target.value
x.board[row][col].bytecode = event.target.value
setProgram(x)
}
return (
<div style={{ marginLeft: `${SIDEBAR_WIDTH + 1}rem` }}>
{/* BUTTON PANEL */}
<div style={{ display: "flex" }}>
<IconButton onClick={() => iterate()}>
<Grid container>
<Grid item xs={4}>
<IconButton onClick={() => setPlaying(!playing)}>
{playing ? <StopIcon /> : <PlayArrowIcon />}
</IconButton>
<IconButton onClick={() => step()}>
<KeyboardDoubleArrowRightIcon />
</IconButton>
<Typography>stack: {JSON.stringify(stack)}</Typography>
</Grid>
<Grid item xs={4}>
<Typography>output: {program.output}</Typography>
</Grid>
<Grid item xs={4}>
<Typography>stack: {JSON.stringify(program.stack)}</Typography>
</Grid>
</Grid>
</div>
{/* BOARD */}
{board.map((row, rowNo): JSX.Element => {
{program.board.map((row, rowNo): JSX.Element => {
return (
<Grid key={key++} container columns={BOARD_WIDTH} sx={{ width: `${5 * 12}rem` }}>
{row.map((tile, colNo): JSX.Element => {
@@ -253,7 +109,8 @@ function Board() {
width: `${TILE_SIZE}rem`,
margin: 1,
bgcolor:
rowNo === programCounter.y && colNo === programCounter.x
rowNo === program.instructionPointer.y &&
colNo === program.instructionPointer.x
? SELECTED_TILE_COLOR
: DEFAULT_TILE_COLOR
}}>
@@ -261,8 +118,8 @@ function Board() {
<TextField
sx={{ mt: "10%" }}
variant="standard"
inputProps={{ maxLength: 1 }}
onChange={(event) => handleChange(colNo, rowNo, event)}
inputProps={{ maxLength: 1, style: { textAlign: "center" } }}
onChange={(event) => handleTextFieldChange(colNo, rowNo, event)}
onDragOver={handleOnDragOver}
onDrop={(event) => {
handleDrop(colNo, rowNo, event)