HashedPoem

From Esolang
Jump to navigation Jump to search

HashedPoem is an esolang invented by User:None1, inspired by the hash function. Something interesting is that this esolang is the 24th esolang invented in the year 2024 (on this wiki).

Official logo

Programs

HashedPoem programs consists of words separated by whitespaces (spaces, tabs, linefeeds). Each word has a hash value, the hash of a lowercase string a0a1a2...al-1 can be computed using this expression:

If one can't see math tags, there is the expression in image form:

Expression.png

To be more clear, a Python function computing the hash value is as follows:

computehash=lambda x:sum((ord(j)*pow(256,i,7))%7 for i,j in enumerate(x))%7

When computing the hash value of a non-lowercase string, it converts to lowercase first.

For convenience, the following table lists some words with every hash value:

Hash Words
0 or, fuck, with, can, to
1 no, its, of, at
2 oh, it, let, hello, word
3 this, down, thanks
4 then, as, for, get
5 is, on, brain, brainfuck
6 yes, esolang, there, the, a

If two words have the same hash value, they are treated equally in HashedPoem.

For convenience, let's call the words with hash value i i-hashed words.

A program doesn't have to make sense (you can even include words that are not in English), but it has to be valid, that means you can even write:

is is is is is is is or

but not:

is is is is is is is

but writing a valid HashedPoem program that makes sense can be a challenge, because it's hard.

Non-letters are ignored, that means:

*#?_

has a hash of 0.

Memory

It uses an unbounded tape. Every cell has a unique nonnegative address, address 0 is the first cell, address 1 is the second, address 2 is the third, etc. The cells contain unbounded signed intergers. Initiall, address 0 contains 1, all the other addresses are 0.

Integer representation

Since HashedPoem programs consist entirely of words and words have only 7 possible hash values, HashedPoem cannot use decimal to represent integers, instead, it uses base-6. A number is represented by a sequence of words ending with a 0-hashed word, all the words except the last one are nonzero-hashed. A digit is represented by a nonzero-hashed word, its value is the word's hash minus 1.

For example, the sequence of words:

this is because

when hashed, becomes:

3 5 0

which becomes 24 in base-6, which is 16 in decimal.

When there is no digit in an integer, it represents 0, so:

because

represents 0.

There is no way to represent negative integers directly in code, but you can use arithmetic operations to calculate one.

Commands

Commands are assembly-like.

Hash Command Meaning
0 ADD <integer1> <integer2> Add the cells with addresses <integer1> and <integer2> and store the result in <integer1>
1 SUB <integer1> <integer2> Subtract the cells with addresses <integer1> and <integer2> and store the result in <integer1>
2 MUL <integer1> <integer2> Multiply the cells with addresses <integer1> and <integer2> and store the result in <integer1>
3 IO <word> <integer> If the hash of the word is even, print the cell with address <integer> as Unicode, otherwise input the cell with address <integer2> as Unicode
4 REF <integer> Set the cell with address <integer> to the cell addressed the absolute value of the value of the cell with address <integer>.
5 LABEL <integer> Defines a label with an ID of <integer>, if a label with this ID already exists, the program errors.
6 JMP <integer1> <integer2> If the cell with address <integer1> is nonzero, jump to the label with the ID <integer2>, if there isn't a label with the ID <integer2>, the program errors.

Code

Assemblers

Currently, there exists a low-level assembler that assembles to hashed HashedPoem in Python. (Note, this assembler automatically converts decimal to base-6, so write in decimal)

import sys
code=map(lambda x:x.split(),sys.stdin.read().split('\n'))
res=''
def dec2base6(x):
    r='0'
    while x:
        r=str(x%6+1)+r
        x//=6
    return r
for i in code:
    try:
        c=i[0].upper()
        if c=='ADD':
            res+='0'+dec2base6(int(i[1]))+dec2base6(int(i[2]))
        if c=='SUB':
            res+='1'+dec2base6(int(i[1]))+dec2base6(int(i[2]))
        if c=='MUL':
            res+='2'+dec2base6(int(i[1]))+dec2base6(int(i[2]))
        if c=='IO':
            res+='3'+i[1]+dec2base6(int(i[2]))
        if c=='REF':
            res+='4'+dec2base6(int(i[1]))
        if c=='LABEL':
            res+='5'+dec2base6(int(i[1]))
        if c=='JMP':
            res+='6'+dec2base6(int(i[1]))+dec2base6(int(i[2]))
    except:
        pass
print(res)

Example:

LABEL 0
JMP 0 0

Assembles to:

50600

Interpreters

In Python:

import sys
class Tape:
    def __init__(self):
        self.data={0:1}
    def __getitem__(self,item):
        if item in self.data:
            return self.data[item]
        else:
            return 0
    def __setitem__(self,item,x):
        self.data[item]=x
_computehash=lambda x:sum((ord(j)*pow(256,i,7))%7 for i,j in enumerate(x))%7
computehash=lambda x:_computehash(''.join(filter(lambda y:y.isalpha(),x.lower())))
hashall=lambda x:''.join(map(str,map(computehash,x.split())))
def nextword(x): # separate the first word
    return (int(x[0]),x[1:])
def _nextnum(x):
    p=x.find('0')
    if p==-1:
        raise SyntaxError('Missing ending zero-hashed word in number')
    return (x[:p],x[p+1:])
def nextnum(x): # separate the next integer
    ns,nx=_nextnum(x)
    r=0
    for i in ns:
        r=r*6+int(i)-1
    return (r,nx)
def disasm(x): # Disassemble code to a list of tuples of integers
    ha=hashall(x)
    res=[]
    ha=''.join(filter(lambda x:x in '0123456',ha))
    while ha:
        i,ha=nextword(ha)
        i=str(i)
        if i in '0126':
            a,ha=nextnum(ha)
            b,ha=nextnum(ha)
            res.append((int(i),a,b))
        elif i in '45':
            a,ha=nextnum(ha)
            res.append((int(i),a))
        else:
            a,ha=nextword(ha)
            b,ha=nextnum(ha)
            res.append((int(i),a,b))
    return res
def getchar():
    c=sys.stdin.read(1)
    return (ord(c) if c else 0)%256
def interpret_asm(x): # Interpret the disassembled code
    ip=0
    tape=Tape()
    labels={}
    for j,i in enumerate(x):
        if i[0]==5: # LABEL
            if i[1] in labels:
                raise SyntaxError("Duplicate label ID")
            labels[i[1]]=j
    while ip<len(x):
        c=x[ip]
        opcode=c[0]
        if opcode==0: # ADD
            tape[c[1]]+=tape[c[2]]
        if opcode==1: # SUB
            tape[c[1]]-=tape[c[2]]
        if opcode==2: # MUL
            tape[c[1]]*=tape[c[2]]
        if opcode==6: # JMP
            if tape[c[1]]:
                if c[2] not in labels:
                    raise SyntaxError("Label ID does not exist")
                ip=labels[c[2]]
                continue
        if opcode==4: # REF
            tape[c[1]]=tape[abs(tape[c[1]])]
        if opcode==3: # IO
            if not (c[1]&1): # even: print
                print(chr(tape[c[2]]),end='')
            else: # odd: input
                tape[c[2]]=getchar()
        ip+=1
def interpret(x):
    return interpret_asm(disasm(x))

if __name__=='__main__':
    interpret(sys.stdin.read())

Unhashers

This is a random unhasher in Python, it unhashes to meaningless code, because, as the previous text claimed, writing a program that makes sense is hard:

from random import choice
_computehash=lambda x:sum((ord(j)*pow(256,i,7))%7 for i,j in enumerate(x))%7
computehash=lambda x:_computehash(''.join(filter(lambda y:y.isalpha(),x.lower())))
def rndgen():
    return ''.join(choice('QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm') for i in range(10))
def gen(x):
    r=rndgen()
    while computehash(r)!=x:
        r=rndgen()
    return r
c=input()
d=[gen(int(i)) for i in filter(lambda x:x in '0123456',c.strip())]
for i in range(len(d)//5):
    print(d[i*5],d[i*5+1],d[i*5+2],d[i*5+3],d[i*5+4])
for i in range(len(d)%5):
    print(d[len(d)//5*5+i],end=' ')

An improvement on the above, generates meaningless "words".

from random import choice
_computehash=lambda x:sum((ord(j)*pow(256,i,7))%7 for i,j in enumerate(x))%7
computehash=lambda x:_computehash(''.join(filter(lambda y:y.isalpha(),x.lower())))
def rndgen():
    return choice('im in il un dis ad re de inter mis  '.split(' '))+choice('act add substract switch hash unhash jump label multiply program research create implement interpret prove and or xor not'.split())+choice('ed tion ation ion er or ist  '.split(' '))
def gen(x):
    r=rndgen()
    while computehash(r)!=x:
        r=rndgen()
    return r
c=input()
d=[gen(int(i)) for i in filter(lambda x:x in '0123456',c.strip())]
for i in range(len(d)//5):
    print(d[i*5],d[i*5+1],d[i*5+2],d[i*5+3],d[i*5+4])
print(*d[len(d)//5*5:])

Examples

Infinite loop

is to yes can can

but actually, it can make more sense:

Brainfuck or esolang? Fuck !

The space between Fuck and ! is mandatory.

Hashed:

50600

Disassembled:

LABEL 0
JMP 0 0

Truth Machine

Currently, the code is only available in hashed form:

00000000000002000200020031003001020503030600

and meaningless form:

interlabelion ilnotist disimplementist rehashor or
ilxortion resubstraction programtion labelor substraction
label or deortion improgram imresearch
reortion or discreate oror injumpion
misinterpretation deadder adsubstractation ilproveation misprogramor
dejumpist imnoted ortion jumper adsubstractist
ilmultiplyist dishash dehash dissubstract iladded
renoted adoror disandor unxored disnottion
uncreateed adprogramist ilandist delabel

Disassembled:

ADD 0 0
ADD 0 0
ADD 0 0
ADD 0 0
ADD 1 0
ADD 1 0
ADD 1 0
IO 1 0
ADD 2 0
SUB 0 1
LABEL 0
IO 1 2
JMP 0 0

If you unhashed it into more meaningful code (code that makes sense), please put it here.

Computational class

Turing complete, because the two register minsky machine can be compiled to it (l is the position of the command in the minsky machine program, first command is 1, second command is 2, third is 3, etc. n is the sum of the number of commands in the minsky machine program and l):

Increment A:
LABEL l
ADD 1 0

Increment B:
LABEL l
ADD 2 0

If A=0, jump to the i-th minsky machine command, otherwise decrement A:
LABEL l
JMP 1 n
JMP 0 i
LABEL n
SUB 1 0

If B=0, jump to the i-th minsky machine command, otherwise decrement B:
LABEL l
JMP 2 n
JMP 0 i
LABEL n
SUB 2 0