Befunge++

Heya folks,

I was building a small interpreter for Befunge earlier, and it worked out quite good. Now I can start adding new features, and I have some good ideas, but of course, new ideas are always great.

So far, I've got these:
- Tunnel mode (or comment mode)
Skips all elements from the first T till the second T you come across.
Example:
>T 0. T@
Wouldn't do anything.

- Move to
Pops y and x and then moves the PC to that location. (useful for functions)
Example:
1
2
3
>90 2 02m@

>2*.m

Firstly 9 is pushed (we use it as an x-coord), then 0 is pushed (we use it as an y-coord). Then 2 is pushed (we use it as function parameter). Finally 0 and 2 are pushed as x and y-coords respectively. The PC then gets moved to [0,2] where it enters the function. 2 is pushed. 2 get multiplied with 2, the output is printed. The PC moves to [9,0] where the program ends.

PC-direction checking:
U (up), D (down), L (left), R (right)
These will push 1 if the PC direction corresponds with the block and 0 otherwise.

Any thoughts or ideas you'd like to share? I will be releasing the source code somewhere too (although it's just some easy code (which I failed on too at some point, thanks Athar ;) ) with lots of if's).
Last edited on
One thing that would be awesome is an ability to call external functions. I know it would be hard to do and it would be near impossible to make most functions work, but just think about the possibility to write real world apps in Befunge.
Just being able to store more than 8 bits per cell would already be a great help.
Writing programs in Befunge is already hard enough without that limitation... this took me quite a while.

v                 v            :                 <
3>5*:1+g: 2 25*:p >25*:g % v >:25*:g 1+: 25*:p `#^_>>>>$v
2^ 2   <                   > |                     >,v  v
>5*:1+p^                     >$    " :emirp toN"  :|:<  v
^  v*2*55:,*52+1.:g+1:*52              <           <    v
   >2*`#@_v                          >!|!:"Prime: "     <
^2        <                          : ,
                                     ^ < 
But it's kind of fun.

>0 25*:p 1 25*:1+p 0 25*:2+p  v
             v<
            v0?    v#         < 
v:*52*g+1:*52<># #1<          ^ 
>2+g+ 25*:2+v>25*:g:1+25*:p6\`|>$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$v
            v^          p+1<  >^v"Guess my number between 0 and 127!"<
            >p25*:1+g2*25*:^    >:#v_25*,            >&:25*:2+g`#v_ v
                                   ,:                ^           v  2
                                   >^                ^           v  5
                                           >:v       ^           v  *
                                           ^,_ >25*, ^           v  >:2+g-v
                                             ^:   "Too small!"   v# <     _v
                                             ^:   "Too large!"  $<         v 
                                            v:_@#:"You guessed it!"        <
                                            >,^

Last edited on
@hamsterman: External functions seem nice (though this is just an interpreter, not a compiler and thus it would be kind of.. hard to implement in a logical way).

@Athar: Nice codes there. On the 8 bits per cell, do you mean that you should be able to store ints in the stack, instead of chars?

Also, I implemented the tunnelmode, move to and direction checking succesfully. :D
On the 8 bits per cell, do you mean that you should be able to store ints in the stack, instead of chars?

Well, I meant the 80x25 code/data pad, I kinda figured the stack would store ints anyway.
There could be P and G that stores and retrieves ints from the pad. p and g would still work with 8 bits.

So... as for external functions, there could be an instruction I that takes two null-terminated strings on the stack (library and function name) and pushes a function handle on the stack. The instruction C could invoke such a function by taking this handle, a null-terminated string that indicates the return type and parameter types and the actual parameters on the stack (for the types, there could be V=void (only return value), I=int, L=64 bit integer (a (high,low) int pair), P=pointer (an (x,y) pair), C=null-terminated string on stack).

A program that opens hello.txt and writes "Hello, World!" could look like this:

>25*26*1+8p                                       030P v
 v"open" 0  _v#!G03  "/lib/x86_64-linux-gnu/libc.so.6"0<
 v"write"0 < >30G1-v   >              >                ^
 v"close"0 ^#   _v#<   ^
 >I30G0Pv  ^     <     ^
        >30G1+:30P 3\` |
                       >88*8*1-88*2+0"txt.olleh" 0"IICI" 00G C 40P v
                                 v $$ C G01 "LIPL"0 G04 80    *72 0<
Hello, World!                    > 40G 0"II" 20G C $@


Somewhat messy, but it works.

Edit: I used some slots as follows:
(0,0)=open function handle
(1,0)=write function handle
(2,0)=close function handle
(3,0)=function loading counter variable
(4,0)=file descriptor returned by open()
There's code in these slots, but it's only executed once in the beginning (it puts a newline after Hello World!), so it's okay to overwrite it.
Last edited on
It must be me, but I still don't really get what you're trying to say on the 8 bits per cell.. :/

The external functions idea seems good, though I think some changes should be made. (I'm assuming that the external functions are going to Befunge sub-programs, I might be missing the point entirely here.) The parameters could just be an amount of n (indicating the amount of top elements from the stack to be passed) in which n needs to be known. The return type would in this context not really matter..
I bet though, that I'm missing the point here and you're talking about C-functions?

I don't have much experience with external function in general though, so if it were to be implemented, I would definitely require some help. ;) A method similar to the external functions might also be used to perform file I/O (this would mean that Befunge programs could actually become practical).

I also forgot to mention that the interpreter reads a file which is convert to a std::vector<string>, meaning that the "Befunge grid" is already more dynamic than the Befunge-93 standard states. Functionally, the size of the grid is: horizontally - the size of the largest string, vertically - the amount of strings. Theoretically, the horizontal size varies depending on the string, but any non-existing spots are simply ignored by checking the string's size. Thus, the 80x25 fixed size is already discarded.
It must be me, but I still don't really get what you're trying to say on the 8 bits per cell.. :/

I just mean that the grid should contain elements of type int. When a cell is executed or retrieved via g, only the lower 8 bit are considered, but when retrieved with G, the whole value is retrieved.

I bet though, that I'm missing the point here and you're talking about C-functions?

Yeah, that what makes this useful, as you can write any sort of program when you can import C functions. I'm pretty sure that is what hamsterman meant. There's probably a need for some adjustments, particularly there needs to be a feasible way to pass structures to functions that need them.

I don't have much experience with external function in general though, so if it were to be implemented, I would definitely require some help. ;)

Loading functions can be done with dlopen/dlsym and LoadLibrary/GetProcAddress, respectively.
Actually calling the functions is a bit more tricky. When dropping 64-bit support (including 64 bit integers), it would be easy, as all parameters would be 32 bit (including pointers). Aside from that possibility, it might be necessary to resort to some assembly to push the parameters on the stack.

Edit: it should be possible to push two 32-bit values in place of a 64-bit value, so the above likely still works. (I forgot that the x86_64 ABI dictates that most parameters are passed in registers, so it's not that easy)
OTOH, since the first parameters each get assigned a full 64-bit register, you can extend all parameters to 64 bit.
It's not very portable, but neither would be doing it via assembly.
Gonna try it when I have some time.
Last edited on
I just added the G functionality and changed the grid to work as a vector< vector< int > >. I'll upload what I have to some site later today. (P wasn't really necessary to add, since p already does this by default.)
I also added the direction checkers U, D, L, R (up, down, left, right, respectively).

Are those functions platform-specific? If so, I'd probably have to do some defining, as I want this code to be cross-platform (for at least Windows and Mac).

I'm not going to get into the specifics right now, as my head will just explode. Tell me what you find when you try that out.
Last edited on
Are those functions platform-specific? If so, I'd probably have to do some defining, as I want this code to be cross-platform (for at least Windows and Mac).

Yeah, dlopen/dlsym is for Mac, Linux, BSD etc. and LoadLibrary/GetProcAddress is for Windows.

As an initial draft, I tested the following on 64-bit Linux (as they use the same ABI, this should work on Mac and BSD too):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
vector<void*> externalFP;
[...]
      case 'I':
      {
        string funcName=s.popCString();
        string lib=s.popCString();
        auto f=getExternalFunction(lib,funcName);
        if (f)
        {
          externalFP.push_back(f);
          s.push(externalFP.size());
        }
        else s.push(0);
        break;
      }
      case 'C':
      {
        uint fhandle=s.pop()-1;
        string params=s.popCString();
        if (params.empty())params="V";

        #ifdef __x86_64__
        typedef uint64_t P;
        #else
          #error "Architecture currently not supported"
        #endif
        vector<P> p;

        if (fhandle>=externalFP.size())
        {
          cout << "Invalid external function called." << endl;
          return 2;
        }
        void* func=externalFP[fhandle];
        for (uint i=1;i<params.length();i++)
        {
          switch (params[i])
          {
            case 'I': p.push_back(s.pop()); break;
            case 'L':
            {
              uint l=s.pop();
              uint h=s.pop();
              p.push_back((uint64_t(h)<<32)|l);
              break;
            }
            case 'P':
            {
              int y=s.pop();
              int x=s.pop();
              void* ptr=&grid(x,y);
              p.push_back((P)ptr);
              break;
            }
            case 'C':
            {
              //todo
              break;
            }
          }
        }

        P ret=0;
        switch(p.size())
        {
          case 0:  ret=reinterpret_cast<P(*)()>(func)(); break;
          case 1:  ret=reinterpret_cast<P(*)(P)>(func)(p[0]); break;
          case 2:  ret=reinterpret_cast<P(*)(P,P)>(func)(p[0],p[1]); break;
          case 3:  ret=reinterpret_cast<P(*)(P,P,P)>(func)(p[0],p[1],p[2]); break;
          case 4:  ret=reinterpret_cast<P(*)(P,P,P,P)>(func)(p[0],p[1],p[2],p[3]); break;
          case 5:  ret=reinterpret_cast<P(*)(P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4]); break;
          case 6:  ret=reinterpret_cast<P(*)(P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5]); break;
          case 7:  ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6]); break;
          case 8:  ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]); break;
          case 9:  ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8]); break;
          case 10: ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9]); break;
          case 11: ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10]); break;
          case 12: ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11]); break;
          case 13: ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12]); break;
          case 14: ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13]); break;
          case 15: ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14]); break;
          case 16: ret=reinterpret_cast<P(*)(P,P,P,P,P,P,P,P,P,P,P,P,P,P,P,P)>(func)(p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],p[13],p[14],p[15]); break;
          default: cout << "External function calls with more than 16 parameters are not supported." << endl; return 1;
        }

        switch (params[0])
        {
          case 'V': break;
          case 'I': s.push(ret); break;
          case 'L': s.push(ret>>32); s.push(ret); break;
        }
        break;
      }


With this, the following program runs successfully (creates and displays a 400x400 window and exits after 10 seconds):
>            T m+1G06*37< T    0:60P  v  <
v  "sleep" 0            ^"libX11.so"0 _v ^
v  "XOpenDisplay" 0     ^"libc.so.6"0  < ^
v  "XMapWindow" 0     >:5 `#v_           ^
v  "XFlush" 0         ^v 000<000"LL"
v  "XCreateWindow" 0  ^>"LL"10G C 70P80P v
v"XDefaultRootWindow"0^v C G05"LL"0G07G08<
>I60G0P 60G1+:60P     ^>90P01P 0000000002v
vG04"LLLIIIIIIILLL"0G07G08G09G1000:*8*5*5<
>C 80G70G 0"LLV" 20G C 80G70G0"LV"30G C  v
                           @C G00"VI"0*25<


I'll test a 32-bit variant later today.
Last edited on
Topic archived. No new replies allowed.