Write an interpreter for my esoteric language Jumper

18

2

I have thought up esoteric language Jumper. Later you will see why.

  • It operates with random-access-memory with bytes as cells. RAM is zero indexed and initially filled with zeros.
  • When trying access cells with negative indexes error should be displayed and program terminated.
  • When trying read at larger index than last, zero should be returned.
  • When trying write at larger index than last, RAM should be increased to multiple of 1024 and new cells filled with zeros (technically you can increase RAM not to multiple of 1024, reason was performance boost, so if it costs you a lot of characters you can do it not to multiple of 1024).
  • Program also has pointer to cell in RAM which initially is zero
  • When program starts executing a prompt for input string should be displayed (or take input from command line arguments, it's up to you). Input string should not contain null character (zero byte). Then input string is written to RAM starting at zero index.
  • When program ends executing a box with program output is displayed - contents of RAM from zero index to first zero byte excluding.

Now, most interesting part, syntax.

Program consists of commands (unary operators-prefixes) and their arguments. Commands and arguments may be delimited with spaces or new lines but not necessary. However spaces inside arguments are invalid, for example, # 2 = 4 is valid, but # 2 = 4 4 is not.
Program can have comments between (). Comments can't be nested - for example, in (abc(def)ghi) comment is (abc(def). Comments can be placed anywhere.

  • #123 sets RAM pointer to 123 (any positive decimal integer or zero).
  • >123 increments RAM pointer by 123 (any positive decimal integer).
  • <123 decrements RAM pointer by 123 (any positive decimal integer).
  • =123 writes 123 (any unsigned 8-bit decimal integer) in current cell.
  • +123 adds 123 (any unsigned 8-bit decimal integer) to current cell (modulo 256).
  • -123 subtracts 123 (any unsigned 8-bit decimal integer) from current cell (modulo 256).
  • :123 - "goto" - goes to command number 123 (first is 0). You can control flow of your program only with goto's - it has to jump - that's why I decided to call this language Jumper.

If argument is missing - think of it is 1 for ><+- commands or 0 for #=: commands.

Also, there is command modifier - ? (prefix to command), it executes next command only if current cell is not zero, else skips that command. Can be applied to any command.
For example, ?:17 - goes to command 17 if current cell is not zero.

If program is invalid or error occurs during runtime can be displayed message "Error". Because of this is CodeGolf, such short message will be fine.

Your task

Write shortest interpreter for this language.

Some test programs

(prints "Hello world!" regardless of input)
=72>=101>=108>=108>=111>=32>=119>=111>=114>=108>=100>=33>=

(appends "!" to the end of input string)
?:2 :4 >1 :0 =33 >1 =0

Somnium

Posted 2014-07-24T16:34:19.733

Reputation: 2 537

How is "Aborted" or "Aborted (core dumped)"? – S.S. Anne – 2020-02-05T00:50:52.587

I will write some test programs in Jumper after some time and own interpreter. – Somnium – 2014-07-24T16:55:36.463

"to first zero byte excluding" So if there still come other bytes after the first zero byte, we shouldn't output them? – ProgramFOX – 2014-07-24T17:15:51.550

Yes, we shouldn't. That is done to not to clear whole used memory, but only copy output to beginning. – Somnium – 2014-07-24T17:24:11.943

5Could you give us some sample programs and their outputs? – arshajii – 2014-07-24T17:47:41.053

@arshajii Some test programs added. Will be more. It's a bit hard to think in Jumper but fun) – Somnium – 2014-07-25T08:48:34.280

Simple language, thus fun challenge. Thanks! – seequ – 2014-07-25T20:26:25.800

May comments be placed between the operator and its argument? – Oberon – 2014-07-25T22:19:27.013

@Oberon comments may be placed anywhere. – Somnium – 2014-07-25T22:32:29.123

@user2992539 Darn, that breaks my interpreter. I thought comments would count as whitespace. Will fix later. – seequ – 2014-07-26T12:55:04.377

1Could you specify the exact error message for negative indices? I think the difference between the two currently leading answers is less than the difference in their error messages, so I think it would be fairer if this was precisely specified. – Martin Ender – 2014-07-26T13:16:07.460

Also, what about invalid arguments for the go-to command? And can the program crash on any invalid syntax? And could you please add test cases which use the remaining commands as well (and also one where the RAM size increases)? – Martin Ender – 2014-07-26T20:49:14.167

If the input is invalid syntax, what should happen? – soktinpk – 2014-07-26T22:25:20.863

You should leave comments on the answers that don't comply with your changed spec regarding invalid syntax (for instance, that'll cost my submission at least 40 characters, I think) – Martin Ender – 2014-07-27T19:32:48.000

Q 1 - What constitutes "program ends executing"? Assumption 1 - Terminate any time the program counter goes out of range. Q 2 - Should post-termination printing add a newline? Assuption 2 - No. – Scott Leadley – 2014-07-28T02:05:00.480

@ScottLeadley Q1 - I mean successfully ends executing - after last command is executed without error. If : command send pointer out of range - it is error. Q2 - it's not necessary, but if it doesn't cost you bytes it's preferable. – Somnium – 2014-07-28T07:00:55.170

It is definitely more difficult to write something useful in Jumper than in Brainfuck. A most devious language. – Fors – 2014-07-28T16:25:05.157

The 2nd chunk of code in my submission is a test harness. Most submissions (except mine, of course :-) fail. If you care to point out blatantly wrong test results {comment at my post so we don't clutter the OP comment thread}, I'll fix them if I have time. Don't pester me over the newline vs. no-newline suffixing. You can change that yourself. – Scott Leadley – 2014-07-28T17:23:04.443

@user2992539 After rereading your answer to Q1, I believe you're saying the interpreter should display "Error" unless a program falls off the end by advancing the program counter by 1. Jumping to the same address, end-of-program+1, is an error. Is this correct? If so, I'm going to need to make some changes. – Scott Leadley – 2014-07-30T23:48:02.060

Feature request for Jumper 2.0: The token * may be used as an argument (in place of a number). The contents of the current cell are used as the argument value. – aschepler – 2014-07-31T00:11:56.690

Jumper is an elegantly minimal toy. A more modern Turing machine. Although :* and #* would give Jumper a useful subroutine mechanism and an accumulator would be nice, I think adding more to Jumper would detract from it's value as a teaching tool. If you're looking for realism, there are plenty of PDP-8, 4040, 6502, 680x and 808x emulators available to play with. – Scott Leadley – 2014-07-31T02:03:51.340

@user2992539 As further exploration of your answer to Q1, I believe you're saying that "#" exits successfully and ":1" throws an error. – Scott Leadley – 2014-07-31T02:07:23.327

@ScottLeadley end-of-program+1 I thought to be an error. If # and :1 you mean as complete program, then you are right. – Somnium – 2014-07-31T08:29:16.367

Upon reflection, Jumper 2.0 with more features seems like a good idea. Incrementally connecting the mathematical model to more realistic systems has a lot of pedagogical value. – Scott Leadley – 2014-08-01T02:55:27.687

@user2992539 Should the interpreter throw an error as soon as the RAM pointer goes negative (a underflow/wrap-around condition with real hardware), or only when a =+-?, i.e. memory load/store operation, occurs? Assumption, I've implemented it as throwing an error as soon as the RAM pointer goes negative. See the test suite attached to my solution for more details. – Scott Leadley – 2014-08-01T03:15:09.603

Answers

6

Ruby, 447 bytes

p,i=$*
l=(i||'').length
r=[0]*l
l.times{|j|r[j]=i[j].ord}
i=j=0
s=p.gsub(/\(.*?\)|\s/,'')
q=s.scan(/(\?)?([#<>=+:-])(\d*)/)
e=->{abort"Error"}
p[/\d\s+\d/]||q*''!=s ?e[]:(r+=[0]until i+1<r.length
c=q[j]
j+=1
f=c[1]
c[0]&&r[i]==0?next: a=c[2]==''? '><+-'[f]?1:0:c[2].to_i
'=+-'[f]&&a>255?e[]: f==?#?i=a :f==?>?i+=a :f==?<?i-=a :f==?=?r[i]=a :f==?+?r[i]+=a :f==?-?r[i]-=a :j=a
i<0?e[]:r[i]%=256)while j<q.length
puts r.first(r.index 0).map(&:chr)*''

Takes both the program and the input via command line arguments.

EDIT: Fixed a few bugs, and added support for invalid syntax at the cost of 40 bytes (while adding a few other optimisations).

Martin Ender

Posted 2014-07-24T16:34:19.733

Reputation: 184 808

Too funny. My Ruby solution just weighed in at 447 characters also. Although I was shooting for mid-400s, exactly the same byte count was a suprise. – Scott Leadley – 2014-08-01T02:47:49.337

@ScottLeadley Ha, that's an interesting tie. ^^ I'd give you an upvote if I hadn't done so already. ;) – Martin Ender – 2014-08-01T09:25:12.840

5

Python (729)

import re,sys
R,p,i,T,q,g=[0]*1024,0,0,re.findall(r'\d+|[()#><=+:?-]',sys.argv[1]),lambda i:0<=i<len(T),lambda i,d:int(T[i])if q(i)and T[i].isdigit()else d
def z(p):
 global R;assert p>=0
 if p>=len(R):R+=[0]*1024
s=sys.argv[2]
R[0:len(s)]=map(ord,s)
while i<len(T):
 t=T[i]
 if t=='(': 
  while T[i]!=')':i+=1
  i+=1
  if not q(i):break
  t=T[i]
 i+=1
 if t=='#':p=g(i,0)
 if t=='>':p+=g(i,1)
 if t=='<':p-=g(i,1)
 if t=='=':z(p);R[p]=g(i,0)
 if t=='+':z(p);R[p]+=g(i,1);R[p]%=256
 if t=='-':z(p);R[p]-=g(i,1);R[p]%=256
 if t==':':
  v=int(T[i])
  i,c=-1,-1
  while c!=v:i+=1;c+=T[i]in'#><=+-:'
 if t=='?':
  assert p>=0
  if p<len(R)and R[p]==0:i+=1
 i+=q(i)and T[i].isdigit()
print''.join(chr(int(c))for c in R).split('\0')[0]

As for running the program:

  • 1st argument: Jumper code
  • 2nd argument: Initialization string

Example:

$ python jumper.py "=97>>(this is a comment)=98>2=99#" "xyz123"
ayb1c3

There are probably a few things I overlooked, so please leave a comment if you happen to try something that should work but doesn't. Note that this is written in Python 2.x code.

arshajii

Posted 2014-07-24T16:34:19.733

Reputation: 2 142

How do you give it input? – Claudiu – 2014-07-24T18:34:19.097

@Claudiu See the example. It's a command-line argument. – arshajii – 2014-07-24T18:36:34.483

That's the program. But look at the 7th bullet point. You should be able to initialize the initial RAM array to, say, "hello" via stdin or an argument – Claudiu – 2014-07-24T18:47:27.817

My mistake, it's the 6th bullet: "When program starts executing a prompt for input string should be displayed (or take input from command line arguments, it's up to you). Input string should not contain null character (zero byte). Then input string is written to RAM starting at zero index." – Claudiu – 2014-07-24T18:52:11.803

@Claudiu Ah, I knew I overlooked something, thanks. Now, upon running the program, you can input said string. – arshajii – 2014-07-24T18:56:55.543

@Claudiu On second thought, I've made the initialization string a command-line parameter. – arshajii – 2014-07-24T19:03:58.740

I assume you're using python 2. Python 3 throws a parse error. – Scott Leadley – 2014-07-28T16:58:13.963

@ScottLeadley Yes ("Note that this is written in Python 2.x code"). – arshajii – 2014-07-28T16:58:59.523

4

Ruby 2 - 540 447 420 characters

Run as " ruby2.0 jumper.rb 'instructions' 'initialization data' ". 1.x Ruby won't work (no String.bytes method).


Added multi-line commands and comments and improved my putting.


i=$*[0].gsub(/\([^)]*\)/m,' ').scan(/(\??)\s*([#=:><+-])\s*(\d*)/m).map{|a|[a[0]!='?',a[1],a[2]==''?/[#=:]/=~a[1]?0:1:a[2].to_i]}
N=i.size
d=$*[1].bytes
r=p=0
while p<N
u,o,x=i[p]
p+=1
d[r]=0 if d[r].nil?
case o
when'#';r=x
when'>';r+=x
when'<';r-=x
when/[=+-]/;eval "d[r]#{o.tr'=',''}=x";d[r]%=256
when':';p=x;abort'Error'if p>=N
end if u||d[r]>0
abort'Error'if r<0
end
printf"%s\n",d.take_while{|v|v&&v!=0}.pack('C*')

Here's a test suite with some scatter-shot tests. The easiest way to use it is to stuff the code into t/jumper.t and run "perl t/jumper.t".


#/usr/bin/perl
use strict;
use warnings;
#       timestamp: 2014 August 3, 19:00
#
# - Assume program takes machine code and initialization string as command
#       line options.
# - Assume all required errors reported as "Error\n".
# - Go with the flow and suffix output with \n. Merged terminal newlines are
#       unacceptable [I'm talkin' to YOU Ruby puts()!].
# - As per OP - jumping to > end-of-program must be an error.

use Test::More qw(no_plan);
# use Test::More tests => 4;

my $jumper = "jumper.rb";
#
#       "happy" path
#
# starter tests provided by OP
is( `$jumper '=72>=101>=108>=108>=111>=32>=119>=111>=114>=108>=100>=33>=' '' 2>&1`, "Hello world!\n", "hello world (from user2992539)");
is( `$jumper '?:2 :4 >1 :0 =33 >1 =0' 'a' 2>&1`, "a!\n", 'append !, #1 (from user2992539)');

# simple variations
is( `$jumper '?:2 :4 >1 :0 =33 >1 =0' '' 2>&1`, "!\n", 'append !, #2');
is( `$jumper '?:2 :4 >1 :0 =33' '' 2>&1`, "!\n", 'append !, #3, no NUL');

# comment delimiters don't nest
is( `$jumper "(()=" 'oops' 2>&1`, "\n", "() don't nest");
# comments and termination
is( `$jumper '(start with a comment)?(comment w/ trailing sp) # (comment w/ surrounding sp) 1 =98' 'a' 2>&1`, "ab\n", 'walk to exit');
is( `$jumper '(start with a comment)? (comment w/ leading sp)= (comment w/ surrounding sp) 97()' '' 2>&1`, "\n", 'skip to exit');
is( `$jumper '#1=0 (actually two instructions, but it scans well) :5 #=(truncate further if not jumped over)' 'a b' 2>&1`, "Error\n", 'truncate & jump to exit');

# is RAM pointer initialized to 0?
is( `$jumper '-103(g-g) ?:1025(exit) =103 #4=10' 'good' 2>&1`, "good\n\n", 'intial string in right place?');

# TBD, do jumps work?
# TBD, do conditional jumps work?
# jump right to a harder case, copy byte 0 to byte 3 and format, e.g. input="Y" output="Y=>Y"
is( `$jumper '#1=61#2=62#4=0#3=#10=#(11:)?:13:20(13:)#3+#10+#0-:11(20:)#10(21:)?:23:28(23:)#0+#10-:21(28:)#' 'Y' 2>&1`, "Y=>Y\n", 'copy a byte');


# test memory allocation by dropping 255s at increasingly large intervals
is( `$jumper '#16=511 #64=511 #256=511 #1024=511 #4096=511 #16384=511 #65536=511 #262144=511 #1048576=511 #65536-255 (20:)?:23(exit) #=' 'wrong' 2>&1`, "\n", 'test alloc()');

# upcase by subtraction
is( `$jumper '-32' 't' 2>&1`, "T\n", 'upcase via subtraction');
# 2 nested loops to upcase a character, like so: #0=2; do { #0--; #1=16; do { #1--; #2--; } while (#1); } while (#0);
is( `$jumper '#=2 (2:)#- #1=16 (6:)#1- #2- #1?:6 #0?:2 #=32 #1=32' '  t' 2>&1`, "  T\n", 'upcase via loops');
# downcase by addition
is( `$jumper '+32' 'B' 2>&1`, "b\n", 'downcase via addition');
# same thing with a loop, adjusted to walk the plank instead of jumping off it
is( `$jumper '#1 ?:3 :7 -<+ :0 #' 'B ' 2>&1`, "b\n", 'downcase via adder (from  Sieg)');
# base 10 adder with carry
is( `$jumper '#0-48#10=9#11=#5=#0(9:)?:11:22(11:)#10?:14:22(14:)-#11+#5+#0-:9(22:)#0?:110#11(25:)?:27:32(27:)#0+#11-:25(32:)#0+48>-43?:110=43>-48#10=9#11=#2(45:)?:47:58(47:)#10?:50:58(50:)-#11+#5+#2-:45(58:)#2?:110#11(61:)?:63:68(63:)#2+#11-:61(68:)#2+48>-61?:110=61>?:110=32#10=9#11=#5-10(83:)?:85:94(85:)#10?:88:94(88:)-#11+#5-:83(94:)#5?:99#4=49:100(99:)+10(100:)#11(101:)?:103:108(103:)#5+#11-:101(108:)#5+48' '1+1=' 2>&1`, "1+1= 2\n", 'base 10 adder, #1');
is( `$jumper '#0-48#10=9#11=#5=#0(9:)?:11:22(11:)#10?:14:22(14:)-#11+#5+#0-:9(22:)#0?:110#11(25:)?:27:32(27:)#0+#11-:25(32:)#0+48>-43?:110=43>-48#10=9#11=#2(45:)?:47:58(47:)#10?:50:58(50:)-#11+#5+#2-:45(58:)#2?:110#11(61:)?:63:68(63:)#2+#11-:61(68:)#2+48>-61?:110=61>?:110=32#10=9#11=#5-10(83:)?:85:94(85:)#10?:88:94(88:)-#11+#5-:83(94:)#5?:99#4=49:100(99:)+10(100:)#11(101:)?:103:108(103:)#5+#11-:101(108:)#5+48' '9+9=' 2>&1`, "9+9=18\n", 'base 10 adder, #2');

# order of assignment shouldn't affect order of print
is( `$jumper '#1=98 #0=97' '' 2>&1`, "ab\n", 'print order != assignment order');

# are chars modulo 256?
is( `$jumper '#10(#10 defaults to 0) +255+(#10 += 256) ?#(skip if #10==0) =' 'good' 2>&1`, "good\n", 'memory values limited to 0<x<255');
# go for the cycle;
is( `$jumper '(0:)+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ (256:)#4=10' 'BCID' 2>&1`, "ACID\n\n", 'cycle character less 1, PC>255');
# same thing with a loop;
is( `$jumper '#4=255(#4 = 255) (2:)#1+(#1++) #4-(#4--) ?:2(loop 255 times) #4=10(#4 = NL)' 'ADID' 2>&1`, "ACID\n\n", 'cycle character less 1, PC>255');


#       Exercise the program counter.
# PC > 255;
is( `$jumper '(0:)= (1:)############################################################################################################################################################################################################################################################### (256:)?:259 (257:)+ (258:):1 (259:)=97#3=10' 'a==' 2>&1`, "a==\n\n", 'program counter range >255');


#
#       "sad" path
#
#       Error checking required by the specification.
#
# simplest test case of PC going out of bounds
is( `$jumper ':2' '' 2>&1`, "Error\n", 'program counter too big by 1');
is( `$jumper ':1024' '' 2>&1`, "Error\n", 'program counter in space');
is( `$jumper ':1073741824' '' 2>&1`, "Error\n", 'program counter in hyperspace');
# try to drive program counter negative, if 32-bit signed integer
is( `$jumper ':2147483648(exit)' 'ridiculous speed' 2>&1`, "Error\n", 'program counter goes negative?, #1');
# try to drive program counter negative, if 64-bit signed integer
is( `$jumper ':9223372036854775808 (exit)' 'ludicrous speed' 2>&1`, "Error\n", 'program counter goes negative?, #2');

# spaces not allowed in operand; error or silently ignore (my choice)
isnt(`$jumper '#= #= #= #= #= +1 4 ' 'aops' 2>&1`, "oops\n", 'do not accept spaces in operands');
# ditto w/ a comment ; error or silently ignore (my choice)
isnt(`$jumper '#= #= #= #= #= +1(not valid)4 ' 'aops' 2>&1`, "oops\n", 'do not accept spaces in operands');

# RAM pointer error-checking; "Error" or "" are OK
isnt( `$jumper '<>=' 'oops' 2>&1 | grep -v Error`, "oops\n", 'unused negative RAM pointer behavior unspecified');
# RAM pointer negative and use it
is( `$jumper '<=' '' 2>&1`, "Error\n", 'cannot use negative RAM pointer, #1');
# check for RAM pointer wrap-around
is( `$jumper '<=' '0123456789' 2>&1`, "Error\n", 'cannot use negative RAM pointer, #2');

# The way I read this
#       "Commands and arguments may be delimited with spaces or new lines but
#       not necessary."
# multi-line commands are legit.
is( `$jumper "#4#?\n=" 'oops' 2>&1`, "\n", 'multi-line commands allowed');

# Multi-line comments would be consistent with multi-line commands, but I can't
# find something I can translate into a "must" or "must not" requirement in
#       "Program can have comments between (). ... Comments can be placed
#       anywhere."
# Until uncertainty resolved, no test case.


#
#       "bad" path
#
#       These tests violate the assumption that the instruction stream is wellll-farmed.
#
# characters not in the language; error or (my choice) silently skip
isnt(`$jumper 'x =' 'oops' 2>&1`, "oops\n", 'opcode discrimination');
# is ? accepted as an operator (vs operation modifier); error or (my choice) silently skip
is(`$jumper '(bad 0, good 0:)??0 (bad 1, good 0:):3 (bad 2, good 1:)#0' '' 2>&1`, "Error\n", '? not accepted as an opcode');

exit 0;

Ungolfed version.


#
#       Turing Machine Mach 2.0.
#       Tape? Tape? We don't need no stinkin' tape! We gots RAM!
#
#       dM = data memory
#       iM = instruction memory
#       pC = program counter
#       rP = RAM pointer
#       u, o, x = current instruction being executed
#
#       N = number of instructions in instruction memory
#

#       instruction decoder
iM = $*[0].gsub(/\([^)]*\)/m,' ').scan(/(\??)\s*([#=:><+-])\s*(\d*)/m).map { |a|
    [
        a[0] != '?',
        a[1],
        (a[2] == '')  ?  (/[#=:]/ =~ a[1] ? 0 : 1)  :  a[2].to_i
    ]
}
pC = 0
N = iM.size

dM = $*[1].bytes
rP = 0

while pC < N do
    #   u, unconditional instruction,   execute if true || (dM[rP] > 0)
    #                                   skip if false && (dM[rP] == 0)
    #   o, operator
    #   x, operand
    (u, o, x) = iM[pC]
    pC += 1
    dM[rP] = 0  if dM[rP].nil?
    if u || (dM[rP] > 0)
        case o
        when '#'
            rP = x
        when '>'
            rP += x
        when '<'
            rP -= x
        when /[=+-]/
            eval "dM[rP]#{o.tr'=',''}=x"
            dM[rP] %= 256
        when ':'
            pC = x
            abort 'Error'  if pC >= N
        end
    end
    abort 'Error'  if rP < 0
end
printf "%s\n", dM.take_while{|v|v&&v!=0}.pack('C*')

A quickie proto-assembler.


#
#       Jumper "assembler" - symbolic goto labels.
#
# what it does:
#       - translates labels/targets into absolute position
#               @label ?:good_exit
#               ...
#               :label
#
#       - a label is [a-zA-Z][a-zA-Z0-9_]*
#       - a target is @label
#       - one special label:
#               - "hyperspace" is last instruction index + 1
#       - strips out user comments
#               - everything from "//" to EOL is stripped
#               - jumper comments are stripped
#       - adds "label" comments of the form "(ddd:)"
# limitations & bugs:
#       - multi-line jumper comments aren't alway handled gracefully
#       - a target not followed by an instruction will reference
#               the previous instruction. this can only happen
#               at the end of the program. recommended idiom to
#               avoid this:
#                       @good_exit #
# what it doesn't do:
#       - TBD, simple error checking
#               - labels defined and not used
#       - TBD, symbolic memory names
#
# Example:
#
#   input -
#       (
#               adder from Sieg
#       )
#       @loop_head # 1  // while (*(1)) {
#       ?:continue
#       :good_exit
#
#       @continue -     //     *(1) -= 1;
#       <-           //     *(0) += 1;
#       +
#       :loop_head      // }
#       @good_exit #
#
#   output -
#       (0:) #1 ?:3 :7 (3:) - < + :0 (7:)#

rawSource = ARGF.map do |line|
  line.gsub(/\([^)]*\)/, ' ')   # eat intra-line jumper comments
    .gsub(/\/\/.*/, ' ')        # eat C99 comments
    .gsub(/^/, "#{$<.filename}@#{$<.file.lineno}\n") # add line ID
end.join
rawSource.gsub! /\([^)]*\)/m, '' # eat multi-line jumper comments
#
# Using example from above
#
# rawSource =
#       "sieg.ja@1\n \n" +
#       "sieg.ja@4\n@loop_head # 1\n"
#       ...
#       "sieg.ja@12\n@good_exit # \n"

instructionPattern = %r{
    (?<label> [[:alpha:]]\w* ){0}
    (?<operator> \??\s*[#=:><+-]) {0}
    (?<operand> \d+|[[:alpha:]]\w* ){0}

    \G\s*(@\g<label>\s*)?(\g<operator>\s*)?(\g<operand>)?
  }x
FAIL = [nil, nil, nil]
instructionOffset = 0
iStream = Array.new
target = Hash.new
targetComment = nil
for a in rawSource.lines.each_slice(2) do
  # only parse non-empty lines
  if /\S/ =~ a[1]
    m = nil
    catch( :parseError ) do
      chopped = a[1]
      while m = instructionPattern.match(chopped)
        if m.captures.eql?(FAIL) || (!m[:operator] && m[:operand])
          m = nil
          throw :parseError
        end
        if m[:label]
          if target.has_key?(m[:label].to_sym)
            printf $stderr, a[0].chomp + ": error: label '#{m[:label]}' is already defined"
            abort a[1]
          end
          target[ m[:label].to_sym ] = instructionOffset
          targetComment = "(#{instructionOffset}:)"
        end
        if m[:operator]
          iStream[instructionOffset] = [
              targetComment,
              m[:operator],
              /\A[[:alpha:]]/.match(m[:operand]) ? m[:operand].to_sym : m[:operand]
            ]
          targetComment = nil
          instructionOffset += 1
        end
        chopped = m.post_match
        if /\A\s*\Z/ =~ chopped
          # nothing parseable left
          break
        end
      end
    end
    if !m
      printf $stderr, a[0].chomp + ": error: parse failure"
      abort a[1]
    end
  end
end

# inject hyperspace label
target[:hyperspace] = instructionOffset

# replace operands that are labels
iStream.each do |instruction|
  if instruction[2]
    if !(/\A\d/ =~ instruction[2]) # its a label
      if target.has_key?(instruction[2])
        instruction[2] = target[instruction[2]]
      else
        abort "error: label '@#{instruction[2]}' is used but not defined"
      end
    end
  end
  puts instruction.join
end

Scott Leadley

Posted 2014-07-24T16:34:19.733

Reputation: 459

2

Clojure - 585 577 bytes

Edit:   I forgot modulo 256, of course
Edit 2: Now supports whitespace and comments anywhere. (585 -> 578)

No special golfing tricks used, because I don't know any for Clojure. The interpreter is purely functional. Comes packed with a nice error message in case of negative RAM address (an error is outputted, but no exceptions or errors are thrown).

(defn j[o i](let[o(re-seq #"\??[#<>=+:-]\d*"(clojure.string/replace o #"\(.*?\)|\s"""))r(loop[c 0 p 0 m(map int i)](if-let[f(nth o c nil)](let[[c p m]((fn r[t](let[f(first t)s(if(next t)(apply str(next t))(case f(\#\=\:)"0"(\>\<\+\-)"1"))v(read-string s)a(nth m p 0)](case f\?(if(=(nth m p 0)0)[c p m](r s))\#[c v m]\>[c(+ p v)m]\<[c(- p v)m]\:[(dec v)p m][c p(assoc(vec(concat m(repeat(- p(count m))0)))p(mod({\+(+ a v)\-(- a v)}f v)256))])))f)](if(< p 0)(str"Negative index "p" caused by "f)(recur(inc c)p m)))m))](if(string? r)r(apply str(map char(take-while #(> % 0)r))))))

Examples:

(j "=72>=101>=108>=108>=111>=32>=119>=111>=114>=108>=100>=33>=" "")
=> "Hello world!"
(j "?:2 :4 >1 :0 =33 >1 =0" "hi there")
=> "hi there!"
(j "#1 ?:3 :7 -<+ :0" "01") ; adder
=> "a"
(j "?:2 :4 >1 :0 =33 <10 =0" "hi there")
=> "Negative index -2 caused by <10"
(j "=72>=101>=108>=108>=111>=3(comment here <100)2>=119>=111>=114>=108>=100>=33>=" "")
=> "Hello world!"

Original slightly ungolfed code:

(defn memory
  ([]
    (vec (repeat 1024 0)))
  ([m i v]
    (assoc (vec (concat m (repeat (- i (+ -1024 (mod i 1024)) (count m)) 0)))
           i v)))

(defn parse [c p m t]
  (let [f (first t)
        s (if-let [v (next t)]
            (apply str v)
            (case f
              (\#\=\:) "0"
              (\>\<\+\-) "1"))
        v (read-string s)
        a (nth m p 0)]
    (case f
      \? (if (= (nth m p 0) 0) [c p m] (parse c p m s))
      \# [c v m]
      \> [c (+ p v) m]
      \< [c (- p v) m]
      \: [(dec v) p m]
      [c p (memory m p (mod ({\+ (+ a v) \- (- a v)} f v) 256))])))

(defn jumper [o i]
  (let [o (re-seq #"\??[#<>=+:-]\d*" (clojure.string/replace o #"\(.*?\)|\s" ""))
        r (loop [c 0
                 p 0
                 m (map int i)]
            (if-let [f (nth o c nil)]
              (let [[c p m] (parse c p m f)]
                (if (< p 0)
                  (str "Negative index " p " caused by " (nth o c))
                  (recur (inc c) p m))) m))]
    (if (string? r)
      r
      (apply str (map char (take-while #(> % 0) r))))))

seequ

Posted 2014-07-24T16:34:19.733

Reputation: 1 714

My program in Jumper which adds a "!" works! It is a bit hard to program in Jumper.. – Somnium – 2014-07-25T21:51:20.350

It's a neat concept you have. – seequ – 2014-07-25T21:56:11.260

@user2992539 I added an example of a simple adder. – seequ – 2014-07-25T22:07:21.763

Generally, it is similar to Brainfuck, however it doesn't have loops, goto's and if's instead and has command parameters. – Somnium – 2014-07-25T22:13:33.550

@user2992539 Figured out that much. – seequ – 2014-07-25T22:15:01.903

@user2992539 Also, you might be interested in the fact that regex /\??[#<>=+\-:]\d*|\(.*?\)/ matches any legal instruction, including comments. – seequ – 2014-07-25T22:19:50.383

I am using similar expression in my Javascript interpreter, only it is not finished yet. – Somnium – 2014-07-25T22:31:50.637

@user2992539 I've got a Javascript interpreter for you :P – tomsmeding – 2014-07-26T12:23:44.120

@user2992539 Fixed. – seequ – 2014-07-26T16:55:30.693

1If you put the - at the end of the character class of your regex, you don't need to escape it. -1 character. – tomsmeding – 2014-07-26T17:16:45.777

@tomsmeding Way ahead of you. – seequ – 2014-07-26T17:17:13.677

@Sieg sorry, didn't notice your ungolfed version is behind. – tomsmeding – 2014-07-26T17:22:58.293

2

CoffeeScript (465)

The first prompt box is for the program and the second prompt box is input. Test it at http://coffeescript.org.

Original:

p=prompt().replace(/\(.*?\)|[\s\n\r]/g,"").match(/\??[^\d]\d*/g) ?[]
y=p[..]
i=prompt()
r=[].map.call i,(c)->c[0].charCodeAt()
n=0
m=[(d)->n=d
(d)->m[5] (r[n]+d)%%256
(d)->p=y[d..]
(d)->m[1] -d
(d)->n-=d
(d)->r[n]=d
(d)->n+=d]
while b=p.shift()
 if b[0]=="?"
  continue unless r[n]
  b=b[1..]
 d="><+-#=:".indexOf(b[0])//4
 ~d||throw "!badcmd '#{b[0]}'"
 m[b[0].charCodeAt()%7](+b[1..]||+!d)
 n<0&&throw "!ramdix<0"
alert String.fromCharCode(r...).replace(/\0.*/,"")

This is still golfed, but commented:

# Get program
p=prompt().replace(/\(.*?\)/g,"").match(/\??[^\s\d]\d*/g) ?[]
# Create a copy of the program (for goto)
y=p[..]
# Get input
i=prompt()
# Put the input in the ram
r=[].map.call i,(c)->c[0].charCodeAt()
# RAM pointer
n=0
# An array of commands
# Since each of "<>+-#=:" is a different
# value mod 7 (what a coincedence?!)
# So 0th value is "#" command because 
# "#".charCodeAt() % 7 === 0
m=[(d)->n=d
(d)->m[5] (r[n]+d)%%256
(d)->p=y[d..]
(d)->m[1] -d
(d)->n-=d
(d)->r[n]=d
(d)->n+=d]
# Iterate through commands
while b=p.shift()
 # If you find a "?" skip unless r[n] is > 0
 if b[0]=="?"
  continue unless r[n]
  b=b[1..]
 # Get the default value
 d="><+-#=:".indexOf(b[0])//4
 # If the command isn't good, throw an error
 throw "!badcmd '#{b[0]}'" if d==-1
 # Call the appropriate command
 # By computing the char code mod 7
 m[b[0].charCodeAt()%7](+b[1..]||+!d)
 # Make sure n is bigger than or equal to 0
 throw "!ramdix<0" if n<0
# Show output
alert String.fromCharCode(r...).replace(/\0.*/,"")

Edit: Adding spaces took more bytes than I thought. This interpreter will throw an error on invalid syntax, the other has unspecified behavior on invalid syntax.

p=prompt().replace(/\(.*?\)/g,"").match(/\??[^\d\s\r\n]\s*\n*\r*\d*/g) ?[]
y=p[..]
i=prompt()
r=[].map.call i,(c)->c[0].charCodeAt()
n=0
m=[(d)->n=d
(d)->m[5] (r[n]+d)%%256
(d)->p=y[d..]
(d)->m[1] -d
(d)->n-=d
(d)->r[n]=d
(d)->n+=d]
while b=p.shift()?.replace /^(.)(\s\r\n)*/,"$1"
 if b[0]=="?"
  continue if !r[n]
  b=b[1..]
 d="><+-#=:".indexOf(b[0])//4
 ~d||throw "!badcmd"
 m[b[0].charCodeAt()%7](+b[1..]||+!d)
 n<0&&throw "!ramdix<0"
alert String.fromCharCode(r...).replace(/\0.*/,"")

soktinpk

Posted 2014-07-24T16:34:19.733

Reputation: 4 080

Apparently this doesn't work for ?:2 :4 >1 :0 = 33 <10 =0 (the append-! program with an extra space) – seequ – 2014-07-26T20:12:15.883

@Sieg It should be <1 not <10. – soktinpk – 2014-07-26T20:41:56.667

@Sieg never mind, I'll update it when I have time – soktinpk – 2014-07-26T21:13:05.467

@Sieg works now, I'll work on golfing it further – soktinpk – 2014-07-26T21:28:15.193

You know, you could have just changed /\(.*?\)/g -> /\(.*?\)|\s/g – seequ – 2014-07-26T22:18:45.120

@Sieg No because you can't replace all spaces. For example, #35 7 shouldn't be the same thing as #357 (based on my interpretation) – soktinpk – 2014-07-26T22:20:10.130

#35 7 would be invalid syntax anyways. – seequ – 2014-07-26T22:20:56.590

@Sieg Yes, so it should throw an error. It shouldn't run successfully. – soktinpk – 2014-07-26T22:21:24.217

1I took the stance of "it was not declared, so it's unspecified behavior", thus I would say it's fine. But again, I'm not the mighty user. – seequ – 2014-07-26T22:23:34.423

Let us continue this discussion in chat.

– soktinpk – 2014-07-26T22:34:57.043

1

Javascript, 519

C=prompt().replace(/\(.*?\)/g,"").match(/\??[#><=+:-]\d*/g)
M=prompt().split("").map(function(c){return c.charCodeAt(0)})
R=0
f=function(I){T=I[0]
A=I.slice(1)
if(T=="?")return M[R]?f(A):P++
A=A==""?1:+A
if(T==">")R+=A
if(T=="<")R-=A
if("=+-".indexOf(T)+1){if(R<0)throw alert("ERR RAMidx<0")
while(R>=M.length)M.push(0)}
if(T=="+")M[R]=M[R]+A&255
if(T=="-")M[R]=M[R]-A&255
A=+I.slice(1)
if(T=="#")R=A
if(T=="=")M[R]=A
if(T==":")P=A;else++P}
for(P=0;C[P];)f(C[P])
alert(String.fromCharCode.apply(7,M).replace(/\0.*/,""))

This gets the program and input via prompt boxes. Pasting this in your browser's Javascript console will work, as well as throwing this in a file, pasting before the code <!DOCTYPE html>, newline, <html><head><script>, and after the code </script></head><body></body></html>, and saving the resulting file as "swagger.html".

This is my first attempt at this thing, and I already like the language. Kinda. It really needs text labels though, instead of this BASIC-style instruction index labeling.

Ungolfed version (kinda):

var C,M,R,P,f;
C=prompt().replace(/\(.*?\)/g,"").match(/\??[#><=+:-]\d*/g); //Code
M=prompt().split("").map(function(c){return c.charCodeAt(0)}); //Memory
R=0; //RAM pointer
f=function(I){ //parser function, Instruction
    var T,A;
    T=I[0]; //Type
    A=I.slice(1); //Argument
    if(T=="?")return M[R]?f(A):P++;
    A=A==""?1:+A;
    if(T==">")R+=A;
    if(T=="<")R-=A;
    if("=+-".indexOf(T)+1){
        if(R<0)throw alert("ERR RAMidx<0");
        while(R>=M.length)M.push(0);
    }
    if(T=="+")M[R]=M[R]+A&255;
    if(T=="-")M[R]=M[R]-A&255;
    A=+I.slice(1);
    if(T=="#")R=A;
    if(T=="=")M[R]=A;
    if(T==":")P=A;else++P;
}
for(P=0;C[P];f(C[P])); //Program pointer
alert(String.fromCharCode.apply(7,M).replace(/\0.*/,""));

tomsmeding

Posted 2014-07-24T16:34:19.733

Reputation: 2 034

If only Clojure's string-replace wouldn't be clojure.string/replace – seequ – 2014-07-26T16:57:50.183

1Also, something I noticed, it doesn't matter at all if you increase the memory to multiples of 1024 or not ;) – seequ – 2014-07-26T18:36:09.680

1I don't think your script can handle ?:2 :4 >1 :0 = 33 <10 =0 (the append-! with an extra space) Not tested though. – seequ – 2014-07-26T20:13:48.110

@Sieg it definitely cannot. Should it? I can just filter out all whitespace if needed... – tomsmeding – 2014-07-26T20:24:40.000

1"Commands and arguments may be delimited with spaces or new lines but not necessary. However spaces inside arguments are invalid" If I understand that correctly, arguments may be preceded by whitespace. @user2992539 Got a word on this? – seequ – 2014-07-26T20:35:25.680

@Sieg In your submission, I read the regex \??[#<>=+:-]\d*, which doesn't allow spaces between oprerator and argument either. – tomsmeding – 2014-07-27T05:51:56.797

I remove them by replacing every \(.*?\)|\s with empty. – seequ – 2014-07-27T09:41:30.690

@tomsmeding If you want symbolic goto labels, check out the proto-assembler in my answer. – Scott Leadley – 2014-08-01T17:45:10.123

Could if(T==">")R+=A;if(T=="<")R-=A; be rewritten R+=T==">"?A:T=="<"?-A:0; etc? Also M[R]+=A&255*T=="+"?1:T=="-"?-1:0; etc. – Will – 2014-09-19T09:32:38.930

@Will it could; will fix later – tomsmeding – 2014-09-19T09:34:15.220

1

C 523

Edits:

  • Thanks to @Dennis it is now much shorter (and works in GCC to boot)
  • Thanks to @ceilingcat for some very nice pieces of golfing - now even shorter

The golfed version

#define M R=realloc(R,r+Q),bzero(R+r,Q),r+=Q
#define J z<0?exit(puts("!")),0:z>r
#define G J?0:R[z]
#define P J?M:0,R[z]
#define O!C[1]?1
#define I if(*C==35
#define W while(*p&&*p<33)p++
Q=1024;char*R,C[999][9],*p,*q,*o;r,z,c,V;E(char*C){I+28)G&&E(C+1);else{V=atoi(C+1);I)z=V;I+27)z+=O:V;I+25)z-=O:V;I+26)P=V;V--;I+8)P=G+O:V;I+10)P=G-O:V;I+23)c=V;}}main(u,v)int**v;{M;for(o=v[u-3||strcpy(R,v[2])];*o;bcopy(p,q,o-p)){p=o;q=C[c++];W;if((*q++=*p++)==63){W;*q++=*p++;}W;strtol(p,&o,0);}for(c=*C[c]=0;*C[c];)E(C[c++]);puts(R);}

Try it online!

A slightly less golfed version of my original program:

#include<stdlib.h>
#include<memory.h>
#include<string.h>
#include<stdio.h>
#define CHAR_STAR char*
#define CASTED_R (CHAR_STAR)RAM
#define UNSIGNED_CHAR unsigned char
#define INCREASE_MEMORY RAM=(UNSIGNED_CHAR*)realloc(CASTED_R,RAM_size+1024),memset(CASTED_R+RAM_size,0,1024),RAM_size+=1024
#define IF_ERROR current<0?exit(puts("!")),0:current>RAM_size?
#define GET_CELL IF_ERROR 0:RAM[current]
#define PUT_CELL(x) IF_ERROR INCREASE_MEMORY,RAM[current]=x:RAM[current]=x;
#define ONE_IF_EMPTY !*(command+1)?1:
#define VALUE atoi(command+1)
#define REMOVE_WHITESPACE while (*pointer&&*pointer<33)pointer++;
#define COPY_CHAR (*command++ = *pointer++)
#define RETURN return
char commands[999][9];
UNSIGNED_CHAR*RAM = 0;
int RAM_size = 0, current = 0, command_size = 0;
CHAR_STAR get_command(CHAR_STAR a)
{
    CHAR_STAR pointer = a, *command = commands[command_size++], *next;
    REMOVE_WHITESPACE
    if (COPY_CHAR == '?')
    {
        REMOVE_WHITESPACE
        COPY_CHAR;
    }
    REMOVE_WHITESPACE
    int i = strtol(pointer, &next, 0);
    memcpy(command, pointer, next - pointer);
    command[next - pointer] = 0;
    RETURN next;
}
void eval(CHAR_STAR command){
    if (*command == '?')RETURN GET_CELL ? eval(command + 1) : 0;
    if (*command == '#')current = VALUE;
    if (*command == '>')current += ONE_IF_EMPTY VALUE;
    if (*command == '<')current -= ONE_IF_EMPTY VALUE;
    if (*command == '=')PUT_CELL(VALUE)
    if (*command == '+')PUT_CELL(GET_CELL + ONE_IF_EMPTY VALUE - 1)
    if (*command == '-')PUT_CELL(GET_CELL - ONE_IF_EMPTY VALUE - 1)
    if (*command == ':')command_size = VALUE - 1;
}
int main(int argc, CHAR_STAR *argv)
{
    INCREASE_MEMORY;
    argc == 3 ? strcpy(CASTED_R, argv[2]) : 0;
    CHAR_STAR command = argv[1];
    while (*command) command = get_command(command);
    *commands[command_size] = 0; command_size = -1;
    while (*commands[++command_size]) eval(commands[command_size]);
    RETURN puts(CASTED_R);
}

Jerry Jeremiah

Posted 2014-07-24T16:34:19.733

Reputation: 1 217

@ceilingcat Thanks very much. I just learned several really good lessons about golfing. Now I need to try to decipher your newer, even shorter version! – Jerry Jeremiah – 2020-02-03T05:20:54.390

error can be abort(). – S.S. Anne – 2020-02-05T00:52:35.923

>

  • I don't know about other compilers, but this won't compile in GCC. This can be fixed by replacing the second R[z]=x in P(x) with (R[z]=x). 2. GCC doesn't require any of the include statements. 3. char C -> U C.
  • < – Dennis – 2014-09-21T15:44:06.450

    @Dennis I was testing it with Visual C++ 2013. I'll test it with GCC tomorrow. – Jerry Jeremiah – 2014-09-22T12:11:34.637

    0

    Perl 5 -F, 345 bytes

    $#F--;$_=<>;s/\(.*?\)|\s*([+<>#=:?-])\s*/$1/g;die'Error'if/[^+<>#=?:0-9-]/+/\?\d/;s/[><+-]\K(?=\D|$)/1/g;s/[#=:]\K(?=\D|$)/0/g;map$_=ord,@F;@c=/.*?\d+/g;while($l<@c){$_=$c[$l++];s/\?//*!$F[$p]&&next;/[=+-]/?$F[$p]=eval"(\$F[\$p]$_)%256":/[<>]/?eval'$p=$p'.y/></+-/r:/#/?$p=y/#//dr:s/://&&($l=$_);die'Error'if$p*$l<0||$l>@c}print$_?chr:last for@F
    

    Try it online!

    The first line of input is the initialization of RAM, the second line is the jumper program.

    Xcali

    Posted 2014-07-24T16:34:19.733

    Reputation: 7 671

    0

    Groovy 582

    ungolfed version:

    I think there is a bug with the comments, which are not recognized correctly, which might be caused by the stupid regex I used, but the 2 programs run as they should:

    class P {
        def c = 0
        def p = 0
        def m = []
    
        P(i="") {
            m = i.chars.collect { it }
            m << 0
        }
    
        def set(v) { m[p] = v }
        def add(v) { m[p] += v }
        def sub(v) { m[p] -= v }
    
        def eval(i) {
            while(c < i.size()) {
                if (i[c].p && m[p] == 0) {c++} 
                else { i[c].f(this,i[c].v) }
            }
            return m
        }
    }
    
    
    def parse(s) {
        def ops = [
           '#' : [{p, v -> p.p = v; p.c++}, "0"],
           '>' : [{p, v -> p.p += v; p.c++}, "1"],
           '<' : [{p, v -> p.p -= v; p.c++}, "1"],
           '=' : [{p, v -> p.set(v); p.c++}, "0"],
           '+' : [{p, v -> p.add(v); p.c++}, "1"],
           '-' : [{p, v -> p.sub(v); p.c++}, "1"],
           ':' : [{p, v -> p.c = v}, "0"]
        ]
    
        (s =~ /\(.*\)/).each {
            s = s.replace(it, "")
        }
    
        (s =~ /(\?)?([#><=+-:])([0-9]*)?/).collect {        
            def op = ops[it[2]]
            [f : op[0], v : Integer.parseInt(it[3] ?: op[1]), p : it[1] != null ]
        }
    }
    

    markusw

    Posted 2014-07-24T16:34:19.733

    Reputation: 121

    0

    Haskell: an ungodly amount of characters

    Alright, right now this is something that might or might not be golfed shortly. It is freakishly huge for what it is, with lots and lots of sloppily written code (it was quite some time since I last touched Haskell). But it was fun to write.

    import Data.Char
    
    parse [] p c a m i =
        if c == ' ' || c == '?' then
            []
        else
            (p ++ [(c, a, m)])
    
    parse (h:t) p c a m i
        | i
            = parse t p c a m (h == ')')
        | isDigit h && a < 0
            = parse t p c (digitToInt h) m i
        | isDigit h
            = parse t p c (10 * a + (digitToInt h)) m i
        | elem h "#><=+-:?"
            = if c == ' ' || c == '?' then
                parse t p h a (c == '?') i
            else
                parse t (p ++ [(c, a, m)]) h (-1) False i
        | otherwise
            = case h of
                '(' -> parse t p c a m True
                ' ' -> parse t p c a m i
                _   -> []
    
    run p pp r rp
        | pp >= length p
            = r
        | pp < 0 || rp < 0
            = []
        | otherwise
            = if mr then
                case c of
                    '#' -> run p (pp + 1) r pa
                    '>' -> run p (pp + 1) r (rp + pa)
                    '<' -> run p (pp + 1) r (rp - pa)
                    '=' -> run p (pp + 1) (rh ++ ((chr pa) : rt)) rp
                    '+' -> run p (pp + 1) (rh ++ (chr (mod ((ord h) + pa) 256) : rt)) rp
                    '-' -> run p (pp + 1) (rh ++ (chr (mod ((ord h) - pa + 256) 256) : rt)) rp
                    ':' -> run p pa r rp
            else
                run p (pp + 1) r rp
            where
                (c, a, m)
                    = p !! pp
                (rh, h:rt)
                    = splitAt rp r
                pa
                    = if a < 0 then
                        if elem c "><+-" then
                            1
                        else
                            0
                    else
                        a
                mr
                    = ord (r !! rp) > 0 || not m
    
    main = do
        p <- getLine
        let n = parse p [] ' ' (-1) False False
        if n == []
            then do
                putStrLn "Error"
            else do
                s <- getLine
                let r = run n 0 (s ++ (repeat (chr 0))) 0
                if r == []
                    then do
                        putStrLn "Error"
                    else do
                        putStrLn (takeWhile (/=(chr 0)) r)
    

    Fors

    Posted 2014-07-24T16:34:19.733

    Reputation: 3 020

    0

    Haskell, 584

    The input and jumper program are provided as the first two lines of input from stdin.

    a g(i,n,x)=(i+1,n,take n x++((g$x!!n)`mod`256):drop(n+1)x)
    b g(i,n,x)=(i+1,g n,x)
    c=b.q:a.(+):g:a.(-):b.(-):a.q:b.(+):c
    d=(%['0'..'9'])
    e=fromEnum
    f=0>1
    g n(_,x,y)=(n,x,y)
    h(x:_)=d x;h _=f
    i g p@(j,n,m)|x$m!!n=g p|t=(j+1,n,m)
    j=0:1:0:1:1:0:1:j
    k=takeWhile
    l[]=[];l(x:y)|x%") \n"=l y|x%"("=l$u(/=')')y|t=x:l y
    main=v>>=(\y->v>>=putStr.map toEnum.k x.r(0,0,map e y++z).p.l)
    o n s|h s=(read$k d s,u d s)|t=(n,s)
    p[]=[];p(x:y)|x%"?"=w$p y|t=(c!!e x)n:p m where(n,m)=o(j!!e x)y
    q=const
    r s@(i,n,m)p|i<length p=r((p!!i)s)p|t=m
    t=0<1
    u=dropWhile
    v=getLine
    w(m:n)=i m:n
    x=(/=0)
    z=0:z
    (%)=elem
    

    I'll post an ungolfed version later, but in the meantime I wanted to leave this as a puzzle for the reader:

    Where are the jumper commands such as '#' etc?

    Have fun!

    Matt Noonan

    Posted 2014-07-24T16:34:19.733

    Reputation: 1 014