added jammed noun converter

This commit is contained in:
Amadeo Bellotti 2024-01-26 09:32:44 -05:00
parent dd4bd97213
commit 2287f99bed
2 changed files with 530 additions and 0 deletions

450
memory/noun.py Normal file
View File

@ -0,0 +1,450 @@
"""
Urbit nouns with mug, jam, and cue.
"""
import mmh3
from bitstream import BitStream
def byte_length(i: int):
"""how many bytes to represent i?
>>> byte_length(0)
0
>>> byte_length(255)
1
>>> byte_length(256)
2
"""
lyn = i.bit_length()
byt = lyn >> 3
return byt + 1 if lyn & 7 else byt
def intbytes(i: int):
"""turn i into a (little endian) bytes object
>>> intbytes(0)
b''
>>> intbytes(256)
b'\\x00\\x01'
"""
return i.to_bytes(byte_length(i), 'little', signed=False)
def mum(syd: int, fal: int, key: int):
"""try to hash key with syd, incrementing syd on zero
hash up to 8 times, falling back to fal.
>>> mum(0xcafebabe, 0x7fff, 0)
2046756072
>>> mum(0xdeadbeef, 0xfffe, 8790750394176177384)
422532488
"""
k = intbytes(key)
for s in range(syd, syd+8):
haz = mmh3.hash(k, seed=s, signed=False)
ham = (haz >> 31) ^ (haz & 0x7fffffff)
if 0 != ham:
return ham
return fal
def mug_both(one: int, two: int):
"""mug from two other mugs (for cells)
>>> mug_both(2046756072, 2046756072)
422532488
"""
return mum(0xdeadbeef, 0xfffe, (two << 32) | one)
class Cell:
"""A cell is an ordered pair of two nouns.
>>> x = Cell(1, Cell(2, 3))
>>> x.head
1
>>> x.tail.head
2
>>> x.tail.tail
3
"""
def __init__(self, head, tail, mug=0):
self.head = head
self.tail = tail
self.mug = mug
def __hash__(self):
"""31-bit non-zero murmur3 (mug)
>>> x = Cell(0, 0)
>>> x.mug
0
>>> hash(x)
422532488
>>> x.mug
422532488
"""
if 0 == self.mug:
self.mug = mug_both(mug(self.head), mug(self.tail))
return self.mug
def __eq__(self, other):
"""unifying equality: after comparing equal, cells share storage.
>>> x = Cell(Cell(1,2),Cell(3,4))
>>> y = Cell(Cell(1,2),Cell(3,4))
>>> hash(y)
1496649457
>>> x.head is y.head or x.tail is y.tail
False
>>> x == y
True
>>> x.head is y.head and x.tail is y.tail
True
>>> x.mug != 0 and x.mug == y.mug
True
"""
if not deep(other):
return False
if self.mug != 0 and other.mug != 0 and self.mug != other.mug:
return False
if self.head != other.head:
return False
other.head = self.head
if self.tail != other.tail:
return False
other.tail = self.tail
if 0 != self.mug:
other.mug = self.mug
elif 0 != other.mug:
self.mug = other.mug
return True
def pretty(self, tail_pos):
"""pretty print a cell in or out of tail position
>>> x = Cell(0, 0)
>>> x.pretty(False)
'[0 0]'
>>> x.pretty(True)
'0 0'
"""
content = '%s %s' % \
(pretty(self.head, False), pretty(self.tail, True))
if tail_pos:
return content
return '[%s]' % content
def __str__(self):
return self.pretty(False)
noun = int | Cell
def deep(n: noun):
"""test whether noun is a cell, like nock 3
>>> deep(1)
False
>>> deep(Cell(1,2))
True
"""
return isinstance(n, Cell)
def mug(n: noun):
"""get the mug for any noun
>>> mug(0)
2046756072
>>> mug(Cell(0, 0))
422532488
"""
if deep(n):
return hash(n)
return mum(0xcafebabe, 0x7fff, n)
def pretty(n: noun, tail_pos:bool):
"""pretty-print a noun, in or out of tail position.
>>> pretty(1, True)
'1'
>>> pretty(Cell(1,Cell(2,3)), False)
'[1 2 3]'
>>> pretty(Cell(Cell(1,2), 3), True)
'[1 2] 3'
"""
if deep(n):
return n.pretty(tail_pos)
return str(n)
def translate(seq):
"""turn python sequences into tuples.
>>> str(translate([1,[2,3],4]))
'[1 [2 3] 4]'
"""
def r(i, l):
if 1 == l:
return seq[i]
return Cell(translate(seq[i]), r(i+1, l-1))
if isinstance(seq, noun):
return seq
c = len(seq)
if 0 == c:
return 0
return r(0, c)
def parse(s: str):
"""parse strings into nouns. dots in atoms are ignored,
outermost braces can be omitted.
>>> parse('1.024')
1024
>>> x = parse('[[1 2] 3]')
>>> [x.head.head, x.head.tail, x.tail]
[1, 2, 3]
>>> x = parse('[1 2] 3')
>>> [x.head.head, x.head.tail, x.tail]
[1, 2, 3]
"""
sep = True
start = 0
wait = []
top = (0, [])
num = []
def frame(i: int):
return (i, [])
def end_atom():
nonlocal sep
if not sep:
sep = True
top[1].append(int(''.join(num)))
num.clear()
def end_cell():
items = top[1]
count = len(items)
if 0 == count:
return 0
count -= 1
tail = items[count]
while count > 0:
count -= 1
tail = Cell(items[count], tail)
return tail
for i in range(0, len(s)):
c = s[i]
match c:
case '[':
end_atom()
wait.append(top)
top = frame(i)
case ']':
if not wait:
raise ValueError('unmatched ] at %d' % i)
end_atom()
val = end_cell()
top = wait.pop()
top[1].append(val)
case ' ':
end_atom()
case '.':
if sep:
raise ValueError('floating dot at %d' % i)
case _:
if not str.isdigit(c):
raise ValueError('unrecognized character %s at %d' % (c, i))
else:
if sep:
start = i
sep = False
num.append(c)
if wait:
raise ValueError('unclosed [ at %d' % top[0])
end_atom()
return end_cell()
def jam_to_stream(n: noun, out: BitStream):
"""jam but put the bits into a stream
>>> s = BitStream()
>>> jam_to_stream(Cell(0,0), s)
>>> s
100101
"""
cur = 0
refs = {}
def bit(b: bool):
nonlocal cur
out.write(b, bool)
cur += 1
def zero():
bit(False)
def one():
bit(True)
def bits(num: int, count: int):
nonlocal cur
for i in range(0, count):
out.write(0 != (num & (1 << i)), bool)
cur += count
def save(a: noun):
refs[a] = cur
def mat(i: int):
if 0 == i:
one()
else:
a = i.bit_length()
b = a.bit_length()
above = b + 1
below = b - 1
bits(1 << b, above)
bits(a & ((1 << below) - 1), below)
bits(i, a)
def back(ref: int):
one()
one()
mat(ref)
def r(a: noun):
dupe = refs.get(a)
if deep(a):
if dupe:
back(dupe)
else:
save(a)
one()
zero()
r(a.head)
r(a.tail)
elif dupe:
isize = a.bit_length()
dsize = dupe.bit_length()
if isize < dsize:
zero()
mat(a)
else:
back(dupe)
else:
save(a)
zero()
mat(a)
r(n)
def read_int(length: int, s: BitStream):
"""read length bits from s and make a python integer.
>>> s = BitStream()
>>> s.write(False, bool)
>>> s.write(False, bool)
>>> s.write(True, bool)
>>> read_int(3, s)
4
"""
r = 0
for i in range(0, length):
r |= s.read(bool) << i
return r
def jam(n: noun):
"""urbit serialization: * -> @
>>> jam(0)
2
>>> jam(Cell(0,0))
41
>>> jam(Cell(Cell(1234567890987654321,1234567890987654321), \\
... Cell(1234567890987654321,1234567890987654321)))
22840095095806892874257389573
"""
out = BitStream()
jam_to_stream(n, out)
return read_int(len(out), out)
def cue_from_stream(s: BitStream):
"""cue but read the bits from a stream
>>> s = BitStream()
>>> s.write(False, bool)
>>> s.write(True, bool)
>>> cue_from_stream(s)
0
"""
refs = {}
cur = 0
def bits(n: int):
nonlocal cur
cur += n
return read_int(n, s)
def one():
nonlocal cur
cur += 1
x= s.read(bool)
return x
def rub():
z = 0
while not one():
z += 1
if 0 == z:
return 0
below = z - 1
lbits = bits(below)
bex = 1 << below
return bits(bex ^ lbits)
def r(start: int):
ret = None
if one():
if one():
ret = refs[rub()]
else:
hed = r(cur)
tal = r(cur)
ret = Cell(hed, tal)
else:
ret = rub()
refs[start] = ret
return ret
return r(cur)
def cue(i: int):
"""urbit deserialization: @ -> *
>>> str(cue(22840095095806892874257389573))
'[[1234567890987654321 1234567890987654321] 1234567890987654321 1234567890987654321]'
"""
s = BitStream()
while i > 0:
s.write(i & 1, bool)
i >>= 1
return cue_from_stream(s)
if '__main__' == __name__:
import doctest
doctest.testmod()

80
memory/noun_converter.py Normal file
View File

@ -0,0 +1,80 @@
from noun import * # make this a pip package some day
import sys
def cue_noun(data):
x = cue(int.from_bytes(data[5:], 'little'))
hed_len = (x.head.bit_length()+7)//8
mark = x.head.to_bytes(hed_len,'little').decode()
noun = x.tail
return (mark,noun)
memory = [0]
bitmask = (1 << 28) -1
def inorder_traversal(noun):
if(isinstance(noun,int)):
print("error")
return
if(len(memory) == 1):
memory.append(1<<63) # mark first cell as execute
else:
memory.append(0)
if(isinstance(noun.head, Cell) and isinstance(noun.tail, Cell)):
memory[-1] = memory[-1] | len(memory) << 28
cell_loc = len(memory)-1
inorder_traversal(noun.head)
memory[cell_loc] = memory[cell_loc] | len(memory)
inorder_traversal(noun.tail)
elif(isinstance(noun.head, Cell) and isinstance(noun.tail, int)):
memory[-1] = memory[-1] | (1<<56) | len(memory) << 28
inorder_traversal(noun.head)
memory[-1] = memory[-1] | (noun.tail & bitmask)
elif(isinstance(noun.head, int) and isinstance(noun.tail, Cell)):
memory[-1] = memory[-1] | (1<<57) | (noun.head & bitmask) << 28 | len(memory)
inorder_traversal(noun.tail)
elif(isinstance(noun.head, int) and isinstance(noun.tail, int)):
memory[-1] = memory[-1] | (1<<57) | (1<<56) | (noun.head & bitmask) << 28 | (noun.tail & bitmask)
number = "59.500.485.596.334.891.570.437"
def main():
# Check if two arguments (excluding the script name) are provided
if len(sys.argv) != 3:
print("Usage: python3 noun_converter.py <jammed-noun> <filename>")
sys.exit(1)
# Extract arguments
number_str = sys.argv[1]
filename = sys.argv[2]
noun = None
# Validate if the first argument is a number
try:
# Attempt to convert the number argument to a float
number = int(number_str.replace('.',''))
noun = cue(number)
#pretty(noun, False)
print(noun)
except ValueError:
print("The first argument must be a number.")
sys.exit(1)
inorder_traversal(noun)
memory[0] = len(memory)
#for mem in memory:
# print(format(mem, '016x'))
# Work with the file
try:
with open(filename, 'w') as file:
for mem in memory:
file.write(format(mem, '016x')+'\n')
except IOError as e:
print(f"An error occurred while working with the file: {e}")
sys.exit(1)
if __name__ == "__main__":
main()