26 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
9dfa3e124f better file selection, rerun handling 2018-11-26 19:12:29 -05:00
4c115f41fb fix termination 2018-11-26 18:42:31 -05:00
7ca74d51d1 title rename 2018-11-26 15:51:01 -05:00
02fc840c4d termination updates UI 2018-11-26 13:37:27 -05:00
145d9fcf3c open, save files 2018-11-26 13:18:17 -05:00
401c0a354a open file 2018-11-26 12:54:47 -05:00
133ef02945 visual tweaks 2018-11-26 12:36:12 -05:00
57520ef947 visual tweaks 2018-11-26 12:35:47 -05:00
ff4b2600a1 make stack start at top to prevent scrolling issue 2018-11-25 23:31:04 -05:00
dd73965e39 make stack start at top to prevent scrolling issue 2018-11-25 23:26:23 -05:00
1eefc31cfe fixed broken start-stop-rerun issue 2018-11-25 22:52:28 -05:00
4cc69a8dbd Merge remote-tracking branch 'origin/master' 2018-11-25 22:17:38 -05:00
bbeb703f9d add more code styles 2018-11-25 22:17:35 -05:00
Jacob
037b9a8e96 Fix code that starts on a space not running 2018-11-25 22:09:32 -05:00
45 changed files with 629 additions and 923 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

18
.idea/artifacts/ide_jar.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<component name="ArtifactManager">
<artifact type="jar" build-on-make="true" name="ide:jar">
<output-path>$PROJECT_DIR$/out/artifacts/ide_jar</output-path>
<root id="archive" name="ide.jar">
<element id="module-output" name="ide" />
<element id="module-output" name="befunge" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.2.60/kotlin-stdlib-jdk8-1.2.60.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.3.0/kotlin-stdlib-1.3.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/javax/json/javax.json-api/1.1.2/javax.json-api-1.1.2.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/no/tornado/tornadofx/1.7.17/tornadofx-1.7.17.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.2/javax.json-1.1.2.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.0/kotlin-stdlib-common-1.3.0.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.2.60/kotlin-stdlib-jdk7-1.2.60.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.2.60/kotlin-reflect-1.2.60.jar" path-in-jar="/" />
</root>
</artifact>
</component>

View File

@@ -1,45 +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 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,353 +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 = 10
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 true
}
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)
else -> noOp()
}
}
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) {
while (funge[ip.pos].asChar == ' ') {
stepIP()
}
}
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,48 +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)
/**
* 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,68 +0,0 @@
package befide.ide
import befide.befunge.core.Interpreter
import javafx.animation.Animation
import javafx.animation.Timeline
import javafx.util.Duration
import tornadofx.*
class ActionView(val interp: Interpreter, val codeView: CodeView, val ioView: IOView) : View() {
var runTimeline: Timeline = timeline(false) {
keyframe(Duration.seconds(0.0)) {
setOnFinished {
if (!interp.step())
this@timeline.stop()
}
}
keyframe(Duration.seconds(1.0)) {}
cycleCount = Animation.INDEFINITE
}
override val root = hbox {
button("step") {
setOnAction {
interp.funge.setString(codeView.src)
interp.step()
}
}
button("reset") {
setOnAction {
interp.reset()
ioView.reset()
codeView.src = interp.funge.toString()
}
}
button("run") {
setOnAction {
interp.funge.setString(codeView.src)
runTimeline.rate = 10000.0
runTimeline.playFromStart()
}
}
button("walk") {
setOnAction {
interp.funge.setString(codeView.src)
runTimeline.rate = 50.0
runTimeline.playFromStart()
}
}
button("crawl") {
setOnAction {
interp.funge.setString(codeView.src)
runTimeline.rate = 4.0
runTimeline.playFromStart()
}
}
button("stop") {
setOnAction {
runTimeline.stop()
}
}
}
}

View File

@@ -1,37 +0,0 @@
package befide.ide
import befide.befunge.core.Interpreter
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() {
val charProperty = SimpleObjectProperty<Char>('\u0000')
var char: Char by charProperty
fun restyle() {
styleClass.setAll("code")
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 (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(charProperty.stringBinding { it?.toString() ?: " " })
setOnMouseClicked {
cursorPos.value = pos
}
charProperty.addListener { _, _, _ -> restyle() }
char = ' '
}
}

View File

@@ -1,126 +0,0 @@
package befide.ide
import befide.befunge.core.Interpreter
import befide.befunge.state.Vec
import javafx.beans.property.ObjectProperty
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.input.KeyCode
import tornadofx.*
import tornadofx.getValue
import tornadofx.setValue
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) } }
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].char = change.to.asChar ?: '\u2022'
labels[change.vec].restyle()
}
}
}
fun move(delta: Vec? = null) {
cursorPos = interp.funge.nextVec(cursorPos, delta ?: cursorDelta)
}
var src: String
get() = labels.joinToString("\n") { row ->
row.dropLastWhile { lbl ->
lbl.char.isWhitespace()
}.joinToString("") { lbl ->
lbl.char.toString()
}
}
set(value) {
val lines = value.lines()
for (row in labels) {
for (lbl in row) {
lbl.char = lines.getOrNull(lbl.pos.y)?.getOrNull(lbl.pos.x) ?: ' '
}
}
}
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].char = ch
move()
}
ch == '\u0008' -> { // backspace
move(-cursorDelta)
labels[cursorPos].char = ' '
}
else -> {
println("'$ch' (${ch.toInt()})")
}
}
}
}
}
}

View File

@@ -1,39 +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)
override val root = borderpane {
top { add(actionView) }
center { add(codeView) }
right { add(stackView) }
bottom { add(ioView) }
}
init {
codeView.src = """ 2>:3g" "-!v\ g30 <
|!`"O":+1_:.:03p>03g+:"O"`|
@ ^ p3\" ":<
2 234567890123456789012345678901234567890123456789012345678901234567890123456789"""
// codeView.src = """"hello world",,,,,,,,,,,@"""
}
override fun onDock() {
super.onDock()
codeView.root.requestFocus()
primaryStage.isResizable = false
}
}

View File

@@ -1,53 +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
}
textfield {
onAction = EventHandler {
oldinput += this.text + '\n'
this.text.forEach { c -> interp.stdInput.add(c) }
this.clear()
}
}
}
fun reset() {
output = ""
oldinput = ""
}
}

View File

@@ -7,3 +7,7 @@ class MainApp : App(EditorView::class) {
importStylesheet(resources["style.css"])
}
}
fun main(args: Array<String>) {
launch<MainApp>(args)
}

View File

@@ -1,47 +0,0 @@
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() {
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
}
}
private fun <T> 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 }
}
}
}

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: befide.ide.MainApp

View File

@@ -1,11 +1,12 @@
* {
-fx-font-size: 14;
-fx-font-size: 18;
-fx-font-fill: black;
}
.code, .text-area {
.code, .text-area, .stack-pane {
-fx-font-family: Consolas, monospace;
-fx-text-fill: black;
-fx-font-size: 18;
}
.code-view, .text-area {
@@ -13,7 +14,6 @@
-fx-border-radius: 2px;
-fx-border-width: 1px;
-fx-background-color: #fff;
-fx-graphic: none;
}
.code-num {
@@ -21,17 +21,27 @@
}
.code-funge {
-fx-text-fill: #258135;
-fx-text-fill: #418148;
-fx-font-style: italic;
-fx-font-weight: bold;
}
.code-dir {
-fx-text-fill: #b82893;
-fx-font-weight: bold;
}
.code-str {
-fx-text-fill: #0a5158;
.code-condition {
-fx-text-fill: #ffa235;
}
.code-stop {
-fx-font-weight: bold;
-fx-text-fill: #990208;
}
.code-quote {
-fx-text-fill: #258135;
}
.code-cursor-ip {