An esoteric programming language - Fiftween
Community Forums/Showcase/An esoteric programming language - Fiftween
| ||
| I've been trying to think of a way to use group theory to make an esoteric programming language for a while, preferably with the Rubik's cube as the underlying group. Well, in the shower this morning I had a brainwave (most brainwaves happen in the shower, don't you agree?) I've made a programming language based on the game Fifteen. It's nowhere near as complicated as Rubik's cube, but it's based on some of the same ideas. If you don't know what it is, here's a brief description - you've got a 4x4 grid of tiles with one tile removed, and the rest shuffled. You have to move the tiles around until they're all in order. So I've made a virtual machine based on an infinitely-large fifteen grid, where data is stored in the spaces under the tiles, so you effectively move the 'gap' around to look at your data. The basic operations are: - writing a number in the cell where you are - moving in a direction - copying the current cell's number to an adjacent cell - adding, subtracting, etc. the current cell's number to an adjacent cell and some more program logic stuff which doesn't directly rely on the grid. I've made a program to compute the fibonacci series: w1d^mvm>w1d^m<s1d^m^w1000-vi2mvp+>d>dvm<d^g1s2e or with comments: w1 'write 1 in first space d^mvm> 'duplicate this in the square above and move back down and right w1 'write 1 in second space d^m< 'duplicate in square above and move left s1 'start of loop d^m^w1000-vi2mv 'check if number is bigger than 1000, if so go to end, otherwise carry on p 'print number +>d>dv 'add the two numbers, then duplicate right and down m<d^ 'move left, and duplicate this number to row above g1 'go back to start of loop s2e 'end of program Here's the code for the interpreter - call it either with the name of a file to interpret or a line of code
Type cell
Field neighbours:cell[4]
Field numneighbours
'0 up
'1 left
'2 down
'3 right
Field value
Method New()
End Method
Method getneighbour:cell(dir)
If Not neighbours[dir]
'debugo "find neighbour"
findneighbours(dir)
If Not neighbours[dir]
'debugo "create neighbour"
c:cell=New cell
neighbours[dir]=c
c.neighbours[(dir+2) Mod 4]=Self
numneighbours:+1
c.numneighbours:+1
EndIf
EndIf
Return neighbours[dir]
End Method
Method findneighbours(tdir)
checked:TList=New TList
onsearch(Self,checked,0,0,tdir)
End Method
Method onsearch(c:cell,checked:TList,x,y,tdir)
If checked.contains(Self) Then Return
checked.addlast Self
If x=0
If y=-1 'up
dir=0
Else 'down
dir=2
EndIf
Else
If x=-1 'left
dir=1
Else 'right
dir=3
EndIf
EndIf
If Abs(x)+Abs(y)=1 And c.neighbours[dir]=Null 'adjacent to original cell, not already known
Select dir
Case 0
'debugo "found up"
Case 2
'debugo "found down"
Case 1
'debugo "found left"
Case 3
'debugo "found right"
End Select
c.neighbours[dir]=Self
neighbours[(dir+2) Mod 4]=c
c.numneighbours:+1
numneighbours:+1
If c.numneighbours=4 Then Return
If tdir=dir Then Return
EndIf
For dir=0 To 3
If neighbours[dir]
dy=-Cos(dir*90)
dx=-Sin(dir*90)
'debugo "check ("+String(dx)+","+String(dy)+")"
neighbours[dir].onsearch(c,checked,x+dx,y+dy,tdir)
If c.numneighbours=4
'debugo "4 neighbours"
Return
EndIf
If c.neighbours[tdir]
'debugo "got wanted neighbour"
Return
EndIf
EndIf
Next
End Method
Method setvalue(n)
debugo "new value "+String(n)
value=n
End Method
End Type
Type machine
Field curcell:cell
Field txt$,pos
Field subs[1000]
Field backtrace[100]
Field tracesize
Field compressedtxt$
Method New()
curcell=New cell
End Method
Function Create:machine(txt$)
m:machine=New machine
m.txt=txt+" "
m.findsubs()
Return m
End Function
Method compresscode(cmd$)
compressedtxt:+cmd
End Method
Method findsubs()
pos=0
incomment=0
While pos<Len(txt)
If incomment
If txt[pos]=39 Or txt[pos]=10
incomment=0
Else
pos:+1
EndIf
Else
cmd$=Chr(txt[pos])
pos:+1
If cmd=" " Or cmd="~t" Or cmd="~n" Or cmd="'"
Else
compresscode cmd
EndIf
Select cmd
Case "s"
n=getnumber()
compresscode String(n)
subs[n]=pos
debugo "sub "+String(n)+" at "+String(pos)
Case "'"
incomment=1
End Select
EndIf
Wend
pos=0
End Method
Method go()
cmd$=Chr(txt[pos])
pos:+1
debugo cmd
Select cmd
Case "w" 'write
debugo "write"
n=getnumber()
curcell.setvalue n
Case "d" 'duplicate
debugo "duplicate"
v=curcell.value
debugo v
move()
curcell.setvalue v
Case "m" 'move
debugo "move"
move()
Case "s" 'sub
debugo "sub"
n=getnumber()
subs[n]=pos
Case "g" 'goto
debugo "goto"
n=getnumber()
repos subs[n]
Case "t" 'goto with backtrace
debugo "goto with backtrace"
n=getnumber()
repos subs[n],1
Case "r" 'return
debugo "return"
goback
Case "p" 'print
debugo "print"
WriteStdout curcell.value
Case "e" 'end
debugo "end"
pos=Len(txt)
Case "+" 'add
debugo "add"
v=curcell.value
debugo v
move()
curcell.setvalue curcell.value +v
Case "-" 'subtract
debugo "subtract"
v=curcell.value
move()
curcell.setvalue curcell.value -v
Case "*" 'multiply
debugo "multiply"
v=curcell.value
move()
curcell.setvalue curcell.value *v
Case "/" 'divide
debugo "divide"
v=curcell.value
move()
curcell.setvalue curcell.value /v
Case "%"
debugo "modulo"
v=curcell.value
move()
curcell.setvalue curcell.value Mod v
Case "i" 'if
n=getnumber()
If curcell.value>0
debugo "true: going to "+String(n)+" which is at "+String(subs[n])
repos subs[n]
EndIf
Case "n" 'not
If curcell.value
curcell.setvalue 0
Else
curcell.setvalue 1
EndIf
Case "'" 'comment
debugo "comment"
cmt$=""
While txt[pos]<>39 And txt[pos]<>10 And pos<Len(txt)
cmt:+Chr(txt[pos])
pos:+1
Wend
debugo cmt
Case "!" 'start/stop debugging
debugging=1-debugging
Case "~q" 'print string
debugo "print"
outstr$=getstring()
WriteStdout outstr
End Select
If pos>=Len(txt)
Return 1
EndIf
End Method
Method repos(n,r=0)
If r
backtrace[tracesize]=pos
tracesize:+1
EndIf
pos=n
End Method
Method goback()
tracesize:-1
pos=backtrace[tracesize]
End Method
Method getnumber()
If txt[pos]=46
debugo "number current cell value = "+String(curcell.value)
Return curcell.value
EndIf
n=0
While txt[pos]>=48 And txt[pos]<=57
n=n*10+txt[pos]-48
pos:+1
Wend
debugo "number "+String(n)
Return n
End Method
Method getstring$()
outstr$=""
While txt[pos]<>34
outstr:+Chr(txt[pos])
pos:+1
Wend
pos:+1
Return outstr
End Method
Method move()
Select txt[pos]
Case 94 '^ - up
dir=0
debugo "up"
Case 118 'v - down
dir=2
debugo "down"
Case 60 '< - left
dir=1
debugo "left"
Case 62 '> - right
dir=3
debugo "right"
End Select
curcell=curcell.getneighbour(dir)
pos:+1
End Method
End Type
Global f:TStream=WriteFile("out.txt")
Function debugo(txt$)
If debugging
Print txt
EndIf
WriteLine f,txt
End Function
inp$=AppArgs[1]
If FileType(inp)=1
f:TStream=ReadFile(inp)
code$=""
While Not f.Eof()
code:+f.ReadLine()+"~n"
Wend
Else
code$=inp
EndIf
Global debugging
m:machine=machine.Create(code)
If FileType(inp)=1
f:TStream=WriteFile("compressed."+inp)
Else
f:TStream=WriteFile("compressed.fiftween")
EndIf
f.WriteLine m.compressedtxt
f.close
While 1
If m.go()
End
EndIf
If debugging
i$=Input()
If i="e" Then End
EndIf
'Delay 1000
Wend
Here's a spec of the language: The interpreter reads along the input, ignoring anything that isn't a command, until it gets to a command character. Commands might take either an integer number or a direction directly after them. The directions are - ^ for up, v for down, < for left, and > for right. Commands, in the form command[argument]: w[number] - write the number in the current cell d[direction] - duplicate the current cell's value to the cell in the given direction and move to that cell m[direction] - move in the given direction +[direction] - add the current cell's value to the cell in the given direction, and move to that cell. - , * / and % also do what you expect them to s[number] - set a marker for a subroutine, referred to by the given number g[number] - go to the marker corresponding to the given number t[number] - same as g, but you can come back to this place later with an r command r - return to the last t command encountered. i[number] - if the value of the current cell is bigger than 0, go to the marker corresponding to the current number p - print the current cell's value " - print every character until another " is reached n - if the current cell's value is not 0, set it to 0, otherwise set it to 1 e - end the program comments start with a ' and can be ended by either another ' or a newline. That's all. I hope someone read all this and actually finds it interesting! |
| ||
| most brainwaves happen in the shower, don't you agree? Isn't it because of the oxygenated environment, or sommat?So it's kinda like a mini 'wrapped-stack' Forth? |
| ||
| not sure how it's like forth. It doesn't have any stacks, really... I suppose a tiny bit of the syntax is the same, but it's more like befunge, which itself is a bit more like forth. |
| ||
It took me about 10 minutes, but I've written my first program in it:w10 'write 10 to cell 1 (counter) m> 'move right w1 'write 1 g1 'goto sub 1 'SUB 4 s4 'sub 4 e 'end program 'SUB 1 s1 'begin sub 1 d> 'duplicate to next cell and move forward m< 'move back +> 'add to next cell p 'print current cell d< 'duplicate to previous cell and move back m< 'move back to counter g2 'goto sub 2 (decrement counter) s5 'return point m> 'move forward to cell 2 (workspace) g1 'end of sub 1 'SUB 2 s2 'decrement counter m^ 'move up w1 'write 1 -v 'subtract 1 from counter and move down 'p 'print counter 'p 'print counter again (to highlight for debug) g3 'end of sub 2 now check counter 'SUB 3 s3 'check the counter i5 g4 'end of sub 3 It computes powers of two for 10 loops :) |
| ||
And here's a prime number generator. Oh, I've added a modulo command to make this possible.w2mvw2 'write 2 as start of list, set n=2 s1 'start of infinite loop mvw1+^ 'add 1 to n dvmvw1000-^i99m^ 'check if n > 1000 g2 s2 'start of prime checking routine m^ i3 'if not at end of list, do modulo check mvd^pg101 ' otherwise n is prime! write n at end of list, go back to start of loop s3 'modulo check d^mvmvd^m^%v 'move y and n up, calculate n mod y i4 'if y mod n != 0, move to next space m^dvg101 ' otherwise put n back in place, go back to start of loop s4 m^dvmvd>g2 'move n back in place, go sub 2 s101 'find start of list sub i102 'if this cell not empty go 102 m>mvg1 'else return to start of loop s102 'sub 102 m<g101 'move left, go 101 s99 e Weirdly, it's not massively longer than perturbatio's program. |
| ||
| Disturbing, yet, addictive. |
| ||
A factorial function:w1 'write f=1 mvw4 'write n "factorial: "p"! = " g201 s201 'start of loop dvm^*^ 'multiply f*n mvmvw1-^ 'set n=n-1 i201 'if n, go back to start of loop m^pe and I've added a " command to allow nice text output. EDIT: and a very slow bubble sort w1m>w2m>w7m>w4m>w6m>w3m>w5 'initialise list m<m<m<m<m<m< ' move back to sort of list g1 'go to start of sort s1 'start of sort - go until swap needed or end of list m>i2g99 'if next element along is empty, finished s2 d^mvm<d^m>-< 'compute a-b above a i3 'if a>b, swap mvm>g1 'otherwise move along to b, go back to start of check s3 mvd^mvm>d<m^d>dv 'swap a and b g101 'go back to start of list s101 'find start of list m<i101 m>g1 s99 'end of sort - go back through list printing elements m< s98 p" "m<i98 e |
| ||
| It doesn't seem to work for me anymore, it just produces compressed versions of the code and then an unhandled exception. :( |
| ||
| Running which program? I can run your powers-of-two program fine with the latest version. |
| ||
| Why not write a real programming language? |
| ||
| It's been done. Why write a real programming language? |
| ||
| I need to check which version of the language I have, but it didn't work with any code samples, including my power of two thing. |
| ||
| Just copy out the code in the first post, and life will be gravy. |
| ||
| If I run the latest code, and enable debug mode, I get Attempt to write to readonly stream in the debugo function. presumably because you're reusing the global TStream variable f which gets reassigned before you're done debugging. (BMax v. 1.28) |