Write an interpreter for 2B

12

2

Write an interpreter for 2B

I like David Catt's esoteric language 2B, having memory stored in a tape where each cell is a seperate tape of bytes (the 'subtape'). Write an interpreter for it!

Language Specification

Official specification can be found here. In this specification, " means a number in the range 0-9 (0 is interpreted as 10), and _ means a string of any length. Each cell stores a value in the range 0-255, and overflow/underflow wraps around as it would a BF. (Thanks @MartinBüttner). To convert text to numbers 0-255, use ASCII codes. Because I can find no detail on this, I'm going to say that the tape length should be 255 minimum, but if you know otherwise please edit.

+-------------+----------------------------------------------------------------------------------------------------------------------------------------+
| Instruction |                                                              Description                                                               |
+-------------+----------------------------------------------------------------------------------------------------------------------------------------+
| 0           | Zeroes the current cell and clears the overflow/underflow flag.                                                                        |
| {           | If the current cell is zero, jump to the matching }.                                                                                   |
| }           | A placeholder for the { instruction.                                                                                                   |
| (           | Read a byte from the input stream and place it in the current cell.                                                                    |
| )           | Write the value of the current cell to the console.                                                                                    |
| x           | Store the value of the current cell in a temporary register.                                                                           |
| o           | Write the value of the temporary register to the console.                                                                              |
| !           | If the last addition overflowed, add one to the current cell. If the last subtraction underflowed, subtract one from the current cell. |
| ?           | Performs a binary NOT on the current cell.                                                                                             |
| +"          | Adds an amount to the current cell.                                                                                                    |
| -"          | Subtracts an amount from the current cell.                                                                                             |
| ^"          | Moves the subtape up a number of times.                                                                                                |
| V"          | Moves the subtape down a number of times.                                                                                              |
| <"          | Moves the tape left a number of times.                                                                                                 |
| >"          | Moves the tape right a number of times.                                                                                                |
| :_:         | Defines a label of name _.                                                                                                             |
| *_*         | Jumps to a label of name _.                                                                                                            |
| ~_~         | Defines a function of name _.                                                                                                          |
| @_@         | Calls a function of name _.                                                                                                            |
| %           | Ends a function definition.                                                                                                            |
| #_#         | Is a comment.                                                                                                                          |
| [SPACE]     | Is an NOP.                                                                                                                             |
| [NEWLINE]   | Is treated as whitespace and removed.                                                                                                  |
| [TAB]       | Is treated as whitespace and removed.                                                                                                  |
+-------------+----------------------------------------------------------------------------------------------------------------------------------------+

Tests

+0+0+0+0+0+0+0+2)+0+0+9)+7))+3)-0-0-0-0-0-0-0-9)+0+0+0+0+0+0+0+0+7)-8)+3)-6)-8)-7-0-0-0-0-0-0)

Should output Hello world!


+1:i:{()*i*}

Sort of a cat program, just without a newline.


+1:loop:{@ReadChar@*loop*}@PrintHello@@WriteAll@(~ReadChar~(x-0-3<2o^1>1+1>1%~PrintHello~+0+0+0+0+0+0+0+2)-1+0+0+0)+7))+3)+1-0-0-0-0-0-0-0-0)%~WriteAll~<1x:reverse:{<1v1>1-1*reverse*}o-1:print:{-1<1)^1>1*print*}%

Should first accept a name, then, on press of Return, should output Hello name (where name is what was inputted).

Credit for that program goes to David Catt.


I'm working on a full test program.

Rules

  • Standard loopholes are forbidden
  • Your interpreter must meet all the specifications, except for comments, which are not required.

Scoring

  • This is , so fewest bytes wins!
  • -10 bytes if your interpreter handles comments.

Leaderboard

Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

# Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

# Ruby, <s>104</s> <s>101</s> 96 bytes

var QUESTION_ID=67123;function answersUrl(e){return"http://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),e.has_more?getAnswers():process()}})}function shouldHaveHeading(e){var a=!1,r=e.body_markdown.split("\n");try{a|=/^#/.test(e.body_markdown),a|=["-","="].indexOf(r[1][0])>-1,a&=LANGUAGE_REG.test(e.body_markdown)}catch(n){}return a}function shouldHaveScore(e){var a=!1;try{a|=SIZE_REG.test(e.body_markdown.split("\n")[0])}catch(r){}return a}function getAuthorName(e){return e.owner.display_name}function process(){answers=answers.filter(shouldHaveScore).filter(shouldHaveHeading),answers.sort(function(e,a){var r=+(e.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0],n=+(a.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0];return r-n});var e={},a=1,r=null,n=1;answers.forEach(function(s){var t=s.body_markdown.split("\n")[0],o=jQuery("#answer-template").html(),l=(t.match(NUMBER_REG)[0],(t.match(SIZE_REG)||[0])[0]),c=t.match(LANGUAGE_REG)[1],i=getAuthorName(s);l!=r&&(n=a),r=l,++a,o=o.replace("{{PLACE}}",n+".").replace("{{NAME}}",i).replace("{{LANGUAGE}}",c).replace("{{SIZE}}",l).replace("{{LINK}}",s.share_link),o=jQuery(o),jQuery("#answers").append(o),e[c]=e[c]||{lang:c,user:i,size:l,link:s.share_link}});var s=[];for(var t in e)e.hasOwnProperty(t)&&s.push(e[t]);s.sort(function(e,a){return e.lang>a.lang?1:e.lang<a.lang?-1:0});for(var o=0;o<s.length;++o){var l=jQuery("#language-template").html(),t=s[o];l=l.replace("{{LANGUAGE}}",t.lang).replace("{{NAME}}",t.user).replace("{{SIZE}}",t.size).replace("{{LINK}}",t.link),l=jQuery(l),jQuery("#languages").append(l)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",answers=[],page=1;getAnswers();var SIZE_REG=/\d+(?=[^\d&]*(?:&lt;(?:s&gt;[^&]*&lt;\/s&gt;|[^&]+&gt;)[^\d&]*)*$)/,NUMBER_REG=/\d+/,LANGUAGE_REG=/^#*\s*([^,]+)/;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"><div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table></div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table></div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table><table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table>

JimBobOH

Posted 2015-12-19T08:31:43.923

Reputation: 223

4Great idea! Here some thoughts: Please define the number of cells per subtape, and the number of subtapes we should use in our implementations (or specify whether it should be some sort of adaptive/infinite). How should an input string be converted to numbers 0-255? ASCII codes perhaps? – flawr – 2015-12-19T11:22:12.450

Answers

4

Python2, 748 736 731 709 704 691 bytes

This was a fun little challenge, I am sure I can make this code even shorter (maybe I'll do that at some point later).

from sys import*
w=stdout.write
p=open(argv[1],'r').read()
c,r,s,x,y,t,o=0,0,256,0,0,0,0
g=[[0]*s]*s
e=lambda d:p.find(d,c+1)
def h(i,j=0,k=0):global c,x,y;c+=1;n=1+(int(p[c])-1)%10;l=g[x][y]+n*i;g[x][y]=l%s;o=l/s;x=(x+n*j)%s;y=(y+n*k)%s
a="g[x][y]"
b="[p[c+1:i]]"
l={}
f={}
d={'0':a+"=0",'{':"if "+a+"<1:c=e('}')",'(':"i=stdin.read(1);"+a+"=ord(i)if i else 0",')':"w(chr("+a+"))",'x':"t="+a,'o':"w(chr(t))",'!':a+"+=o",'?':a+"=0if "+a+"else 1",'+':"h(1)",'-':"h(-1)",'^':"h(0,1)",'V':"h(0,-1)",'<':"h(0,0,-1)",'>':"h(0,0,1)",':':"i=e(':');l"+b+"=i;c=i",'*':"i=e('*');c=l"+b,'~':"i=e('~');f"+b+"=i;c=e('%')",'@':"i=e('@');r=i;c=f"+b,'%':"c=r"}
while c<len(p):
    if p[c]in d:exec d[p[c]]
    c+=1

This implementation requires for labels and functions to be declared (implemented) before being called. It works perfectly with the two tests given but it unfortunately doesn't work with the "SayHi.2b" program written by the author of the language (even after changing the order of declaration of the functions). I think this problem might have to do with the way I understood the tape and subtape system. When moving along the main tape, is the position on the corresponding subtape reset to 0? At the moment I am keeping the position on the subtape even when moving on the main tape.

Here is the more readable version:

#!/usr/bin/python

import sys
w=sys.stdout.write
p=open(sys.argv[1],'r').read()
c,r,s,x,y,t,o=0,0,256,0,0,0,0
# c is the current index in the program string
# r is the return index (for functions)
# s is the size of the tape, subtapes and modulo for ints (max int will be 255)
# x and y are the coordinates in the grid
# t is the temporary register
# o is overflow
g=[[0]*s]*s # initialise a grid 256x256 with 0

e=lambda d:p.find(d,c+1)
def n():global c;c+=1;i=int(p[c]);return i if i>0 else 10 # get the number specified
def h(i):j=g[x][y]+i;g[x][y]=j%s;o=j/s # handle addition and substraction
def m(i,j):global x,y;x=(x+i)%s;y=(y+j)%s # move current cell

a="g[x][y]" # string of current cell
b="[p[c+1:i]]" # key for label or function
l={} # dictionary of labels
f={} # dictionary of functions
d={'0':a+"=0",
   '{':"if "+a+"<1:c=e('}')",
   '(':"i=sys.stdin.read(1);"+a+"=ord(i)if i else 0",
   ')':"w(chr("+a+"))",
   'x':"t="+a,
   'o':"w(chr(t))",
   '!':a+"+=o",
   '?':a+"=0if "+a+"else 1",
   '+':"h(n())",
   '-':"h(-n())",
   '^':"m(n(),0)",
   'V':"m(-n(),0)",
   '<':"m(0,-n())",
   '>':"m(0,n())",
   ':':"i=e(':');l"+b+"=i;c=i",
   '*':"i=e('*');c=l"+b,
   '~':"i=e('~');f"+b+"=i;c=e('%')",
   '@':"i=e('@');r=i;c=f"+b,
   '%':"c=r",
   '#':"c=e('#')"
   }

while c<len(p): # loop while c is not EOF
    # print c, p[c]
    if p[c]in d:exec d[p[c]] # execute code kept as a string
    c+=1 # increment index

Edit: Take into account the comments handling (-10 bytes), fixing an off by one error. This implementation doesn't support nested function calls (I could implement it if it is a required feature)

Edit2: Changed the handler function to do addition, substraction and cell movement. More lambdas! :D (The "more readable version" may be out of sync now)

Edit3: I just realized that handling comments costs me 5 bytes (taking into account the -10). So I just removed it, it's shame it now feels incomplete.

Edit4: moved definition of n from lambda to var inside the handler h()

basile-henry

Posted 2015-12-19T08:31:43.923

Reputation: 381

with so many +a+ might it be better to join by a? It would also get rid of you having to assign it to a var. – Maltysen – 2015-12-19T18:20:32.240

Well except I can't join the strings in a dictionary as a whole and it wouldn't be worth doing so for each string separately. Assigning the string to a is just a trick to gain some bytes nothing actually useful for the code. – basile-henry – 2015-12-19T18:23:32.447

I guess I can't complain about the order of functions, as I didn't actually specify that, but try to get the SayHi.2b file working. What happens if it is changed to reset the subtape to zero on a shift? – JimBobOH – 2015-12-21T08:33:13.290