Scala, 3,123 2,844 2,626 2,540 bytes
In addition to the constraints outlined in the question, this interpreter was written to lean as much as possible towards FP principles. Specifically:
- Only immutable stuctures
- All functions are pure
This was accomplished, with the exception of the four lines of code that drive the main loop of the interpreter. Immutable structures were very difficult to utilize here because the state of the registers drives the control flow of the loop (specifically the two GOTO statements). I am still thinking on how to convert it to use pure & immutable structures, but that is irrelevant to the code golf challenge.
import java.util.Scanner
import scala.util._
object Z extends App{type K=Long
type G=List[K]
type I=String
type E=Boolean
val(f,t,ф,д,б)=(false,true,(z:G)=>z(0),(z:G)=>z.tail,(q:K,w:K,e:K)=>q::w::e::Nil)
trait O{def h(z:I)=BigInt(z,16).longValue()}
trait S extends O
trait B extends S with P{def apply(r:G):E}
trait R extends O{def a(r:G):G}
trait T extends O{def a(r:G,s:G):(G,G)}
trait P{def p:I}
case class L(p:I)extends S with P
case class U(p:I)extends B{def apply(r:G):E=r(2)==0}
case class J(p:I)extends B{def apply(r:G):E=r(2)!=0}
case class M(p:I)extends R with P{def a(r:G):G=h(p)::д(r)}
class Y extends R{def a(r:G):G=б(r(0),r(0)%r(1),r(0)/r(1))}
case class Q(p:I)extends T with P{def r(q:G,i:Int):G={val s=д(q):+ф(q)
if(i>0)r(s,i-1)else s}
def a(e:G,t:G)=e->r(t,Try(p.toInt).getOrElse(1))}
case class N(p:I)extends T with P{def r(q:G,i:Int):G={
val s=q.last::q.iterator.sliding(2).map(_(0)).toList
if(i>0)r(s,i-1)else s}
def a(e:G,t:G)=e->r(t,Try(p.toInt).getOrElse(1))}
case class A(n:Array[O], l:Map[I,Int]){def e={var (r,t,x)=(List(0L,0,0),List[K](),0)
while(x<n.length){x=n(x)match{case i:B=>if(i(r))l(i.p)else x+1
case i:R=>r=i.a(r);x+1
case i:T=>val(y,u)=i.a(r,t);r=y;t=u;x+1
case _=>x+1}}}}
object A{def apply(i:Seq[O]):A={A(n=i.toArray,l=Map(i.zipWithIndex.flatMap{case(e:L,i)=>Some(e.p->i)
case _=>None}.toList:_*))}}
object X{def v(y:(Char, Option[Z.I]))=y._2.getOrElse("");val F=Map('x->new R{def a(t:G)=б(t(1),t(0),t(2))},'|'->new R{def a(r:G)=h(new Scanner(System.in).next("[0-9a-fA-F]+"))::д(r)},'!'->new R{def a(r:G)=(System.in.read match{case i if i== -1=>0;case i=>i})::д(r)},'%'->new R{def a(r:G)={print(Integer.toHexString(r(2).toInt));r}},'$'->new R{def a(r:G)={print(r(2).toChar);r}},'~'->new R{def a(r:G)=д(r):+ф(r)},'='->new R{def a(r:G)=б(r(2),r(0),r(1))},'?'->new T{def a(r:G,s:G)=r->List()},'>'->new T{def a(r:G,s:G)=r->(r(2)::s)},'{'->new T{def a(r:G, s:G)=(ф(s)::д(r))->д(s)},'+'->new R{def a(r:G)=б(r(0),r(1),r(0)+r(1))},'-'->new R{def a(r:G)=б(r(0),r(1),r(0)-r(1))},'*'->new R{def a(r:G)=б(r(0),r(1),r(0)*r(1))},'/'->new Y);def apply(i:I)={(i+" ").foldLeft((List[(Char,Option[I])](),None:Option[Char],"",f))((a,n)=>{n match{case i if i==':'=>if(a._4)(a._1:+(a._2.get->Some(a._3)),None,"",f)else(a._1,a._2,"",t)
case i if a._4=>(a._1,a._2,a._3+i,t)
case i if a._2.isEmpty=>(a._1,Some(i),"",f)
case i=>(a._1:+(a._2.get->None),Some(i),"",f)}})._1.map(x=>x._1 match{
case'<'=>M(v(x))
case'}'=>L(v(x))
case'^'=>J(v(x))
case'#'=>U(v(x))
case'@'=>Q(v(x))
case'&'=>N(v(x))
case c=>F(c)})}}
A(X(args(0))).e}
I will post the ungolfed version on Github and will provide a link when I do so. For now, I will post the original version here:
import java.util.Scanner
import scala.util.Try
trait Operation {
def hexToLong(hex:String):Long = BigInt(hex, 16).longValue()
}
trait Parameter {
def param:String
}
trait RegisterOperation extends Operation { def apply(registers:List[Long]):List[Long] }
trait StackOperation extends Operation { def apply(registers:List[Long], stack:List[Long]):(List[Long], List[Long]) }
trait SpecialOperation extends Operation
trait SpecialRegisterOperation extends SpecialOperation with Parameter { def apply(registers:List[Long]):Boolean }
class Move(val param:String) extends RegisterOperation with Parameter { override def apply(registers:List[Long]): List[Long] = hexToLong(param) :: registers.tail }
class Swap extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = registers(1) :: registers(0) :: registers(2) :: Nil }
class InputNumber extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = hexToLong(new Scanner(System.in).next("[0-9a-fA-F]+")) :: registers.tail }
class InputAscii extends RegisterOperation {
override def apply(registers:List[Long]): List[Long] = (System.in.read() match {
case i if i == -1 => 0
case i => i}) :: registers.tail }
class PrintNumber extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = { print(Integer.toHexString(registers(2).toInt)); registers } }
class PrintAscii extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = { print(registers(2).toChar); registers } }
class RegisterRollLeft extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = registers.tail :+ registers.head }
class RegisterRollRight extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = registers(2) :: registers(0) :: registers(1) :: Nil }
class Add extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = registers(0) :: registers(1) :: (registers(0) + registers(1)) :: Nil }
class Subtract extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = registers(0) :: registers(1) :: (registers(0) - registers(1)) :: Nil }
class Multiply extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = registers(0) :: registers(1) :: (registers(0) * registers(1)) :: Nil }
class Divide extends RegisterOperation { override def apply(registers:List[Long]): List[Long] = registers(0) :: (registers(0) % registers(1)) :: (registers(0) / registers(1)) :: Nil }
class Clear extends StackOperation { override def apply(registers:List[Long], stack:List[Long]) = registers -> List() }
class Enqueue extends StackOperation { override def apply(registers:List[Long], stack:List[Long]) = registers -> (registers(2) :: stack) }
class Dequeue extends StackOperation { override def apply(registers:List[Long], stack:List[Long]) = (stack.head :: registers.tail) -> stack.tail }
class QueueRollLeft(val param:String) extends StackOperation with Parameter {
def roll(stack:List[Long], i:Int):List[Long] = {
val s = stack.tail :+ stack.head
if (i > 0) roll(s, i-1) else s
}
override def apply(registers:List[Long], stack:List[Long]) = registers -> roll(stack, Try(param.toInt).toOption.getOrElse(1))
}
class QueueRollRight(val param:String) extends StackOperation with Parameter {
def roll(stack:List[Long], i:Int):List[Long] = {
val s = stack.last :: stack.iterator.sliding(2).map(_.head).toList
if (i > 0) roll(s, i-1) else s
}
override def apply(registers:List[Long], stack:List[Long]) = registers -> roll(stack, Try(param.toInt).toOption.getOrElse(1))
}
class SetLabel(val param:String) extends SpecialOperation with Parameter
class JumpLabelIfZero(val param:String) extends SpecialRegisterOperation { override def apply(registers: List[Long]): Boolean = registers(2) == 0 }
class JumpLabelIfNotZero(val param:String) extends SpecialRegisterOperation { override def apply(registers: List[Long]): Boolean = registers(2) != 0 }
class Script(val instructions:Array[Operation],
val labels:Map[String, Int]) {
def execute() = {
var registers = List[Long](0, 0, 0)
var stack = List[Long]()
var idx = 0;
while(idx < instructions.length) {
idx = instructions(idx) match {
case i: SpecialRegisterOperation => if(i(registers)) labels(i.param) else idx + 1
case i: RegisterOperation => { registers = i(registers); idx + 1 }
case i: StackOperation => { val (zregisters, zstack) = i(registers, stack); registers = zregisters; stack = zstack; idx + 1 }
case _ => idx + 1
}
}
}
}
object Script {
def apply(instructions: Seq[Operation]):Script = {
new Script(instructions = instructions.toArray, labels = Map(instructions.zipWithIndex.flatMap {
case (e:SetLabel, i) => Some(e.param -> i)
case _ => None
}.toList:_*))
}
}
object Parser {
def apply(input:String): Seq[Operation] = {
case class Accumulator(val list: List[(Char, Option[String])] = List(), val char:Option[Char] = None, val str:String = "", val parsingVar:Boolean = false)
(input + " ").foldLeft(Accumulator())((acc, next) => {
next match {
case i if i == ':' => if(acc.parsingVar) Accumulator(acc.list :+ (acc.char.get -> Some(acc.str)), None, "", false) else Accumulator(acc.list, acc.char, "", true)
case i if acc.parsingVar => Accumulator(acc.list, acc.char, acc.str + i, true)
case i if !acc.char.isDefined => Accumulator(acc.list, Some(i), "", false)
case i => Accumulator(acc.list :+ (acc.char.get -> None), Some(i), "", false)
}
}).list.map(x => x._1 match {
case '<' => new Move(x._2.getOrElse(""))
case 'x' => new Swap
case '}' => new SetLabel(x._2.getOrElse(""))
case '|' => new InputNumber
case '!' => new InputAscii
case '%' => new PrintNumber
case '$' => new PrintAscii
case '~' => new RegisterRollLeft
case '=' => new RegisterRollRight
case '^' => new JumpLabelIfNotZero(x._2.getOrElse(""))
case '#' => new JumpLabelIfZero(x._2.getOrElse(""))
case '?' => new Clear
case '>' => new Enqueue
case '{' => new Dequeue
case '@' => new QueueRollLeft(x._2.getOrElse(""))
case '&' => new QueueRollRight(x._2.getOrElse(""))
case '+' => new Add
case '-' => new Subtract
case '*' => new Multiply
case '/' => new Divide
})
}
}
object Go extends App {
Script(Parser(args(0))).execute()
}
3What's the distinction between
~
and~
in your "roll registers" commands? Do you have an example program that people can use to test? – AdmBorkBork – 2015-12-07T18:54:40.3603Any examples you could give us? – Conor O'Brien – 2015-12-07T18:59:21.357
!
reads from STDIN, but how to write STDOUT? – cat – 2015-12-07T22:39:20.413the creator's 0815 page is up. – ev3commander – 2015-12-08T01:30:31.200
@ev3commander The page describing the language is working, but the link to the interpreter links to a zip file that returns an Error 404. If you're curious, here's a direct link.
– DanTheMan – 2015-12-08T01:51:32.637Ohh. Whoops.. I misread. – ev3commander – 2015-12-08T01:52:12.267
@MickyT Sorry about leaving out the print commands. I will correct that immediately. – DanTheMan – 2015-12-08T01:52:27.973
1@DanTheMan I am seeing "Hello, Wlrod" when I run your Hello World example using my (still-WIP) implementation... Not sure whether that's a bug in my code or the example itself – Ruslan – 2015-12-08T12:10:18.877
2
@DanTheMan Yes, but changing the link gets you the interpreter. :-)
– Sven Writes Code – 2015-12-08T13:56:01.607@Ruslan: the example prints "Hello, World" in my interpreter (you can find it below). – Gabriele D'Antona – 2015-12-08T16:44:57.387
What do you mean by
"Input the the ASCII code of one ASCII character in the input"
? Do you mean enqueue it or setX
to it or both? Some of these instructions are really vague. – usandfriends – 2015-12-08T22:44:13.1601@usandfriends Fixed! – DanTheMan – 2015-12-09T02:21:16.273
@friol That is very weird, because my implementation seems to be passing all the other tests fine. – Ruslan – 2015-12-09T11:36:43.183
@Ruslan: also Tim Pederick's solution (see below) outputs "Hello, World"... Post your solution, and we can see if there's an error in it... – Gabriele D'Antona – 2015-12-09T18:56:22.920
@friol Fixed it! The queue roll left/right was not firing if there was no parameter – Ruslan – 2015-12-10T02:41:39.830
I've found at least one bug in the example programs from the Esolang wiki, so I'm documenting those programs and providing corrected versions in the same gist where I posted my ungolfed code.
– Tim Pederick – 2015-12-10T13:23:56.643@TimPederick I ran into that issue too, but later noticed this requirement (listed on the creator's website) - "If the label that the jump is pointed to is not found, the program terminates." – Ruslan – 2015-12-10T20:33:51.533
@Ruslan: Yep, and maybe that's the intended behaviour--exit when the user inputs a zero. But zero is even... – Tim Pederick – 2015-12-11T08:09:25.843