diff --git a/befunge/src/main/kotlin/befide/befunge/b93/B93Interpreter.kt b/befunge/src/main/kotlin/befide/befunge/b93/B93Interpreter.kt index 1e34412..573c953 100644 --- a/befunge/src/main/kotlin/befide/befunge/b93/B93Interpreter.kt +++ b/befunge/src/main/kotlin/befide/befunge/b93/B93Interpreter.kt @@ -6,6 +6,8 @@ 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 { @@ -22,6 +24,12 @@ class B93Interpreter : Interpreter { 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 = 10 private val fungeMods = HashMap() @@ -55,9 +63,10 @@ class B93Interpreter : Interpreter { stackChanged.invoke(StackEvent(StackAction.Push, listOf(v))) } - private fun push(vs: List) { + private fun push(v: Value, vararg vs: Value) { + _push(v) vs.forEach { _push(it) } - stackChanged.invoke(StackEvent(StackAction.Push, vs)) + stackChanged.invoke(StackEvent(StackAction.Push, listOf(v) + vs.toList())) } private fun peek(): Value { @@ -67,7 +76,7 @@ class B93Interpreter : Interpreter { return Value(0) } - private fun binop(bop: Char) { + private fun binop(bop: Char): Boolean { val (vb, va) = pop(2) val a = va.value val b = vb.value @@ -80,26 +89,26 @@ class B93Interpreter : Interpreter { '`' -> if (a > b) 1L else 0L else -> null } - res?.let { + return res?.let { val vres = Value(res) push(vres) - } + } != null } - private fun unop(uop: Char) { + 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 } - res?.let { + return res?.let { val vres = Value(it) push(vres) - } + } != null } - private fun changeDir(dir: Char) { + private fun changeDir(dir: Char): Boolean { val newDelta = when(dir) { '>' -> RIGHT '<' -> LEFT @@ -107,18 +116,19 @@ class B93Interpreter : Interpreter { 'v' -> DOWN else -> null } - newDelta?.let { + return newDelta?.let { ip.delta = it - } + } != null } - private fun randomDir() { + private fun randomDir(): Boolean { val dirs = listOf('<', '>', '^', 'v') val ind = Random().nextInt(4) changeDir(dirs[ind]) + return true } - private fun conditional(cop: Char) { + private fun conditional(cop: Char): Boolean { val vcond = pop() val cond = vcond.value == 0L val newDelta = when(cop) { @@ -126,21 +136,23 @@ class B93Interpreter : Interpreter { '_' -> if (cond) RIGHT else LEFT else -> null } - newDelta?.let { + return newDelta?.let { ip.delta = it - } + } != null } - private fun toggleStrmode() { + 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) { + private fun stackop(sop: Char): Boolean { + var ret = true when (sop) { ':' -> { val vc = peek().copy() @@ -148,52 +160,91 @@ class B93Interpreter : Interpreter { } '\\' -> { val (v2, v1) = pop(2) - push(listOf(v2, v1)) + push(v2, v1) } '$' -> { pop() } + else -> { + ret = false + } } + return ret } - private fun output(type: Char) { + private fun output(type: Char): Boolean { val vv = pop() val v = vv.value val out = when (type) { - '.' -> v.toString().toCharArray() + ' ' - ',' -> charArrayOf(v.toChar()) - else -> charArrayOf() + '.' -> v.toString() + ' ' + ',' -> v.toChar().toString() + else -> null } - print(out) //TEMPORARY - //TODO output {out} chararray + 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() { + 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) { - val inp = readLine() - val vinp = Value(when(type) { - '&' -> inp?.toLong() ?: 0L - '~' -> inp?.get(0)?.toLong() ?: 0L - else -> 0L - }) - push(vinp) + 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() { + 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() { + private fun fput(): Boolean { val (vy, vx, vv) = pop(3) val x = vx.value.toInt() val y = vy.value.toInt() @@ -205,23 +256,30 @@ class B93Interpreter : Interpreter { fungeMods[loc] = old } fungeChanged(FungeEvent(listOf(FungeChange(loc,old,vv)))) + return true } + return false } - private fun terminate() { + private fun terminate(): Boolean { + stdOutput.add(outBuf.toString()) + outBuf.delete(0, outBuf.length) + outputChanged.invoke(OutputEvent()) ip.mode = IpMode.Inactive + return true } - private fun pushDig(dig: Char) { + private fun pushDig(dig: Char): Boolean { val num = dig.toLong() - '0'.toLong() push(Value(num)) + return true } - private fun noOp() {} + private fun noOp(): Boolean = false - private fun execInstr(instr: Value) { + private fun execInstr(instr: Value): Boolean { val car = instr.asChar - when (car) { + return when (car) { null -> noOp() '+', '-', '*', '/', '%', '`' -> binop(car) '!' -> unop(car) @@ -241,23 +299,24 @@ class B93Interpreter : Interpreter { } } - private fun strMode(instr: Value) { + private fun strMode(instr: Value): Boolean { if (instr.asChar == '"') { toggleStrmode() - return + return true } push(instr) + return true } override fun step(): Boolean { val instr = funge[ip.pos] val currIP = ip.copy() - when (ip.mode) { + val processed = when (ip.mode) { IpMode.Inactive -> noOp() IpMode.Normal -> execInstr(instr) IpMode.String -> strMode(instr) } - if (ip.mode != IpMode.Inactive) { + if (processed) { stepIP() if (ip.mode != IpMode.String) { while (funge[ip.pos].asChar == ' ') { @@ -281,6 +340,9 @@ class B93Interpreter : Interpreter { 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 diff --git a/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt b/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt index 15e435d..18bf2fc 100644 --- a/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt +++ b/befunge/src/main/kotlin/befide/befunge/core/Interpreter.kt @@ -1,11 +1,9 @@ package befide.befunge.core -import befide.befunge.events.Event -import befide.befunge.events.FungeEvent -import befide.befunge.events.IpEvent -import befide.befunge.events.StackEvent +import befide.befunge.events.* import befide.befunge.state.Value import java.util.* +import java.util.concurrent.BlockingQueue /** * Interface for a Befunge interpreter @@ -22,6 +20,10 @@ interface Interpreter { 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` diff --git a/befunge/src/main/kotlin/befide/befunge/events/OutputEvent.kt b/befunge/src/main/kotlin/befide/befunge/events/OutputEvent.kt new file mode 100644 index 0000000..c73e5fa --- /dev/null +++ b/befunge/src/main/kotlin/befide/befunge/events/OutputEvent.kt @@ -0,0 +1,3 @@ +package befide.befunge.events + +class OutputEvent diff --git a/befunge/src/main/kotlin/befide/befunge/state/Value.kt b/befunge/src/main/kotlin/befide/befunge/state/Value.kt index dd469fe..45fefaf 100644 --- a/befunge/src/main/kotlin/befide/befunge/state/Value.kt +++ b/befunge/src/main/kotlin/befide/befunge/state/Value.kt @@ -3,5 +3,5 @@ package befide.befunge.state data class Value(val value: Long) { constructor(value: Char) : this(value.toLong()) - val asChar: Char? = if (value in (32..126)) value.toChar() else null + val asChar: Char? = if (value in (32..127)) value.toChar() else null } \ 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 index fd0f203..22a5d99 100644 --- a/ide/src/main/kotlin/befide/ide/ActionView.kt +++ b/ide/src/main/kotlin/befide/ide/ActionView.kt @@ -6,7 +6,7 @@ import javafx.animation.Timeline import javafx.util.Duration import tornadofx.* -class ActionView(val interp: Interpreter) : View() { +class ActionView(val interp: Interpreter, val ioView: IOView) : View() { var runTimeline: Timeline = timeline(false) { keyframe(Duration.seconds(0.0)) { setOnFinished { @@ -25,19 +25,29 @@ class ActionView(val interp: Interpreter) : View() { } button("reset") { - setOnAction { interp.reset() } + setOnAction { + interp.reset() + ioView.reset() + } } button("run") { setOnAction { - runTimeline.rate = 1000.0 + runTimeline.rate = 10000000.0 + runTimeline.playFromStart() + } + } + + button("walk") { + setOnAction { + runTimeline.rate = 50.0 runTimeline.playFromStart() } } button("crawl") { setOnAction { - runTimeline.rate = 2.0 + runTimeline.rate = 4.0 runTimeline.playFromStart() } } diff --git a/ide/src/main/kotlin/befide/ide/CodeView.kt b/ide/src/main/kotlin/befide/ide/CodeView.kt index 8dfa299..185b083 100644 --- a/ide/src/main/kotlin/befide/ide/CodeView.kt +++ b/ide/src/main/kotlin/befide/ide/CodeView.kt @@ -14,6 +14,7 @@ class CodeView(val interp: Interpreter) : View() { } override val root = textarea(srcProperty) { - + prefRowCount = 25 + prefColumnCount = 80 } } \ 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 30874d9..2403271 100644 --- a/ide/src/main/kotlin/befide/ide/EditorView.kt +++ b/ide/src/main/kotlin/befide/ide/EditorView.kt @@ -8,9 +8,9 @@ class EditorView : View("Befide") { private var interp: Interpreter = B93Interpreter() private val codeView = CodeView(interp) - private val actionView = ActionView(interp) private val stackView = StackView(interp) private val ioView = IOView(interp) + private val actionView = ActionView(interp, ioView) override val root = borderpane { top { add(actionView) } @@ -27,6 +27,35 @@ class EditorView : View("Befide") { } init { - codeView.src = """64+"!dlroW ,olleH">:#,_@""" + codeView.src = """>84*>:#v_55+"ude.ub@yelruta">:#,_@>188*+>\02p\12p\:22p#v_${'$'} 55+,1- v + ^ 0 v +1\ _^#-+*< >22g02g*"_@"*-!1- #v_v> + >:>::3g: ,\188 ^^ -1\g21\g22 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 index 6b2bcbf..44939fd 100644 --- a/ide/src/main/kotlin/befide/ide/StackView.kt +++ b/ide/src/main/kotlin/befide/ide/StackView.kt @@ -1,10 +1,47 @@ package befide.ide +import befide.befunge.b93.padEnd import befide.befunge.core.Interpreter +import befide.befunge.state.Value +import javafx.beans.property.SimpleStringProperty import tornadofx.* +import tornadofx.getValue +import tornadofx.setValue class StackView(val interp: Interpreter) : View() { - override val root = textarea { - prefWidth = 100.0 + val charOutProperty = SimpleStringProperty() + var charOut by charOutProperty + val longOutProperty = SimpleStringProperty() + var longOut by longOutProperty + + + override val root = hbox { + textarea(charOutProperty) { + maxWidth = 1.0 + paddingHorizontal = 1 + isEditable = false + } + textarea(longOutProperty) { + prefWidth = 90.0 + isEditable = false + } } -} \ No newline at end of file + + private fun getStackStr(mapping: (Value) -> T): String { + return interp.stack.takeLast(interp.funge.height) + .map (mapping) + .padEnd(interp.funge.height, "") + .reversed() + .joinToString("\n") + } + + init { + charOut = getStackStr { it.asChar ?: '*'} + longOut = getStackStr { it.value } + + interp.stackChanged += { + charOut = getStackStr { it.asChar ?: '*'} + longOut = getStackStr { it.value } + } + } +}