12 Commits

Author SHA1 Message Date
660a54eda5 better multithreaded streams behavior 2018-11-28 02:18:39 -05:00
14eb1a87f7 rename notify to not shadow object.notify 2018-11-28 00:13:26 -05:00
81750a920d more consistent naming, corrected abstract class 2018-11-27 18:18:05 -05:00
2e03a031b0 make mutablefunge abstract, add notify method 2018-11-27 17:50:24 -05:00
8d6da62be1 ignore statistic plugin 2018-11-27 15:49:57 -05:00
626094320d add jump-over to b93extras 2018-11-27 14:14:28 -05:00
dad81ffd40 use map rather than jagged array 2018-11-27 14:02:45 -05:00
ffab11f785 move reset to abstract class 2018-11-27 13:35:40 -05:00
dd6076441c consistency tweaks 2018-11-27 03:38:43 -05:00
53c7b87354 rudimentary instruction set composition 2018-11-27 03:28:14 -05:00
fcb017e6df separate instructionset type 2018-11-27 03:12:18 -05:00
bda23d7e9d working framework 2018-11-27 02:02:32 -05:00
41 changed files with 587 additions and 1081 deletions

2
.gitignore vendored
View File

@@ -24,7 +24,6 @@ buildNumber.properties
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
@@ -94,3 +93,4 @@ fabric.properties
# Mobile Tools for Java (J2ME)
# Package Files #
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
.idea/statistic.xml

View File

@@ -1,53 +0,0 @@
package befide.befunge.b93
import befide.befunge.core.*
import befide.befunge.state.*
fun <T> List<T>.padEnd(size: Int, factory: (Int) -> (T)): List<T> = this + (this.size until size).map { factory(it) }
fun <T> List<T>.padEnd(size: Int, value: T): List<T> = 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()
}
}
}
}

View File

@@ -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<Value>()
override val ip = B93Pointer(Vec(0, 0), Vec(1, 0), IpMode.Normal)
override val fungeChanged: Event<FungeEvent> = Event()
override val stackChanged: Event<StackEvent> = Event()
override val ipChanged: Event<IpEvent> = Event()
override val outputChanged: Event<OutputEvent> = Event()
override val stdInput = LinkedList<Char>()
override val stdOutput = LinkedList<String>()
private var outBuf: StringBuffer = StringBuffer()
private var bufLimit = 0
private val fungeMods = HashMap<Vec,Value>()
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<Value> {
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 <T> Queue<T>.takeWhile(predicate: (T) -> Boolean): Sequence<T?> {
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))
}
}

View File

@@ -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

View File

@@ -0,0 +1,97 @@
package befide.befunge.b93
import befide.befunge.b93.state.Data93
import befide.befunge.b93.state.PointerMode
import befide.befunge.b93.state.Vec2
import befide.befunge.core.InstructionSet
import befide.befunge.core.MutableInterpreter
import befide.befunge.core.util.chooseOne
class B93Instructions : InstructionSet<Vec2, Data93, PointerMode> {
override fun MutableInterpreter<Vec2, Data93, PointerMode>.handle(): Boolean {
when (mode) {
PointerMode.Terminated -> return true
PointerMode.String -> when (instr.char) {
null -> return false
'"' -> mode = PointerMode.Normal
else -> push(instr)
}
PointerMode.Normal -> when (instr.char) {
null -> return false
in "0123456789" -> push(Data93(instr.char.toString().toLong(16)))
'+' -> pop(2).let { (b, a) -> push(a + b) }
'-' -> pop(2).let { (b, a) -> push(a - b) }
'*' -> pop(2).let { (b, a) -> push(a * b) }
'/' -> pop(2).let { (b, a) -> push(a / b) }
'%' -> pop(2).let { (b, a) -> push(a mod b) }
'!' -> pop(1).let { (n) -> push(Data93(if (n.data == 0L) 1L else 0L)) }
'`' -> pop(2).let { (b, a) -> push(Data93(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()
'_' -> pop(1).let { (n) -> delta = if (n.data == 0L) Vec2.RIGHT else Vec2.LEFT }
'|' -> pop(1).let { (n) -> delta = if (n.data == 0L) Vec2.DOWN else Vec2.UP }
'"' -> mode = PointerMode.String
':' -> pop(1).let { (n) -> push(n, n) }
'\\' -> pop(2).let { (b, a) -> push(b, a) }
'$' -> pop(1)
'.' -> pop(1).let { (n) -> stdout?.write("${n.data} ") }
',' -> pop(1).let { (ch) -> stdout?.write(ch.data.toChar().toString()) }
'p' -> pop(3).let { (y, x, n) -> funge[Vec2(x.data.toInt(), y.data.toInt())] = n }
'g' -> pop(2).let { (y, x) -> push(funge[Vec2(x.data.toInt(), y.data.toInt())]) }
'~' -> push(Data93(stdin?.read()?.toLong() ?: 0L))
'&' -> {
val chars = generateSequence {
stdin?.read()?.toChar()?.takeIf { it.isDigit() }
}
val long = chars.joinToString("") {
it.toString()
}.ifEmpty { "0" }.toLong()
push(Data93(long))
}
'@' -> mode = PointerMode.Terminated
' ' -> do move() while (next.char == ' ')
else -> return false
}
}
return true
}
}
class B93Extras : InstructionSet<Vec2, Data93, PointerMode> {
override fun MutableInterpreter<Vec2, Data93, PointerMode>.handle(): Boolean {
when (mode) {
PointerMode.Terminated -> return true
PointerMode.String -> return false
PointerMode.Normal -> when (instr.char) {
null -> return false
in "0123456789abcdef" -> push(Data93(instr.char.toString().toLong(16)))
'\'' -> {
move()
push(instr)
}
';' -> do move() while (instr.char != ';')
}
}
return true
}
}

View File

@@ -0,0 +1,31 @@
package befide.befunge.b93
import befide.befunge.b93.state.*
import befide.befunge.core.*
import befide.befunge.core.util.readAll
import com.sun.imageio.spi.OutputStreamImageOutputStreamSpi
import java.io.*
import java.util.*
import kotlin.concurrent.thread
import kotlin.concurrent.timer
class Interpreter93(override var stdin: Reader? = null, override var stdout: Writer? = null)
: MutableInterpreter<Vec2, Data93, PointerMode>() {
override val ip = Pointer93()
override val funge = Funge93()
override val stack = Stack<Data93>()
override fun stackDefault() = Data93.ZERO
override val instructionSet = B93Instructions() + B93Extras()
override fun run(afterEach: () -> Unit) {
while (mode != PointerMode.Terminated) {
step()
afterEach()
stdout?.flush()
}
}
}

View File

@@ -0,0 +1,67 @@
package befide.befunge.b93
import befide.befunge.core.util.readAll
import java.io.PipedReader
import java.io.PipedWriter
import kotlin.concurrent.thread
import kotlin.concurrent.timer
fun runSync() {
val int = Interpreter93(System.`in`.reader(), System.out.writer())
// interactive calculator
int.funge.src = "\" :b ]%/*-+[ a\">:#,_&~~\$a3*0p& .@"
int.run {}
println()
}
fun runThreaded() {
val stdout = PipedReader()
val stdin = PipedWriter()
val int = Interpreter93(PipedReader(stdin), PipedWriter(stdout))
// incrementer
int.funge.src = ":.&+"
val input = timer("input", period = 50L) {
generateSequence {
System.`in`.read().takeUnless { it == -1 }
}.forEach(stdin::write)
}
val output = timer("output", period = 500L) {
print(stdout.readAll())
}
val stepper = thread {
int.run { Thread.sleep(50L) }
}
// input, output, and computation happen concurrently at this point.
stepper.join()
input.cancel()
output.cancel()
println(stdout.readAll())
int.stdout?.close()
stdout.close()
int.stdin?.close()
stdin.close()
}
fun main(args: Array<String>) {
println("synchronous:")
runSync()
println()
println("threaded:")
runThreaded()
}

View File

@@ -0,0 +1,26 @@
package befide.befunge.b93.state
import befide.befunge.core.state.Data
import befide.befunge.core.util.mod
data class Data93(val data: Long)
: Data {
companion object {
val SPACE = Data93(' ')
val ZERO = Data93(0)
}
override val char = data.toChar().takeUnless { it.isISOControl() }
constructor(char: Char) : this(char.toLong())
operator fun plus(o: Data93) = Data93(data + o.data)
operator fun minus(o: Data93) = Data93(data - o.data)
operator fun times(o: Data93) = Data93(data * o.data)
operator fun div(o: Data93) = Data93(data / o.data)
operator fun unaryMinus() = Data93(-data)
infix fun mod(o: Data93) = Data93(data mod o.data)
override fun toString() = "${char ?: '\u2022'} ($data)"
}

View File

@@ -0,0 +1,61 @@
package befide.befunge.b93.state
import befide.befunge.core.state.MutableFunge
class Funge93
: MutableFunge<Vec2, Data93>() {
companion object {
val DEFAULT_DATA = Data93.SPACE
const val DEFAULT_CHAR = '\u2022' // bullet
}
override val size = Vec2(80, 25)
override fun next(pos: Vec2, delta: Vec2): Vec2 = (pos + delta) mod size
val contents = hashMapOf<Vec2, Data93>()
override fun get(pos: Vec2): Data93 = contents[pos] ?: DEFAULT_DATA
override fun set(pos: Vec2, data: Data93) {
if (pos in Vec2.ZERO until size) notifyChanged(pos) {
contents[pos] = data
}
}
override var data: Map<Vec2, Data93>
get() {
return contents.toMap()
}
set(data) {
clear()
for ((v, d) in data.entries)
this[v] = d
}
override var src: String
get() {
val lines = mutableListOf<String>()
for ((y, row) in data.entries.groupBy { it.key.y }.toSortedMap()) {
lines += List(y - lines.size) { "" }
val chars = mutableListOf<Char>()
for ((v, ch) in row.sortedBy { it.key.x }) {
chars += List(v.x - chars.size) { ' ' }
chars += ch.char ?: DEFAULT_CHAR
}
lines += chars.joinToString("")
}
return lines.joinToString("\n")
}
set(src) {
data = src.lines().mapIndexed { y, line ->
line.mapIndexed { x, ch ->
Pair(Vec2(x, y), Data93(ch))
}
}.flatten().toMap()
}
override fun clear() {
contents.clear()
}
}

View File

@@ -0,0 +1,12 @@
package befide.befunge.b93.state
import befide.befunge.core.state.MutablePointer
import befide.befunge.core.state.Pointer
data class Pointer93(override var pos: Vec2 = Vec2.ZERO,
override var delta: Vec2 = Vec2.RIGHT,
override var mode: PointerMode = PointerMode.Normal)
: MutablePointer<Vec2, PointerMode> {
override fun copy(): Pointer<Vec2, PointerMode> = Pointer93(pos, delta, mode)
}

View File

@@ -0,0 +1,5 @@
package befide.befunge.b93.state
enum class PointerMode {
Normal, String, Terminated
}

View File

@@ -0,0 +1,39 @@
package befide.befunge.b93.state
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)
operator fun rangeTo(o: Vec2) = Bounds2(x..o.x, y..o.y)
infix fun until(o: Vec2) = Bounds2(x until o.x, y until o.y)
}
data class Bounds2(val x: IntRange, val y: IntRange) {
override fun toString(): String = "($x, $y)"
operator fun contains(o: Vec2): Boolean = o.x in x && o.y in y
}

View File

@@ -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<List<Value>>
/**
* 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<Vec> {
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<Value>) {
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<Value> {
return nextVecs(vec, delta).take(count).map(this::get).asIterable()
}
}

View File

@@ -0,0 +1,33 @@
package befide.befunge.core
import befide.befunge.core.state.Data
interface InstructionSet<V, D : Data, M : Enum<M>> {
/**
* @return whether this instruction set handled the instruction. If true, the step will be completed
*/
fun MutableInterpreter<V, D, M>.handle(): Boolean
fun step(inter: MutableInterpreter<V, D, M>): Boolean = inter.handle()
operator fun plus(o: InstructionSet<V, D, M>): MultiInstructionSet<V, D, M> =
MultiInstructionSet(this, o)
operator fun plus(o: MultiInstructionSet<V, D, M>): MultiInstructionSet<V, D, M> =
MultiInstructionSet(listOf(this) + o.sets)
}
class MultiInstructionSet<V, D : Data, M : Enum<M>>
(vararg val sets: InstructionSet<V, D, M>)
: InstructionSet<V, D, M> {
constructor(sets: List<InstructionSet<V, D, M>>) : this(*sets.toTypedArray())
override fun MutableInterpreter<V, D, M>.handle(): Boolean {
for (set in sets) if (set.step(this)) return true
return false
}
override operator fun plus(o: InstructionSet<V, D, M>): MultiInstructionSet<V, D, M> =
MultiInstructionSet(sets.toList() + listOf(o))
}

View File

@@ -1,38 +1,36 @@
package befide.befunge.core
import befide.befunge.events.*
import befide.befunge.state.Value
import java.util.*
import java.util.concurrent.BlockingQueue
import befide.befunge.core.events.IpChange
import befide.befunge.core.events.StackChange
import befide.befunge.core.state.Data
import befide.befunge.core.state.Funge
import befide.befunge.core.state.Pointer
import befide.befunge.core.util.Event
import java.io.PipedReader
import java.io.PipedWriter
import java.io.Reader
import java.io.Writer
/**
* 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<Value>
val ip: Pointer
interface Interpreter<V, D : Data, M : Enum<M>> {
val ip: Pointer<V, M>
val funge: Funge<V, D>
val stack: List<D>
val fungeChanged: Event<FungeEvent>
val stackChanged: Event<StackEvent>
val ipChanged: Event<IpEvent>
val outputChanged: Event<OutputEvent>
fun stackDefault(): D
val stdInput: Queue<Char>
val stdOutput: Queue<String>
val mode: M get() = ip.mode
val delta: V get() = ip.delta
val pos: V get() = ip.pos
val instr: D get() = funge[ip.pos]
val next: D get() = funge[funge.next(pos, delta)]
/**
* @return If [ip] is inactive after this step, indicating execution has halted, then return `false`
*/
fun step(): Boolean
val onIpChange: Event<IpChange<V, M>>
val onStackChange: Event<StackChange<D>>
/**
* 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()
var stdin: Reader?
val stdout: Writer?
fun step()
fun run(afterEach: () -> Unit)
}

View File

@@ -0,0 +1,71 @@
package befide.befunge.core
import befide.befunge.core.events.IpChange
import befide.befunge.core.events.StackChange
import befide.befunge.core.events.StackOp
import befide.befunge.core.state.Data
import befide.befunge.core.state.MutableFunge
import befide.befunge.core.state.MutablePointer
import befide.befunge.core.util.Event
import befide.befunge.core.util.readAll
import java.util.*
abstract class MutableInterpreter<V, D : Data, M : Enum<M>>
: Interpreter<V, D, M> {
abstract override val ip: MutablePointer<V, M>
abstract override val funge: MutableFunge<V, D>
abstract override val stack: Stack<D>
abstract val instructionSet: InstructionSet<V, D, M>
override val onIpChange: Event<IpChange<V, M>> = Event()
override val onStackChange: Event<StackChange<D>> = Event()
override fun step() {
instructionSet.step(this)
move()
}
fun notifyIp(op: () -> Unit) {
val from = ip.copy()
op()
val to = ip.copy()
onIpChange(IpChange(from, to))
}
fun notifyStack(op: StackOp, block: () -> D): D {
val res = block()
onStackChange(StackChange(op, res))
return res
}
override var mode: M
get() = ip.mode
set(mode) = notifyIp { ip.mode = mode }
override var delta: V
get() = ip.delta
set(delta) = notifyIp { ip.delta = delta }
override var pos: V
get() = ip.pos
set(pos) = notifyIp { ip.pos = pos }
fun move(delta: V? = null) {
pos = funge.next(pos, delta ?: this.delta)
}
fun pop(): D = notifyStack(StackOp.Pop) {
return@notifyStack if (stack.empty()) stackDefault() else stack.pop()
}
fun pop(n: Int): List<D> {
return (0 until n).map { pop() }
}
fun push(vararg data: D) {
for (datum in data) {
notifyStack(StackOp.Push) { stack.push(datum) }
}
}
}

View File

@@ -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
}

View File

@@ -0,0 +1,9 @@
package befide.befunge.core.events
import befide.befunge.core.state.Data
import befide.befunge.core.state.Funge
data class FungeChange<V, D : Data>
(val pos: V,
val from: D,
val to: D)

View File

@@ -0,0 +1,6 @@
package befide.befunge.core.events
import befide.befunge.core.state.Pointer
data class IpChange<V, M : Enum<M>>
(val from: Pointer<V, M>, val to: Pointer<V, M>)

View File

@@ -0,0 +1,8 @@
package befide.befunge.core.events
import befide.befunge.core.state.Data
enum class StackOp { Push, Pop }
data class StackChange<D : Data>
(val op: StackOp, val data: D)

View File

@@ -0,0 +1,5 @@
package befide.befunge.core.state
interface Data {
val char: Char?
}

View File

@@ -0,0 +1,17 @@
package befide.befunge.core.state
import befide.befunge.core.events.FungeChange
import befide.befunge.core.util.Event
interface Funge<V, D : Data> {
val size: V
val data: Map<V, D>
val src: String
fun next(pos: V, delta: V): V
operator fun get(pos: V): D
val onChange: Event<FungeChange<V, D>>
}

View File

@@ -0,0 +1,25 @@
package befide.befunge.core.state
import befide.befunge.core.events.FungeChange
import befide.befunge.core.util.Event
abstract class MutableFunge<V, D : Data>
: Funge<V, D> {
abstract override var data: Map<V, D>
abstract override var src: String
override val onChange = Event<FungeChange<V, D>>()
fun notifyChanged(pos: V, op: () -> Unit) {
val from = this[pos]
op()
val to = this[pos]
onChange(FungeChange(pos, from, to))
}
abstract operator fun set(pos: V, data: D)
abstract fun clear()
}

View File

@@ -0,0 +1,9 @@
package befide.befunge.core.state
interface MutablePointer<V, M : Enum<M>>
: Pointer<V, M> {
override var pos: V
override var delta: V
override var mode: M
}

View File

@@ -0,0 +1,9 @@
package befide.befunge.core.state
interface Pointer<V, M : Enum<M>> {
val pos: V
val delta: V
val mode: M
fun copy(): Pointer<V, M>
}

View File

@@ -1,4 +1,4 @@
package befide.befunge.events
package befide.befunge.core.util
import java.util.function.Consumer
@@ -12,7 +12,7 @@ internal operator fun <T> Handler<T>.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<String>) {
* val event = Event<ServerEvent>()

View File

@@ -0,0 +1,27 @@
package befide.befunge.core.util
import java.io.PipedReader
import java.io.Reader
import java.util.*
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 <T> List<T>.chooseOne(): T = this[Random.nextInt(size)]
fun <T> Boolean.orNull(block: () -> T): T? = if (this) block() else null
fun Reader.readAll(): String = generateSequence {
ready().orNull { read().toChar() }
}.joinToString("")

View File

@@ -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<FungeChange>)

View File

@@ -1,5 +0,0 @@
package befide.befunge.events
import befide.befunge.core.Pointer
data class IpEvent(val from: Pointer, val to: Pointer)

View File

@@ -1,3 +0,0 @@
package befide.befunge.events
class OutputEvent

View File

@@ -1,7 +0,0 @@
package befide.befunge.events
enum class StackAction {
Push,
Pop,
Clear
}

View File

@@ -1,5 +0,0 @@
package befide.befunge.events
import befide.befunge.state.Value
data class StackEvent(val op: StackAction, val values: List<Value>)

View File

@@ -1,7 +0,0 @@
package befide.befunge.state
enum class IpMode {
Normal,
String,
Inactive
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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<File>()
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()
}
}

View File

@@ -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<Vec>, val interp: Interpreter) : Label() {
var valueProperty = SimpleObjectProperty<Value>(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()
}
}

View File

@@ -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 <T> List<List<T>>.get(v: Vec): T = this[v.y][v.x]
class CodeView(val interp: Interpreter) : View() {
val cursorPosProperty: ObjectProperty<Vec> = SimpleObjectProperty<Vec>(Vec(0, 0))
var cursorPos by cursorPosProperty
val cursorDeltaProperty = SimpleObjectProperty<Vec>(Vec(1, 0))
var cursorDelta by cursorDeltaProperty
var labels: List<List<CodeLabel>> = List(25) { y -> List(80) { x -> CodeLabel(Vec(x, y), cursorPosProperty, interp) } }
var values: List<List<Value>>
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()})")
}
}
}
}
}
}

View File

@@ -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()
}
}

View File

@@ -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 = ""
}
}

View File

@@ -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 <T> 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})" }
}
}
}