diff --git a/befunge/src/main/kotlin/befide/befunge/b93/B93Funge.kt b/befunge/src/main/kotlin/befide/befunge/b93/B93Funge.kt deleted file mode 100644 index f941fe9..0000000 --- a/befunge/src/main/kotlin/befide/befunge/b93/B93Funge.kt +++ /dev/null @@ -1,53 +0,0 @@ -package befide.befunge.b93 - -import befide.befunge.core.* -import befide.befunge.state.* - -fun List.padEnd(size: Int, factory: (Int) -> (T)): List = this + (this.size until size).map { factory(it) } -fun List.padEnd(size: Int, value: T): List = this.padEnd(size) { value } - -class B93Funge : Funge { - override val width = 80 - override val height = 25 - - val bounds = Vec(width, height) - - private var cars = Array(height) { Array(width) { Value(' ') } } - - override var values - get() = cars.map { it.toList() } - set(data) { - for (y in 0 until cars.size) - for (x in 0 until cars[y].size) - cars[y][x] = data[y][x] - } - - override fun get(vec: Vec): Value { - return cars[vec.y][vec.x] - } - - override fun set(vec: Vec, value: Value) { - cars[vec.y][vec.x] = value - } - - override fun nextVec(vec: Vec, delta: Vec): Vec { - return (vec + delta) mod bounds - } - - override fun setString(data: String) { - cars = data.split("\n").map { - it.map(::Value).padEnd(width, Value(' ')).toTypedArray() - }.padEnd(height) { - Array(width) { Value(' ') } - }.toTypedArray() - } - - override fun toString(): String { - return cars.joinToString("\n") { row -> - row.joinToString("") { value -> - (value.asChar ?: '?').toString() - } - } - } -} - diff --git a/befunge/src/main/kotlin/befide/befunge/b93/B93Interpreter.kt b/befunge/src/main/kotlin/befide/befunge/b93/B93Interpreter.kt deleted file mode 100644 index 250e199..0000000 --- a/befunge/src/main/kotlin/befide/befunge/b93/B93Interpreter.kt +++ /dev/null @@ -1,359 +0,0 @@ -package befide.befunge.b93 - -import befide.befunge.core.Interpreter -import befide.befunge.events.* -import befide.befunge.state.IpMode -import befide.befunge.state.Value -import befide.befunge.state.Vec -import java.util.* -import java.util.concurrent.BlockingQueue -import java.util.concurrent.LinkedBlockingQueue - -class B93Interpreter : Interpreter { - private companion object { - val RIGHT = Vec(1, 0) - val LEFT = Vec(-1, 0) - val UP = Vec(0, -1) - val DOWN = Vec(0,1) - } - - override val funge = B93Funge() - override val stack = Stack() - override val ip = B93Pointer(Vec(0, 0), Vec(1, 0), IpMode.Normal) - - override val fungeChanged: Event = Event() - override val stackChanged: Event = Event() - override val ipChanged: Event = Event() - override val outputChanged: Event = Event() - - override val stdInput = LinkedList() - override val stdOutput = LinkedList() - private var outBuf: StringBuffer = StringBuffer() - private var bufLimit = 0 - - private val fungeMods = HashMap() - - private fun _pop(): Value? { - if (!stack.empty()) - return stack.pop() - return null - } - - private fun pop(): Value { - val v = _pop() - val ret = v?.let { - stackChanged(StackEvent(StackAction.Pop, listOf(it))) - it - } ?: Value(0) - return ret - } - - private fun pop(num: Int): List { - val vs = List(num) {_pop()} - stackChanged(StackEvent(StackAction.Pop, vs.filterNotNull())) - return vs.map { it ?: Value(0) } - } - - private fun _push(v: Value) { - stack.push(v) - } - - private fun push(v: Value) { - _push(v) - stackChanged.invoke(StackEvent(StackAction.Push, listOf(v))) - } - - private fun push(v: Value, vararg vs: Value) { - _push(v) - vs.forEach { _push(it) } - stackChanged.invoke(StackEvent(StackAction.Push, listOf(v) + vs.toList())) - } - - private fun peek(): Value { - if (!stack.empty()) { - return stack.peek() - } - return Value(0) - } - - private fun binop(bop: Char): Boolean { - val (vb, va) = pop(2) - val a = va.value - val b = vb.value - val res = when(bop) { - '+' -> a + b - '-' -> a - b - '*' -> a * b - '/' -> a / b - '%' -> a % b - '`' -> if (a > b) 1L else 0L - else -> null - } - return res?.let { - val vres = Value(res) - push(vres) - } != null - } - - private fun unop(uop: Char): Boolean { - val vv = pop() - val v = vv.value - val res = when(uop) { - '!' -> if (v == 0L) 1L else 0L - else -> null - } - return res?.let { - val vres = Value(it) - push(vres) - } != null - } - - private fun changeDir(dir: Char): Boolean { - val newDelta = when(dir) { - '>' -> RIGHT - '<' -> LEFT - '^' -> UP - 'v' -> DOWN - else -> null - } - return newDelta?.let { - ip.delta = it - } != null - } - - private fun randomDir(): Boolean { - val dirs = listOf('<', '>', '^', 'v') - val ind = Random().nextInt(4) - changeDir(dirs[ind]) - return true - } - - private fun conditional(cop: Char): Boolean { - val vcond = pop() - val cond = vcond.value == 0L - val newDelta = when(cop) { - '|' -> if (cond) DOWN else UP - '_' -> if (cond) RIGHT else LEFT - else -> null - } - return newDelta?.let { - ip.delta = it - } != null - } - - private fun toggleStrmode(): Boolean { - val newMode = when (ip.mode) { - IpMode.String -> IpMode.Normal - IpMode.Normal -> IpMode.String - IpMode.Inactive -> IpMode.Inactive - } - ip.mode = newMode - return true - } - - private fun stackop(sop: Char): Boolean { - var ret = true - when (sop) { - ':' -> { - val vc = peek().copy() - push(vc) - } - '\\' -> { - val (v2, v1) = pop(2) - push(v2, v1) - } - '$' -> { - pop() - } - else -> { - ret = false - } - } - return ret - } - - private fun output(type: Char): Boolean { - val vv = pop() - val v = vv.value - val out = when (type) { - '.' -> v.toString() + ' ' - ',' -> v.toChar().toString() - else -> null - } - return out?.let{ - outBuf.append(it) - if (outBuf.length > bufLimit) { - stdOutput.add(outBuf.toString()) - outBuf.delete(0, outBuf.length) - outputChanged.invoke(OutputEvent()) - } - } != null - } - - private fun stepIP(): Boolean { - ip.pos = funge.nextVec(ip.pos, ip.delta) - return true - // No ipChanged here, shown in execInstr - } - - private fun input(type: Char): Boolean { - fun Queue.takeWhile(predicate: (T) -> Boolean): Sequence { - return sequence { - if (peek() == null) { - yield(null) - } - else { - while (peek()?.let(predicate) == true) { - yield(remove()) - } - } - yield(null) - } - } - - val zer = '0'.toLong() - val inp = when (type) { - '&' -> { - val nums = stdInput.takeWhile { it in '0' until '9' } - val first = nums.first() - first?.let { - nums.filterNotNull() - .map {c -> c.toLong() - zer} - .fold(it.toLong()-zer) { curr: Long, next: Long -> - curr * 10 + next - } - } - } - '~' -> stdInput.poll()?.toLong() - else -> null - } - return inp?.let { - push(Value(it)) - } != null - } - - private fun fget(): Boolean { - val (vy, vx) = pop(2) - val x = vx.value.toInt() - val y = vy.value.toInt() - if (0 <= x && x < funge.width && 0 <= y && y <= funge.height) { - val vv = funge[Vec(x, y)] - push(vv) - return true - } - return false - } - - private fun fput(): Boolean { - val (vy, vx, vv) = pop(3) - val x = vx.value.toInt() - val y = vy.value.toInt() - if (0 <= x && x < funge.width && 0 <= y && y <= funge.height) { - val loc = Vec(x, y) - val old = funge[loc] - funge[loc] = vv - if (!fungeMods.contains(loc)) { - fungeMods[loc] = old - } - fungeChanged(FungeEvent(listOf(FungeChange(loc,old,vv)))) - return true - } - return false - } - - private fun terminate(): Boolean { - stdOutput.add(outBuf.toString()) - outBuf.delete(0, outBuf.length) - outputChanged.invoke(OutputEvent()) - ip.mode = IpMode.Inactive - return false - } - - private fun pushDig(dig: Char): Boolean { - val num = dig.toLong() - '0'.toLong() - push(Value(num)) - return true - } - - private fun noOp(): Boolean = false - - private fun execInstr(instr: Value): Boolean { - val car = instr.asChar - return when (car) { - null -> noOp() - '+', '-', '*', '/', '%', '`' -> binop(car) - '!' -> unop(car) - '>', '<', '^', 'v' -> changeDir(car) - '?' -> randomDir() - '_', '|' -> conditional(car) - '"' -> toggleStrmode() - ':', '\\', '$' -> stackop(car) - '.', ',' -> output(car) - '#' -> stepIP() - 'g' -> fget() - 'p' -> fput() - '&', '~' -> input(car) - '@' -> terminate() - in '0'..'9' -> pushDig(car) - ' ' -> true - else -> true - } - } - - private fun strMode(instr: Value): Boolean { - if (instr.asChar == '"') { - toggleStrmode() - return true - } - push(instr) - return true - } - - override fun step(): Boolean { - val instr = funge[ip.pos] - val currIP = ip.copy() - val processed = when (ip.mode) { - IpMode.Inactive -> noOp() - IpMode.Normal -> execInstr(instr) - IpMode.String -> strMode(instr) - } - if (processed) { - stepIP() - if (ip.mode != IpMode.String) { - var numsteps = 0 - val maxsteps = if (funge.height > funge.width) funge.height else funge.width - while (funge[ip.pos].asChar == ' ' && numsteps++ < maxsteps) { - stepIP() - } - if (numsteps == maxsteps) { - ip.mode = IpMode.Inactive - } - } - val newIP = ip.copy() - ipChanged(IpEvent(currIP, newIP)) - } - return ip.mode != IpMode.Inactive - } - - override fun reset() { - val oldIP = ip.copy() - ip.pos = Vec(0,0) - ip.delta = RIGHT - ip.mode = IpMode.Normal - val newIP = ip.copy() - ipChanged.invoke(IpEvent(oldIP, newIP)) - - stack.clear() - stackChanged(StackEvent(StackAction.Clear, listOf())) - - stdInput.clear() - stdOutput.clear() - - val changes = fungeMods.map { FungeChange(it.key, funge[it.key], it.value) } - for ((k,v) in fungeMods) { - funge[k] = v - } - fungeMods.clear() - fungeChanged(FungeEvent(changes)) - } -} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/b93/B93Pointer.kt b/befunge/src/main/kotlin/befide/befunge/b93/B93Pointer.kt deleted file mode 100644 index ffadaf5..0000000 --- a/befunge/src/main/kotlin/befide/befunge/b93/B93Pointer.kt +++ /dev/null @@ -1,7 +0,0 @@ -package befide.befunge.b93 - -import befide.befunge.core.Pointer -import befide.befunge.state.IpMode -import befide.befunge.state.Vec - -data class B93Pointer(override var pos: Vec, override var delta: Vec, override var mode: IpMode) : Pointer \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/b93/Befunge93.kt b/befunge/src/main/kotlin/befide/befunge/b93/Befunge93.kt new file mode 100644 index 0000000..9619afd --- /dev/null +++ b/befunge/src/main/kotlin/befide/befunge/b93/Befunge93.kt @@ -0,0 +1,150 @@ +package befide.befunge.b93 + +import befide.befunge.core.* +import befide.befunge.core.util.chooseOne +import befide.befunge.core.util.readAll +import befide.befunge.events.Event +import java.io.PipedReader +import java.io.PipedWriter +import java.util.* + + +class Interpreter93(stdinSrc: PipedWriter, stdoutDest: PipedReader) + : Interpreter { + + override val ip = Pointer93() + override val funge = Funge93() + override val stack = Stack() + + override val onIpChange = Event>() + override val onStackChange = Event>() + + override val stdin: PipedReader = PipedReader(stdinSrc) + override val stdout: PipedWriter = PipedWriter(stdoutDest) + + private fun notifyIp(op: () -> Unit) { + val from = ip.copy() + op() + val to = ip.copy() + onIpChange(IpChange(from, to)) + } + + private fun move(dir: Vec2? = null) = notifyIp { + ip.pos = funge.next(ip.pos, dir ?: ip.delta) + } + + private fun pop(): LongData { + val d = if (stack.empty()) LongData.ZERO else stack.pop() + onStackChange(StackChange(StackOp.Pop, d)) + return d + } + + private fun push(vararg data: LongData) { + for (d in data) { + stack.push(d) + onStackChange(StackChange(StackOp.Push, d)) + } + } + + private fun argPop(n: Int, op: (Array) -> Unit) { + val args = (0 until n).map { pop() }.toTypedArray() + op(args) + } + + private var mode + get() = ip.mode + set(mode) = notifyIp { ip.mode = mode } + + private var delta + get() = ip.delta + set(delta) = notifyIp { ip.delta = delta } + + override fun step() { + val instr = funge[ip.pos] + + when (mode) { + PointerMode.Terminated -> return + PointerMode.String -> when (instr.char) { + null -> Unit + + '"' -> mode = PointerMode.Normal + + else -> push(instr) + } + PointerMode.Normal -> when (instr.char) { + null -> Unit + in "0123456789abcdef" -> push(LongData(instr.char.toString().toLong(16))) + + '+' -> argPop(2) { (b, a) -> push(a + b) } + '-' -> argPop(2) { (b, a) -> push(a - b) } + '*' -> argPop(2) { (b, a) -> push(a * b) } + '/' -> argPop(2) { (b, a) -> push(a / b) } + '%' -> argPop(2) { (b, a) -> push(a mod b) } + '!' -> argPop(1) { (n) -> push(LongData(if (n.data == 0L) 1L else 0L)) } + '`' -> argPop(2) { (b, a) -> push(LongData(if (a.data > b.data) 1L else 0L)) } + + '>' -> delta = Vec2.RIGHT + '<' -> delta = Vec2.LEFT + 'v' -> delta = Vec2.DOWN + '^' -> delta = Vec2.UP + '?' -> delta = Vec2.DIRS.chooseOne() + '#' -> move() + + '_' -> argPop(1) { (n) -> delta = if (n.data == 0L) Vec2.RIGHT else Vec2.LEFT } + '|' -> argPop(1) { (n) -> delta = if (n.data == 0L) Vec2.DOWN else Vec2.UP } + + '"' -> mode = PointerMode.String + + ':' -> argPop(1) { (n) -> push(n, n) } + '\\' -> argPop(2) { (b, a) -> push(b, a) } + '$' -> pop() + + '.' -> argPop(1) { (n) -> stdout.write("${n.data} ") } + ',' -> argPop(1) { (ch) -> stdout.write(ch.data.toChar().toString()) } + + 'p' -> argPop(3) { (y, x, n) -> funge[Vec2(x.data.toInt(), y.data.toInt())] = n } + 'g' -> argPop(2) { (y, x) -> push(funge[Vec2(x.data.toInt(), y.data.toInt())]) } + + '~' -> push(LongData(stdin.read().toLong())) + '&' -> { + val chars = generateSequence { + stdin.read().takeIf { it.toChar().isDigit() } + } + val long = chars.joinToString("").toLong() + push(LongData(long)) + } + + '@' -> mode = PointerMode.Terminated + } + } + + move() + } + + override fun reset() { + stdin.readAll() + stdout.flush() + } +} + +fun main(args: Array) { + val stdinSrc = PipedWriter() + val stdoutDest = PipedReader() + + val int = Interpreter93(stdinSrc, stdoutDest) + +// int.onIpChange += { +// println(it.to.pos) +// } + + int.funge.src = """2>:3g" "-!v\ g30 < + |!`"O":+1_:.:03p>03g+:"O"`| + @ ^ p3\" ":< +2 234567890123456789012345678901234567890123456789012345678901234567890123456789""" + + while (int.ip.mode != PointerMode.Terminated) { + int.step() + print(stdoutDest.readAll()) + } + +} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/b93/Funge93.kt b/befunge/src/main/kotlin/befide/befunge/b93/Funge93.kt new file mode 100644 index 0000000..51b0fab --- /dev/null +++ b/befunge/src/main/kotlin/befide/befunge/b93/Funge93.kt @@ -0,0 +1,56 @@ +package befide.befunge.b93 + +import befide.befunge.core.FungeChange +import befide.befunge.core.MutableFunge +import befide.befunge.events.Event + +class Funge93 + : MutableFunge { + override val size = Vec2(80, 25) + + override val onChange = Event>() + + override fun next(pos: Vec2, delta: Vec2): Vec2 = (pos + delta) mod size + + val contents = Array(size.y) { Array(size.x) { LongData.SPACE } } + + override fun get(pos: Vec2): LongData = contents[pos.y][pos.x] + override fun set(pos: Vec2, data: LongData) { + val from = this[pos] + contents[pos.y][pos.x] = data + onChange(FungeChange(this, pos, from, data)) + } + + override var data: List> + get() { + return contents.map(Array::toList) + } + set(data) { + for (y in 0 until size.y) for (x in 0 until size.x) { + val pos = Vec2(x, y) + val new = data.getOrNull(y)?.getOrNull(x) ?: this[pos] + this[pos] = new + } + } + + override var src: String + get() { + return contents.map { row -> + row.dropLastWhile { ch -> + ch.char?.isISOControl() ?: false + }.map { ch -> + ch.char ?: '\u2022' // bullet + }.joinToString("") + }.dropLastWhile { line -> + line.isEmpty() + }.joinToString("\n") + } + set(data) { + val lines = data.lines() + for (y in 0 until size.y) for (x in 0 until size.x) { + val pos = Vec2(x, y) + val new = lines.getOrNull(y)?.getOrNull(x)?.let(::LongData) ?: this[pos] + this[pos] = new + } + } +} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/b93/LongData.kt b/befunge/src/main/kotlin/befide/befunge/b93/LongData.kt new file mode 100644 index 0000000..b7c961e --- /dev/null +++ b/befunge/src/main/kotlin/befide/befunge/b93/LongData.kt @@ -0,0 +1,27 @@ +package befide.befunge.b93 + +import befide.befunge.core.Data +import befide.befunge.core.util.mod + +data class LongData(val data: Long) + : Data { + + companion object { + val SPACE = LongData(' ') + val ZERO = LongData(0) + } + + override val char = data.toChar().takeUnless { it.isISOControl() } + + constructor(char: Char) : this(char.toLong()) + + operator fun plus(o: LongData) = LongData(data + o.data) + operator fun minus(o: LongData) = LongData(data - o.data) + operator fun times(o: LongData) = LongData(data * o.data) + operator fun div(o: LongData) = LongData(data / o.data) + operator fun unaryMinus() = LongData(-data) + + infix fun mod(o: LongData) = LongData(data mod o.data) + + override fun toString() = "${char ?: '\u2022'} ($data)" +} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/b93/Pointer93.kt b/befunge/src/main/kotlin/befide/befunge/b93/Pointer93.kt new file mode 100644 index 0000000..f4b39e2 --- /dev/null +++ b/befunge/src/main/kotlin/befide/befunge/b93/Pointer93.kt @@ -0,0 +1,12 @@ +package befide.befunge.b93 + +import befide.befunge.core.MutablePointer + +enum class PointerMode { + Normal, String, Terminated +} + +data class Pointer93(override var pos: Vec2 = Vec2.ZERO, + override var delta: Vec2 = Vec2.RIGHT, + override var mode: PointerMode = PointerMode.Normal) + : MutablePointer \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/b93/Vec2.kt b/befunge/src/main/kotlin/befide/befunge/b93/Vec2.kt new file mode 100644 index 0000000..d9d1d7f --- /dev/null +++ b/befunge/src/main/kotlin/befide/befunge/b93/Vec2.kt @@ -0,0 +1,30 @@ +package befide.befunge.b93 + +import befide.befunge.core.util.mod + +data class Vec2(val x: Int, val y: Int) { + companion object { + val ZERO = Vec2(0, 0) + val ONE = Vec2(0, 0) + + val RIGHT = Vec2(1, 0) + val DOWN = Vec2(0, 1) + val LEFT = Vec2(-1, 0) + val UP = Vec2(0, -1) + + val DIRS = listOf(RIGHT, LEFT, DOWN, UP) + } + + override fun toString(): String = "($x, $y)" + + val ccw by lazy { Vec2(-y, x) } + val cw by lazy { Vec2(y, -x) } + + operator fun plus(o: Vec2) = Vec2(x + o.x, y + o.y) + operator fun minus(o: Vec2) = Vec2(x - o.x, y - o.y) + operator fun times(o: Int) = Vec2(x * o, y * o) + operator fun div(o: Int) = Vec2(x / o, y / o) + operator fun unaryMinus() = Vec2(-x, -y) + + infix fun mod(o: Vec2) = Vec2(x mod o.x, y mod o.y) +} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/core/Funge.kt b/befunge/src/main/kotlin/befide/befunge/core/Funge.kt deleted file mode 100644 index 1cb0189..0000000 --- a/befunge/src/main/kotlin/befide/befunge/core/Funge.kt +++ /dev/null @@ -1,50 +0,0 @@ -package befide.befunge.core - -import befide.befunge.events.Event -import befide.befunge.events.FungeEvent -import befide.befunge.state.Value -import befide.befunge.state.Vec - -interface Funge { - val width: Int - val height: Int - - operator fun get(vec: Vec): Value - operator fun set(vec: Vec, value: Value) - - var values: List> - - /** - * Get the next position to be executed starting at [vec] and stepping by [delta] - */ - fun nextVec(vec: Vec, delta: Vec): Vec - - /** - * Set the contents of the funge based on a string, handling newlines appropriately - */ - fun setString(data: String) - - /** - * @return An iterable of executable positions, starting at [vec] and stepping by [delta] - */ - fun nextVecs(vec: Vec, delta: Vec): Iterable { - return generateSequence(vec) { - nextVec(it, delta) - }.asIterable() - } - - /** - * Starting at [vec] and stepping by [delta], fill those positions with values in [data] - */ - fun setMany(vec: Vec, delta: Vec, data: List) { - for ((e, v) in data.zip(nextVecs(vec, delta))) - set(v, e) - } - - /** - * @return An iterable of those values at the executable positions starting at [vec] and stepping by [delta]. - */ - fun getMany(vec: Vec, delta: Vec, count: Int): Iterable { - return nextVecs(vec, delta).take(count).map(this::get).asIterable() - } -} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt b/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt index 18bf2fc..a2483b4 100644 --- a/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt +++ b/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt @@ -1,38 +1,75 @@ package befide.befunge.core -import befide.befunge.events.* -import befide.befunge.state.Value +import befide.befunge.events.Event +import java.io.PipedReader +import java.io.PipedWriter import java.util.* -import java.util.concurrent.BlockingQueue -/** - * Interface for a Befunge interpreter - * - * @property fungeChanged Invoked whenever the [funge] is modified - * @property stackChanged Invoked whenever the [stack] is modified - * @property ipChanged Invoked whenever the [ip] is modified - */ -interface Interpreter { - val funge: Funge - val stack: Stack - val ip: Pointer - - val fungeChanged: Event - val stackChanged: Event - val ipChanged: Event - val outputChanged: Event - - val stdInput: Queue - val stdOutput: Queue - - /** - * @return If [ip] is inactive after this step, indicating execution has halted, then return `false` - */ - fun step(): Boolean - - /** - * Reset the state of the interpreter to the last state before [step] was executed - that is, reset [funge] and [ip] to the state which was manually set, before any interpretation via [step] - */ - fun reset() +interface Pointer> { + val pos: V + val delta: V + val mode: M } +interface MutablePointer> + : Pointer { + + override var pos: V + override var delta: V + override var mode: M +} + +interface Data { + val char: Char? +} + +data class FungeChange +(val funge: Funge, + val pos: V, + val from: D, + val to: D) + +interface Funge { + val size: V + + val data: List> + val src: String + + fun next(pos: V, delta: V): V + + operator fun get(pos: V): D + + val onChange: Event> +} + +interface MutableFunge + : Funge { + + override var data: List> + override var src: String + + operator fun set(pos: V, data: D) +} + +data class IpChange> +(val from: Pointer, val to: Pointer) + +enum class StackOp { Push, Pop } + +data class StackChange +(val op: StackOp, val data: D) + +interface Interpreter> { + val ip: Pointer + val funge: Funge + val stack: List + + val onIpChange: Event> + val onStackChange: Event> + + val stdin: PipedReader + val stdout: PipedWriter + + fun step() + fun reset() +} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/core/Pointer.kt b/befunge/src/main/kotlin/befide/befunge/core/Pointer.kt deleted file mode 100644 index efb17a8..0000000 --- a/befunge/src/main/kotlin/befide/befunge/core/Pointer.kt +++ /dev/null @@ -1,17 +0,0 @@ -package befide.befunge.core - -import befide.befunge.events.Event -import befide.befunge.events.IpEvent -import befide.befunge.state.IpMode -import befide.befunge.state.Vec - -/** - * Represents a Befunge Instruction Pointer - * - * Note that [Vec] need not be a concrete type, but is simply to be easier to implement 2d Befunge - */ -interface Pointer { - val pos: Vec - val delta: Vec - val mode: IpMode -} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/core/util/Misc.kt b/befunge/src/main/kotlin/befide/befunge/core/util/Misc.kt new file mode 100644 index 0000000..61b497b --- /dev/null +++ b/befunge/src/main/kotlin/befide/befunge/core/util/Misc.kt @@ -0,0 +1,23 @@ +package befide.befunge.core.util + +import java.io.PipedReader +import kotlin.random.Random + + +infix fun Int.mod(o: Int): Int { + var rem = this % o + if (rem < 0) rem += o + return rem +} + +infix fun Long.mod(o: Long): Long { + var rem = this % o + if (rem < 0) rem += o + return rem +} + +fun List.chooseOne(): T = this[Random.nextInt(size)] + +fun PipedReader.readAll(): String = generateSequence { + if (ready()) read().toChar() else null +}.joinToString("") diff --git a/befunge/src/main/kotlin/befide/befunge/events/Event.kt b/befunge/src/main/kotlin/befide/befunge/events/Event.kt index 4e084f9..9ac2afa 100644 --- a/befunge/src/main/kotlin/befide/befunge/events/Event.kt +++ b/befunge/src/main/kotlin/befide/befunge/events/Event.kt @@ -12,7 +12,7 @@ internal operator fun Handler.invoke(t: T) = accept(t) * Sample usage: * * ``` - * data class ServerEvent(val joined: Boolean, val user: String) + * contents class ServerEvent(val joined: Boolean, val user: String) * fun main(args: Array) { * val event = Event() diff --git a/befunge/src/main/kotlin/befide/befunge/events/FungeEvent.kt b/befunge/src/main/kotlin/befide/befunge/events/FungeEvent.kt deleted file mode 100644 index 86b45df..0000000 --- a/befunge/src/main/kotlin/befide/befunge/events/FungeEvent.kt +++ /dev/null @@ -1,8 +0,0 @@ -package befide.befunge.events - -import befide.befunge.state.Value -import befide.befunge.state.Vec - -data class FungeChange(val vec: Vec, val from: Value, val to: Value) - -data class FungeEvent(val changes: List) \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/events/IpEvent.kt b/befunge/src/main/kotlin/befide/befunge/events/IpEvent.kt deleted file mode 100644 index a88d660..0000000 --- a/befunge/src/main/kotlin/befide/befunge/events/IpEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package befide.befunge.events - -import befide.befunge.core.Pointer - -data class IpEvent(val from: Pointer, val to: Pointer) \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/events/OutputEvent.kt b/befunge/src/main/kotlin/befide/befunge/events/OutputEvent.kt deleted file mode 100644 index c73e5fa..0000000 --- a/befunge/src/main/kotlin/befide/befunge/events/OutputEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package befide.befunge.events - -class OutputEvent diff --git a/befunge/src/main/kotlin/befide/befunge/events/StackAction.kt b/befunge/src/main/kotlin/befide/befunge/events/StackAction.kt deleted file mode 100644 index 2258a0b..0000000 --- a/befunge/src/main/kotlin/befide/befunge/events/StackAction.kt +++ /dev/null @@ -1,7 +0,0 @@ -package befide.befunge.events - -enum class StackAction { - Push, - Pop, - Clear -} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/events/StackEvent.kt b/befunge/src/main/kotlin/befide/befunge/events/StackEvent.kt deleted file mode 100644 index e54d3c3..0000000 --- a/befunge/src/main/kotlin/befide/befunge/events/StackEvent.kt +++ /dev/null @@ -1,5 +0,0 @@ -package befide.befunge.events - -import befide.befunge.state.Value - -data class StackEvent(val op: StackAction, val values: List) \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/state/IpMode.kt b/befunge/src/main/kotlin/befide/befunge/state/IpMode.kt deleted file mode 100644 index 16af492..0000000 --- a/befunge/src/main/kotlin/befide/befunge/state/IpMode.kt +++ /dev/null @@ -1,7 +0,0 @@ -package befide.befunge.state - -enum class IpMode { - Normal, - String, - Inactive -} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/state/Value.kt b/befunge/src/main/kotlin/befide/befunge/state/Value.kt deleted file mode 100644 index 45fefaf..0000000 --- a/befunge/src/main/kotlin/befide/befunge/state/Value.kt +++ /dev/null @@ -1,7 +0,0 @@ -package befide.befunge.state - -data class Value(val value: Long) { - constructor(value: Char) : this(value.toLong()) - - val asChar: Char? = if (value in (32..127)) value.toChar() else null -} \ No newline at end of file diff --git a/befunge/src/main/kotlin/befide/befunge/state/Vec.kt b/befunge/src/main/kotlin/befide/befunge/state/Vec.kt deleted file mode 100644 index fb6f263..0000000 --- a/befunge/src/main/kotlin/befide/befunge/state/Vec.kt +++ /dev/null @@ -1,14 +0,0 @@ -package befide.befunge.state - -infix fun Int.mod(other: Int): Int { - val x = this % other - return if (x >= 0) x else (x + other) -} - -data class Vec(val x: Int, val y: Int) { - operator fun plus(other: Vec) = Vec(x + other.x, y + other.y) - operator fun times(c: Int) = Vec(x * c, y * c) - - infix fun mod(other: Vec) = Vec(x mod other.x, y mod other.y) - operator fun unaryMinus() = Vec(-x, -y) -} \ No newline at end of file diff --git a/ide/src/main/kotlin/befide/ide/ActionView.kt b/ide/src/main/kotlin/befide/ide/ActionView.kt deleted file mode 100644 index 6966213..0000000 --- a/ide/src/main/kotlin/befide/ide/ActionView.kt +++ /dev/null @@ -1,185 +0,0 @@ -package befide.ide - -import befide.befunge.core.Interpreter -import befide.befunge.state.IpMode -import javafx.animation.Animation -import javafx.animation.Timeline -import javafx.beans.property.SimpleBooleanProperty -import javafx.beans.property.SimpleObjectProperty -import javafx.stage.FileChooser -import javafx.util.Duration -import tornadofx.* -import tornadofx.getValue -import tornadofx.setValue -import java.io.File - -class ActionView(val interp: Interpreter, val codeView: CodeView, val ioView: IOView, val editorView: EditorView) : View() { - val stepProperty = SimpleBooleanProperty(false) - var step by stepProperty - - var runTimeline: Timeline = timeline(false) { - keyframe(Duration.seconds(0.0)) { - setOnFinished { - step = interp.step() - } - } - keyframe(Duration.seconds(1.0)) {} - - cycleCount = Animation.INDEFINITE - } - - val isRunningProperty = SimpleBooleanProperty(false) - var isRunning by isRunningProperty - - val canResetProperty = SimpleBooleanProperty(false) - var canReset by canResetProperty - - val saveFileProperty = SimpleObjectProperty() - var saveFile by saveFileProperty - - fun start(rate: Double) { - stop() - - if (interp.ip.mode == IpMode.Inactive) reset() - - isRunning = true - canReset = true - - interp.funge.values = codeView.values - - runTimeline.rate = rate - runTimeline.playFromStart() - } - - fun step() { - canReset = true - - interp.funge.values = codeView.values - - step = interp.step() - } - - fun stop() { - isRunning = false - - runTimeline.stop() - } - - fun reset() { - stop() - interp.reset() - ioView.reset() - canReset = false - } - - fun clearCode() { - reset() - codeView.clear() - } - - fun save() { - if (saveFile == null) { - saveAs() - } else { - saveFile.writeText(codeView.src) - } - } - - private val chooser: FileChooser = FileChooser().apply { - title = "Befunge File" - extensionFilters.setAll( - FileChooser.ExtensionFilter("Befunge 93", "*.bf", "*.b93"), - FileChooser.ExtensionFilter("Befunge 98", "*.bf", "*.b98")) - } - - fun saveAs() { - val file: File? = chooser.showSaveDialog(primaryStage) - if (file != null) { - saveFile = file - save() - } - } - - fun open() { - val file: File? = chooser.showOpenDialog(primaryStage) - if (file != null) { - saveFile = file - - editorView.title = "${saveFile.nameWithoutExtension} [${saveFile.absolutePath}] - Befide" - - reset() - codeView.src = saveFile.readText() - } - } - - fun new() { - clearCode() - saveFile = null - editorView.title = "Befide" - } - - override val root = hbox { - button("step") { - setOnAction { step() } - disableWhen(isRunningProperty) - } - - separator {} - - button("run") { - setOnAction { start(10000.0) } - } - - button("walk") { - setOnAction { start(50.0) } - } - - button("crawl") { - setOnAction { start(4.0) } - } - - separator {} - - button("stop") { - setOnAction { stop() } - enableWhen(isRunningProperty) - } - - spacer {} - - button("reset") { - setOnAction { reset() } - enableWhen(canResetProperty) - } - - separator {} - - button("open") { - setOnAction { open() } - disableWhen(isRunningProperty) - } - - button("save as") { - setOnAction { saveAs() } - } - - button("save") { - setOnAction { save() } - - enableWhen(saveFileProperty.isNotNull.and(isRunningProperty.not())) - } - - button("new") { - setOnAction { new() } - disableWhen(isRunningProperty) - } - } - - init { - stepProperty.onChange { - if (!it) stop() - } - - new() - } -} \ No newline at end of file diff --git a/ide/src/main/kotlin/befide/ide/CodeLabel.kt b/ide/src/main/kotlin/befide/ide/CodeLabel.kt deleted file mode 100644 index dbe39e9..0000000 --- a/ide/src/main/kotlin/befide/ide/CodeLabel.kt +++ /dev/null @@ -1,43 +0,0 @@ -package befide.ide - -import befide.befunge.core.Interpreter -import befide.befunge.state.Value -import befide.befunge.state.Vec -import javafx.beans.property.ObjectProperty -import javafx.beans.property.SimpleObjectProperty -import javafx.scene.control.Label -import tornadofx.* - -class CodeLabel(val pos: Vec, val cursorPos: ObjectProperty, val interp: Interpreter) : Label() { - var valueProperty = SimpleObjectProperty(Value(' ')) - var value: Value by valueProperty - - fun restyle() { - styleClass.setAll("code") - - val char = value.asChar ?: '\u2022' - - if (char in "0123456789") styleClass.add("code-num") - if (char in "gp") styleClass.add("code-funge") - if (char in "<>^v?#") styleClass.add("code-dir") - if (char in "@") styleClass.add("code-stop") - if (char in "_|") styleClass.add("code-condition") - if (char in "\"") styleClass.add("code-quote") - - if (pos == cursorPos.value) styleClass.add("code-cursor") - if (pos == interp.ip.pos) styleClass.add("code-cursor-ip") - if (char == '\u2022') styleClass.add("unknown") - } - - init { - textProperty().bind(valueProperty.stringBinding { it?.asChar?.toString() ?: "\u2022" }) - - setOnMouseClicked { - cursorPos.value = pos - } - - valueProperty.addListener { _, _, _ -> restyle() } - - restyle() - } -} \ No newline at end of file diff --git a/ide/src/main/kotlin/befide/ide/CodeView.kt b/ide/src/main/kotlin/befide/ide/CodeView.kt deleted file mode 100644 index 393e759..0000000 --- a/ide/src/main/kotlin/befide/ide/CodeView.kt +++ /dev/null @@ -1,160 +0,0 @@ -package befide.ide - -import befide.befunge.core.Interpreter -import befide.befunge.state.Value -import befide.befunge.state.Vec -import javafx.beans.property.ObjectProperty -import javafx.beans.property.SimpleObjectProperty -import javafx.scene.input.KeyCode -import tornadofx.* - -operator fun List>.get(v: Vec): T = this[v.y][v.x] - -class CodeView(val interp: Interpreter) : View() { - val cursorPosProperty: ObjectProperty = SimpleObjectProperty(Vec(0, 0)) - var cursorPos by cursorPosProperty - - val cursorDeltaProperty = SimpleObjectProperty(Vec(1, 0)) - var cursorDelta by cursorDeltaProperty - - var labels: List> = List(25) { y -> List(80) { x -> CodeLabel(Vec(x, y), cursorPosProperty, interp) } } - - var values: List> - get() = labels.map { it.map { it.value } } - set(data) { - for (y in 0 until labels.size) - for (x in 0 until labels[y].size) - labels[y][x].value = data[y][x] - } - - init { - cursorPosProperty.addListener { _, old, new -> - labels[old].restyle() - labels[new].restyle() - } - - interp.ipChanged += { - labels[it.from.pos].restyle() - labels[it.to.pos].restyle() - } - - interp.fungeChanged += { - for (change in it.changes) { - labels[change.vec].value = change.to - labels[change.vec].restyle() - } - } - } - - fun move(delta: Vec? = null) { - cursorPos = interp.funge.nextVec(cursorPos, delta ?: cursorDelta) - } - - fun clear() { - for (row in labels) { - for (lbl in row) { - lbl.value = Value(' ') - } - } - } - - var src: String - get() = labels.joinToString("\n") { row -> - row.dropLastWhile { lbl -> - lbl.value.asChar?.isWhitespace() ?: false - }.joinToString("") { lbl -> - lbl.value.asChar?.toString() ?: "\u2022" - } - } - set(value) { - val lines = value.lines() - - for (row in labels) { - for (lbl in row) { - val char = lines.getOrNull(lbl.pos.y)?.getOrNull(lbl.pos.x) ?: ' ' - lbl.value = Value(char) - } - } - } - - override val root = hbox { - isFocusTraversable = true - - addClass("code-view") - - vbox { - children.setAll(labels.map { row -> hbox { children.setAll(row) } }) - } - - setOnMouseClicked { - requestFocus() - } - - setOnKeyPressed { - when (it.code) { - KeyCode.RIGHT -> move(Vec(1, 0)) - KeyCode.LEFT -> move(Vec(-1, 0)) - KeyCode.DOWN -> move(Vec(0, 1)) - KeyCode.UP -> move(Vec(0, -1)) - else -> return@setOnKeyPressed - } - if (it.isAltDown) when (it.code) { - KeyCode.RIGHT -> cursorDelta = Vec(1, 0) - KeyCode.LEFT -> cursorDelta = Vec(-1, 0) - KeyCode.DOWN -> cursorDelta = Vec(0, 1) - KeyCode.UP -> cursorDelta = Vec(0, -1) - else -> Unit - } - it.consume() - } - - setOnKeyTyped { - for (ch in it.character) { - when { - !ch.isISOControl() -> { - cursorDelta = when (ch) { - '>' -> Vec(1, 0) - '<' -> Vec(-1, 0) - 'v' -> Vec(0, 1) - '^' -> Vec(0, -1) - else -> cursorDelta - } - - labels[cursorPos].value = Value(ch) - - move() - } - - ch == '\u0008' -> { // backspace - move(-cursorDelta) - - labels[cursorPos].value = Value(' ') - } - - ch == '\u000d' -> when (cursorDelta) { // return - Vec(1, 0) -> { - move(Vec(0, 1)) - cursorPos = Vec(0, cursorPos.y) - } - Vec(-1, 0) -> { - move(Vec(0, -1)) - cursorPos = Vec(interp.funge.width - 1, cursorPos.y) - } - Vec(0, 1) -> { - move(Vec(-1, 0)) - cursorPos = Vec(cursorPos.x, 0) - } - Vec(0, -1) -> { - move(Vec(1, 0)) - cursorPos = Vec(cursorPos.x, interp.funge.height - 1) - } - } - - else -> { - println("'$ch' (${ch.toInt()})") - } - } - } - } - } -} \ No newline at end of file diff --git a/ide/src/main/kotlin/befide/ide/EditorView.kt b/ide/src/main/kotlin/befide/ide/EditorView.kt index 4c3f280..4be392d 100644 --- a/ide/src/main/kotlin/befide/ide/EditorView.kt +++ b/ide/src/main/kotlin/befide/ide/EditorView.kt @@ -1,34 +1,12 @@ package befide.ide -import befide.befunge.b93.B93Interpreter -import befide.befunge.core.Interpreter import tornadofx.* class EditorView : View("Befide") { - private var interp: Interpreter = B93Interpreter() - - private val codeView = CodeView(interp) - private val stackView = StackView(interp) - private val ioView = IOView(interp) - private val actionView = ActionView(interp, codeView, ioView, this) - override val root = borderpane { - top { add(actionView) } - - center { add(codeView) } - - right { add(stackView) } - - bottom { add(ioView) } } init { primaryStage.isResizable = false } - - override fun onDock() { - super.onDock() - - codeView.root.requestFocus() - } } \ No newline at end of file diff --git a/ide/src/main/kotlin/befide/ide/IOView.kt b/ide/src/main/kotlin/befide/ide/IOView.kt deleted file mode 100644 index 5087de2..0000000 --- a/ide/src/main/kotlin/befide/ide/IOView.kt +++ /dev/null @@ -1,55 +0,0 @@ -package befide.ide - -import befide.befunge.core.Interpreter -import javafx.beans.property.SimpleStringProperty -import javafx.event.EventHandler -import tornadofx.* -import tornadofx.getValue -import tornadofx.setValue - -class IOView(val interp: Interpreter) : View() { - val outputProperty = SimpleStringProperty("") - var output by outputProperty - val oldinputProperty = SimpleStringProperty("") - var oldinput by oldinputProperty - - - init { - interp.outputChanged += { - var str = "" - while (!interp.stdOutput.isEmpty()) { - str += interp.stdOutput.remove() - } - output += str - } - // add listeners to interp, handle streams, idk - } - - override val root = vbox { - addClass("") - textarea(outputProperty) { - isWrapText = true - isEditable = false - // width, height, idk - prefHeight = 200.0 - } - textarea(oldinputProperty) { - isEditable = false - prefHeight = 0.0 - - oldinputProperty.onChange { scrollTop = Double.MAX_VALUE } - } - textfield { - onAction = EventHandler { - oldinput += this.text + '\n' - this.text.forEach { c -> interp.stdInput.add(c) } - this.clear() - } - } - } - - fun reset() { - output = "" - oldinput = "" - } -} \ No newline at end of file diff --git a/ide/src/main/kotlin/befide/ide/StackView.kt b/ide/src/main/kotlin/befide/ide/StackView.kt deleted file mode 100644 index c4ac334..0000000 --- a/ide/src/main/kotlin/befide/ide/StackView.kt +++ /dev/null @@ -1,42 +0,0 @@ -package befide.ide - -import befide.befunge.b93.padEnd -import befide.befunge.core.Interpreter -import befide.befunge.state.Value -import javafx.beans.property.SimpleListProperty -import javafx.beans.property.SimpleObjectProperty -import javafx.beans.property.SimpleStringProperty -import javafx.collections.FXCollections -import javafx.scene.layout.Priority -import javafx.scene.text.Font -import tornadofx.* -import tornadofx.getValue -import tornadofx.setValue - -class StackView(val interp: Interpreter) : View() { - val charOutProperty = SimpleStringProperty() - var charOut by charOutProperty - - override val root = textarea(charOutProperty) { - addClass("stack-pane") - prefWidth = 150.0 - isEditable = false - } - - private fun getStackStr(mapping: (Value) -> T): String { - val num = interp.funge.height - 3 - return interp.stack.takeLast(num) - .map(mapping) - .padEnd(num, "") -// .reversed() - .joinToString("\n") - } - - init { - charOut = getStackStr { "${it.asChar ?: '\u2022'} (${it.value})" } - - interp.stackChanged += { - charOut = getStackStr { "${it.asChar ?: '\u2022'} (${it.value})" } - } - } -}