Reformatted code; made paths in generated documentation relative.

This commit is contained in:
Simon Brooke 2026-02-14 15:32:59 +00:00
parent 222368bf64
commit 08a7c4153c
24 changed files with 496 additions and 716 deletions

View file

@ -162,7 +162,7 @@ FULL_PATH_NAMES = YES
# will be relative from the directory where doxygen is started. # will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES. # This tag requires that the tag FULL_PATH_NAMES is set to YES.
STRIP_FROM_PATH = src/ STRIP_FROM_PATH = ../../
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which # path mentioned in the documentation of a class, which tells the reader which

View file

@ -6,6 +6,10 @@ Work towards the implementation of a software system like that described in [Pos
*Originally most of this documentation was on a wiki attached to the [GitHub project](https://github.com/simon-brooke/post-scarcity); when that was transferred to [my own foregejo instance](https://git.journeyman.cc/simon/post-scarcity) the wiki was copied. However, it's more convenient to keep documentation in the project with the source files, and version controlled in the same Git repository. So while both wikis still exist, they should no longer be considered canonical. The canonical version is in `/docs`, and is incorporated by [Doxygen](https://www.doxygen.nl/) into the generated documentation — which is generated into `/doc` using the command `make doc`.* *Originally most of this documentation was on a wiki attached to the [GitHub project](https://github.com/simon-brooke/post-scarcity); when that was transferred to [my own foregejo instance](https://git.journeyman.cc/simon/post-scarcity) the wiki was copied. However, it's more convenient to keep documentation in the project with the source files, and version controlled in the same Git repository. So while both wikis still exist, they should no longer be considered canonical. The canonical version is in `/docs`, and is incorporated by [Doxygen](https://www.doxygen.nl/) into the generated documentation — which is generated into `/doc` using the command `make doc`.*
## State of Play
You can read about the current [state of play](md_home_2simon_2workspace_2post-scarcity_2docs_2state-of-play.html).
## AWFUL WARNING 1 ## AWFUL WARNING 1
This does not work. It isn't likely to work any time soon. If you want to learn Lisp, don't start here; try Clojure, Scheme or Common Lisp (in which case I recommend Steel Bank Common Lisp). If you want to learn how Lisp works, still don't start here. This isn't ever going to be anything like a conventional Lisp environment. This does not work. It isn't likely to work any time soon. If you want to learn Lisp, don't start here; try Clojure, Scheme or Common Lisp (in which case I recommend Steel Bank Common Lisp). If you want to learn how Lisp works, still don't start here. This isn't ever going to be anything like a conventional Lisp environment.

View file

@ -90,9 +90,10 @@ struct cons_pointer make_integer( int64_t value, struct cons_pointer more ) {
struct cons_pointer result = NIL; struct cons_pointer result = NIL;
debug_print( L"Entering make_integer\n", DEBUG_ALLOC ); debug_print( L"Entering make_integer\n", DEBUG_ALLOC );
if ( integerp(more) && (pointer2cell( more ).payload.integer.value < 0)) if ( integerp( more )
{ && ( pointer2cell( more ).payload.integer.value < 0 ) ) {
printf("WARNING: negative value %" PRId64 " passed as `more` to `make_integer`\n", printf( "WARNING: negative value %" PRId64
" passed as `more` to `make_integer`\n",
pointer2cell( more ).payload.integer.value ); pointer2cell( more ).payload.integer.value );
} }
@ -129,7 +130,9 @@ struct cons_pointer acquire_integer( int64_t value, struct cons_pointer more ) {
struct cons_pointer result; struct cons_pointer result;
if ( !nilp( more ) || value < 0 || value >= SMALL_INT_LIMIT ) { if ( !nilp( more ) || value < 0 || value >= SMALL_INT_LIMIT ) {
debug_print( L"acquire_integer passing to make_integer (outside small int range)\n", DEBUG_ALLOC ); debug_print
( L"acquire_integer passing to make_integer (outside small int range)\n",
DEBUG_ALLOC );
result = make_integer( value, more ); result = make_integer( value, more );
} else { } else {
if ( !small_int_cache_initialised ) { if ( !small_int_cache_initialised ) {
@ -141,7 +144,8 @@ struct cons_pointer acquire_integer( int64_t value, struct cons_pointer more ) {
debug_print( L"small_int_cache initialised.\n", DEBUG_ALLOC ); debug_print( L"small_int_cache initialised.\n", DEBUG_ALLOC );
} }
debug_printf( DEBUG_ALLOC, L"acquire_integer: returning %" PRId64 "\n", value); debug_printf( DEBUG_ALLOC, L"acquire_integer: returning %" PRId64 "\n",
value );
result = small_int_cache[value]; result = small_int_cache[value];
} }
return result; return result;
@ -162,7 +166,9 @@ void release_integer( struct cons_pointer p) {
!nilp( o.payload.integer.more ) || // or it's a bignum; !nilp( o.payload.integer.more ) || // or it's a bignum;
o.payload.integer.value >= SMALL_INT_LIMIT || // or it's bigger than the small int cache limit; o.payload.integer.value >= SMALL_INT_LIMIT || // or it's bigger than the small int cache limit;
!eq( p, small_int_cache[o.payload.integer.value] ) // or it's simply not the copy in the cache... !eq( p, small_int_cache[o.payload.integer.value] ) // or it's simply not the copy in the cache...
) { dec_ref( p); } else { ) {
dec_ref( p );
} else {
debug_printf( DEBUG_ALLOC, L"release_integer: releasing %" PRId64 "\n", debug_printf( DEBUG_ALLOC, L"release_integer: releasing %" PRId64 "\n",
o.payload.integer.value ); o.payload.integer.value );
} }
@ -252,7 +258,8 @@ struct cons_pointer add_integers( struct cons_pointer a,
debug_print( L"\n", DEBUG_ARITH ); debug_print( L"\n", DEBUG_ARITH );
if ( carry == 0 && ( rv >= 0 || rv < SMALL_INT_LIMIT ) ) { if ( carry == 0 && ( rv >= 0 || rv < SMALL_INT_LIMIT ) ) {
result = acquire_integer( (int64_t)(rv & 0xffffffff), NIL); result =
acquire_integer( ( int64_t ) ( rv & 0xffffffff ), NIL );
break; break;
} else { } else {
struct cons_pointer new = make_integer( 0, NIL ); struct cons_pointer new = make_integer( 0, NIL );
@ -376,7 +383,8 @@ struct cons_pointer multiply_integers( struct cons_pointer a,
/* if xj exceeds one digit, break it into the digit dj and /* if xj exceeds one digit, break it into the digit dj and
* the carry */ * the carry */
carry = xj >> INTEGER_BIT_SHIFT; carry = xj >> INTEGER_BIT_SHIFT;
struct cons_pointer dj = acquire_integer( xj & MAX_INTEGER, NIL ); struct cons_pointer dj =
acquire_integer( xj & MAX_INTEGER, NIL );
replace_integer_p( ri, append_cell( ri, dj ) ); replace_integer_p( ri, append_cell( ri, dj ) );
// struct cons_pointer new_ri = append_cell( ri, dj ); // struct cons_pointer new_ri = append_cell( ri, dj );
@ -412,9 +420,11 @@ struct cons_pointer multiply_integers( struct cons_pointer a,
struct cons_pointer integer_to_string_add_digit( int digit, int digits, struct cons_pointer integer_to_string_add_digit( int digit, int digits,
struct cons_pointer tail ) { struct cons_pointer tail ) {
wint_t character = btowc( hex_digits[digit] ); wint_t character = btowc( hex_digits[digit] );
debug_printf( DEBUG_IO, L"integer_to_string_add_digit: digit is %d, digits is %d; returning: ", digit, digits); debug_printf( DEBUG_IO,
struct cons_pointer r = ( digits % 3 == 0 ) ? L"integer_to_string_add_digit: digit is %d, digits is %d; returning: ",
make_string( L',', make_string( character, digit, digits );
struct cons_pointer r =
( digits % 3 == 0 ) ? make_string( L',', make_string( character,
tail ) ) : tail ) ) :
make_string( character, tail ); make_string( character, tail );
@ -460,7 +470,8 @@ struct cons_pointer integer_to_string( struct cons_pointer int_pointer,
while ( accumulator > 0 || !nilp( next ) ) { while ( accumulator > 0 || !nilp( next ) ) {
if ( accumulator < MAX_INTEGER && !nilp( next ) ) { if ( accumulator < MAX_INTEGER && !nilp( next ) ) {
accumulator += accumulator +=
( pointer2cell( next ).payload.integer.value % INT_CELL_BASE ); ( pointer2cell( next ).payload.integer.value %
INT_CELL_BASE );
next = pointer2cell( next ).payload.integer.more; next = pointer2cell( next ).payload.integer.more;
} }
int offset = ( int ) ( accumulator % base ); int offset = ( int ) ( accumulator % base );

View file

@ -65,7 +65,8 @@ struct cons_pointer simplify_ratio( struct cons_pointer pointer ) {
result = acquire_integer( ddrv / gcd, NIL ); result = acquire_integer( ddrv / gcd, NIL );
} else { } else {
debug_printf( DEBUG_ARITH, debug_printf( DEBUG_ARITH,
L"simplify_ratio: %ld/%ld => %ld/%ld\n", ddrv, drrv, ddrv/gcd, drrv/gcd); L"simplify_ratio: %ld/%ld => %ld/%ld\n",
ddrv, drrv, ddrv / gcd, drrv / gcd );
result = result =
make_ratio( acquire_integer( ddrv / gcd, NIL ), make_ratio( acquire_integer( ddrv / gcd, NIL ),
acquire_integer( drrv / gcd, NIL ) ); acquire_integer( drrv / gcd, NIL ) );
@ -126,8 +127,12 @@ struct cons_pointer add_ratio_ratio( struct cons_pointer arg1,
r = add_ratio_ratio( r1, r2 ); r = add_ratio_ratio( r1, r2 );
if (!eq( r, r1)) { dec_ref( r1);} if ( !eq( r, r1 ) ) {
if (!eq( r, r2)) { dec_ref( r2);} dec_ref( r1 );
}
if ( !eq( r, r2 ) ) {
dec_ref( r2 );
}
/* because the references on dd1vm, dr1vm, dd2vm and dr2vm were /* because the references on dd1vm, dr1vm, dd2vm and dr2vm were
* never incremented except when making r1 and r2, decrementing * never incremented except when making r1 and r2, decrementing
@ -238,8 +243,7 @@ struct cons_pointer multiply_ratio_ratio( struct
struct cons_pointer dividend = acquire_integer( ddrv, NIL ); struct cons_pointer dividend = acquire_integer( ddrv, NIL );
struct cons_pointer divisor = acquire_integer( drrv, NIL ); struct cons_pointer divisor = acquire_integer( drrv, NIL );
struct cons_pointer unsimplified = struct cons_pointer unsimplified = make_ratio( dividend, divisor );
make_ratio( dividend, divisor);
result = simplify_ratio( unsimplified ); result = simplify_ratio( unsimplified );
release_integer( dividend ); release_integer( dividend );
@ -321,7 +325,9 @@ struct cons_pointer make_ratio( struct cons_pointer dividend,
cell->payload.ratio.divisor = divisor; cell->payload.ratio.divisor = divisor;
result = simplify_ratio( unsimplified ); result = simplify_ratio( unsimplified );
if ( !eq( result, unsimplified)) { dec_ref( unsimplified); } if ( !eq( result, unsimplified ) ) {
dec_ref( unsimplified );
}
} else { } else {
result = result =
throw_exception( c_string_to_lisp_string throw_exception( c_string_to_lisp_string

View file

@ -45,7 +45,8 @@
* @param location_descriptor a description of where the pointer was caught. * @param location_descriptor a description of where the pointer was caught.
* @return struct cons_pointer * @return struct cons_pointer
*/ */
struct cons_pointer check_exception( struct cons_pointer pointer, char * location_descriptor) { struct cons_pointer check_exception( struct cons_pointer pointer,
char *location_descriptor ) {
struct cons_pointer result = NIL; struct cons_pointer result = NIL;
struct cons_space_object *object = &pointer2cell( pointer ); struct cons_space_object *object = &pointer2cell( pointer );
@ -92,17 +93,19 @@ void free_init_symbols() {
* the name on the source pointer. Would make stack frames potentially * the name on the source pointer. Would make stack frames potentially
* more readable and aid debugging generally. * more readable and aid debugging generally.
*/ */
struct cons_pointer bind_function( wchar_t *name, struct cons_pointer ( *executable ) struct cons_pointer bind_function( wchar_t *name,
struct cons_pointer ( *executable )
( struct stack_frame *, ( struct stack_frame *,
struct cons_pointer, struct cons_pointer ) ) { struct cons_pointer,
struct cons_pointer ) ) {
struct cons_pointer n = c_string_to_lisp_symbol( name ); struct cons_pointer n = c_string_to_lisp_symbol( name );
struct cons_pointer meta = struct cons_pointer meta =
make_cons( make_cons( init_primitive_symbol, TRUE ), make_cons( make_cons( init_primitive_symbol, TRUE ),
make_cons( make_cons( init_name_symbol, n ), make_cons( make_cons( init_name_symbol, n ),
NIL ) ); NIL ) );
struct cons_pointer r = check_exception( struct cons_pointer r =
deep_bind( n, make_function( meta, executable ) ), check_exception( deep_bind( n, make_function( meta, executable ) ),
"bind_function" ); "bind_function" );
dec_ref( n ); dec_ref( n );
@ -114,9 +117,10 @@ struct cons_pointer bind_function( wchar_t *name, struct cons_pointer ( *executa
* Bind this compiled `executable` function, as a Lisp special form, to * Bind this compiled `executable` function, as a Lisp special form, to
* this `name` in the `oblist`. * this `name` in the `oblist`.
*/ */
struct cons_pointer bind_special( wchar_t *name, struct cons_pointer ( *executable ) struct cons_pointer bind_special( wchar_t *name,
( struct stack_frame *, struct cons_pointer ( *executable )
struct cons_pointer, struct cons_pointer ) ) { ( struct stack_frame *, struct cons_pointer,
struct cons_pointer ) ) {
struct cons_pointer n = c_string_to_lisp_symbol( name ); struct cons_pointer n = c_string_to_lisp_symbol( name );
struct cons_pointer meta = struct cons_pointer meta =
@ -136,9 +140,9 @@ struct cons_pointer bind_special( wchar_t *name, struct cons_pointer ( *executab
* Bind this `value` to this `symbol` in the `oblist`. * Bind this `value` to this `symbol` in the `oblist`.
*/ */
struct cons_pointer struct cons_pointer
bind_symbol_value( struct cons_pointer symbol, struct cons_pointer value, bool lock) { bind_symbol_value( struct cons_pointer symbol, struct cons_pointer value,
struct cons_pointer r = check_exception( bool lock ) {
deep_bind( symbol, value ), struct cons_pointer r = check_exception( deep_bind( symbol, value ),
"bind_symbol_value" ); "bind_symbol_value" );
if ( lock && !exceptionp( r ) ) { if ( lock && !exceptionp( r ) ) {
@ -153,7 +157,8 @@ bind_symbol_value( struct cons_pointer symbol, struct cons_pointer value, bool l
/** /**
* Bind this `value` to this `name` in the `oblist`. * Bind this `value` to this `name` in the `oblist`.
*/ */
struct cons_pointer bind_value( wchar_t *name, struct cons_pointer value, bool lock ) { struct cons_pointer bind_value( wchar_t *name, struct cons_pointer value,
bool lock ) {
struct cons_pointer p = c_string_to_lisp_symbol( name ); struct cons_pointer p = c_string_to_lisp_symbol( name );
struct cons_pointer r = bind_symbol_value( p, value, lock ); struct cons_pointer r = bind_symbol_value( p, value, lock );
@ -270,14 +275,17 @@ int main( int argc, char *argv[] ) {
FILE *infile = infilename == NULL ? stdin : fopen( infilename, "r" ); FILE *infile = infilename == NULL ? stdin : fopen( infilename, "r" );
lisp_io_in = bind_value( C_IO_IN, make_read_stream( file_to_url_file(infile), lisp_io_in =
bind_value( C_IO_IN,
make_read_stream( file_to_url_file( infile ),
make_cons( make_cons make_cons( make_cons
( c_string_to_lisp_keyword ( c_string_to_lisp_keyword
( L"url" ), ( L"url" ),
c_string_to_lisp_string c_string_to_lisp_string
( L"system:standard input" ) ), ( L"system:standard input" ) ),
NIL ) ), false ); NIL ) ), false );
lisp_io_out = bind_value( C_IO_OUT, lisp_io_out =
bind_value( C_IO_OUT,
make_write_stream( file_to_url_file( stdout ), make_write_stream( file_to_url_file( stdout ),
make_cons( make_cons make_cons( make_cons
( c_string_to_lisp_keyword ( c_string_to_lisp_keyword
@ -285,14 +293,16 @@ int main( int argc, char *argv[] ) {
c_string_to_lisp_string c_string_to_lisp_string
( L"system:standard output]" ) ), ( L"system:standard output]" ) ),
NIL ) ), false ); NIL ) ), false );
bind_value( L"*log*", make_write_stream( file_to_url_file( stderr ), bind_value( L"*log*",
make_write_stream( file_to_url_file( stderr ),
make_cons( make_cons make_cons( make_cons
( c_string_to_lisp_keyword ( c_string_to_lisp_keyword
( L"url" ), ( L"url" ),
c_string_to_lisp_string c_string_to_lisp_string
( L"system:standard log" ) ), ( L"system:standard log" ) ),
NIL ) ), false ); NIL ) ), false );
bind_value( L"*sink*", make_write_stream( sink, bind_value( L"*sink*",
make_write_stream( sink,
make_cons( make_cons make_cons( make_cons
( c_string_to_lisp_keyword ( c_string_to_lisp_keyword
( L"url" ), ( L"url" ),
@ -303,7 +313,8 @@ int main( int argc, char *argv[] ) {
* the default prompt * the default prompt
*/ */
prompt_name = bind_value( L"*prompt*", prompt_name = bind_value( L"*prompt*",
show_prompt ? c_string_to_lisp_symbol( L":: " ) : NIL, false ); show_prompt ? c_string_to_lisp_symbol( L":: " ) :
NIL, false );
/* /*
* primitive function operations * primitive function operations
*/ */

View file

@ -410,8 +410,7 @@ void collect_meta( struct cons_pointer stream, char *url ) {
*/ */
struct cons_pointer get_default_stream( bool inputp, struct cons_pointer env ) { struct cons_pointer get_default_stream( bool inputp, struct cons_pointer env ) {
struct cons_pointer result = NIL; struct cons_pointer result = NIL;
struct cons_pointer stream_name = struct cons_pointer stream_name = inputp ? lisp_io_in : lisp_io_out;
inputp ? lisp_io_in : lisp_io_out;
result = c_assoc( stream_name, env ); result = c_assoc( stream_name, env );
@ -509,8 +508,8 @@ lisp_read_char( struct stack_frame *frame, struct cons_pointer frame_pointer,
if ( readp( frame->arg[0] ) ) { if ( readp( frame->arg[0] ) ) {
result = result =
make_string( url_fgetwc make_string( url_fgetwc
( pointer2cell( frame->arg[0] ).payload.stream. ( pointer2cell( frame->arg[0] ).payload.
stream ), NIL ); stream.stream ), NIL );
} }
return result; return result;

View file

@ -90,7 +90,7 @@ struct cons_pointer read_path( URL_FILE * input, wint_t initial,
switch ( initial ) { switch ( initial ) {
case '/': case '/':
prefix = c_string_to_lisp_symbol( L"oblist" ); prefix = make_cons( c_string_to_lisp_symbol( L"oblist" ), NIL);
break; break;
case '$': case '$':
case LSESSION: case LSESSION:
@ -308,7 +308,8 @@ struct cons_pointer read_number( struct stack_frame *frame,
initial ); initial );
for ( c = initial; iswdigit( c ) for ( c = initial; iswdigit( c )
|| c == LPERIOD || c == LSLASH || c == LCOMMA; c = url_fgetwc( input ) ) { || c == LPERIOD || c == LSLASH || c == LCOMMA;
c = url_fgetwc( input ) ) {
switch ( c ) { switch ( c ) {
case LPERIOD: case LPERIOD:
if ( seen_period || !nilp( dividend ) ) { if ( seen_period || !nilp( dividend ) ) {
@ -342,8 +343,8 @@ struct cons_pointer read_number( struct stack_frame *frame,
break; break;
default: default:
result = add_integers( multiply_integers( result, base ), result = add_integers( multiply_integers( result, base ),
acquire_integer( ( int ) c - ( int ) '0', acquire_integer( ( int ) c -
NIL ) ); ( int ) '0', NIL ) );
debug_printf( DEBUG_IO, debug_printf( DEBUG_IO,
L"read_number: added character %c, result now ", L"read_number: added character %c, result now ",

View file

@ -188,7 +188,8 @@ void free_cell( struct cons_pointer pointer ) {
free_vso( pointer ); free_vso( pointer );
break; break;
default: default:
fprintf( stderr, "WARNING: Freeing object of type %s!", (char *) &(cell->tag.bytes)); fprintf( stderr, "WARNING: Freeing object of type %s!",
( char * ) &( cell->tag.bytes ) );
} }
strncpy( &cell->tag.bytes[0], FREETAG, TAGLENGTH ); strncpy( &cell->tag.bytes[0], FREETAG, TAGLENGTH );
@ -240,8 +241,8 @@ struct cons_pointer allocate_cell( uint32_t tag ) {
total_cells_allocated++; total_cells_allocated++;
debug_printf( DEBUG_ALLOC, debug_printf( DEBUG_ALLOC,
L"Allocated cell of type '%4.4s' at %d, %d \n", cell->tag.bytes, L"Allocated cell of type '%4.4s' at %d, %d \n",
result.page, result.offset ); cell->tag.bytes, result.page, result.offset );
} else { } else {
debug_printf( DEBUG_ALLOC, L"WARNING: Allocating non-free cell!" ); debug_printf( DEBUG_ALLOC, L"WARNING: Allocating non-free cell!" );
} }
@ -270,5 +271,6 @@ void initialise_cons_pages( ) {
void summarise_allocation( ) { void summarise_allocation( ) {
fwprintf( stderr, fwprintf( stderr,
L"Allocation summary: allocated %lld; deallocated %lld; not deallocated %lld.\n", L"Allocation summary: allocated %lld; deallocated %lld; not deallocated %lld.\n",
total_cells_allocated, total_cells_freed, total_cells_allocated - total_cells_freed ); total_cells_allocated, total_cells_freed,
total_cells_allocated - total_cells_freed );
} }

View file

@ -101,11 +101,13 @@ struct cons_pointer c_type( struct cons_pointer pointer ) {
struct cons_pointer result = NIL; struct cons_pointer result = NIL;
struct cons_space_object cell = pointer2cell( pointer ); struct cons_space_object cell = pointer2cell( pointer );
if ( strncmp( (char *)&cell.tag.bytes, VECTORPOINTTAG, TAGLENGTH ) == 0 ) { if ( strncmp( ( char * ) &cell.tag.bytes, VECTORPOINTTAG, TAGLENGTH ) ==
0 ) {
struct vector_space_object *vec = pointer_to_vso( pointer ); struct vector_space_object *vec = pointer_to_vso( pointer );
for ( int i = TAGLENGTH - 1; i >= 0; i-- ) { for ( int i = TAGLENGTH - 1; i >= 0; i-- ) {
result = make_string( (wchar_t)vec->header.tag.bytes[i], result ); result =
make_string( ( wchar_t ) vec->header.tag.bytes[i], result );
} }
} else { } else {
for ( int i = TAGLENGTH - 1; i >= 0; i-- ) { for ( int i = TAGLENGTH - 1; i >= 0; i-- ) {
@ -213,11 +215,15 @@ struct cons_pointer make_exception( struct cons_pointer message,
/** /**
* Construct a cell which points to an executable Lisp function. * Construct a cell which points to an executable Lisp function.
*/ */
struct cons_pointer make_function( struct cons_pointer make_function( struct cons_pointer meta,
struct cons_pointer meta, struct cons_pointer ( *executable ) ( struct
struct cons_pointer ( *executable )( struct stack_frame *, stack_frame
struct cons_pointer, *,
struct cons_pointer ) ) { struct
cons_pointer,
struct
cons_pointer ) )
{
struct cons_pointer pointer = allocate_cell( FUNCTIONTV ); struct cons_pointer pointer = allocate_cell( FUNCTIONTV );
struct cons_space_object *cell = &pointer2cell( pointer ); struct cons_space_object *cell = &pointer2cell( pointer );
inc_ref( meta ); inc_ref( meta );
@ -283,7 +289,9 @@ uint32_t calculate_hash( wint_t c, struct cons_pointer ptr ) {
if ( nilp( cell->payload.string.cdr ) ) { if ( nilp( cell->payload.string.cdr ) ) {
result = ( uint32_t ) c; result = ( uint32_t ) c;
} else { } else {
result = ( (uint32_t)c * cell->payload.string.hash ) & 0xffffffff; result =
( ( uint32_t ) c *
cell->payload.string.hash ) & 0xffffffff;
} }
break; break;
} }
@ -356,8 +364,9 @@ struct cons_pointer make_symbol_or_key( wint_t c, struct cons_pointer tail,
} }
} }
} else { } else {
result = make_exception( result =
c_string_to_lisp_string( L"Unexpected tag when making symbol or key." ), make_exception( c_string_to_lisp_string
( L"Unexpected tag when making symbol or key." ),
NIL ); NIL );
} }
@ -367,11 +376,16 @@ struct cons_pointer make_symbol_or_key( wint_t c, struct cons_pointer tail,
/** /**
* Construct a cell which points to an executable Lisp special form. * Construct a cell which points to an executable Lisp special form.
*/ */
struct cons_pointer make_special( struct cons_pointer make_special( struct cons_pointer meta,
struct cons_pointer meta, struct cons_pointer ( *executable ) ( struct
struct cons_pointer ( *executable )( struct stack_frame *frame, stack_frame
struct cons_pointer, *frame,
struct cons_pointer env ) ) { struct
cons_pointer,
struct
cons_pointer
env ) )
{
struct cons_pointer pointer = allocate_cell( SPECIALTV ); struct cons_pointer pointer = allocate_cell( SPECIALTV );
struct cons_space_object *cell = &pointer2cell( pointer ); struct cons_space_object *cell = &pointer2cell( pointer );
inc_ref( meta ); inc_ref( meta );

View file

@ -114,10 +114,10 @@ void dump_object( URL_FILE * output, struct cons_pointer pointer ) {
case RATIOTV: case RATIOTV:
url_fwprintf( output, url_fwprintf( output,
L"\t\tRational cell: value %ld/%ld, count %u\n", L"\t\tRational cell: value %ld/%ld, count %u\n",
pointer2cell( cell.payload.ratio.dividend ).payload. pointer2cell( cell.payload.ratio.dividend ).
integer.value, payload.integer.value,
pointer2cell( cell.payload.ratio.divisor ).payload. pointer2cell( cell.payload.ratio.divisor ).
integer.value, cell.count ); payload.integer.value, cell.count );
break; break;
case READTV: case READTV:
url_fputws( L"\t\tInput stream; metadata: ", output ); url_fputws( L"\t\tInput stream; metadata: ", output );

View file

@ -88,8 +88,7 @@ struct cons_pointer lisp_make_hashmap( struct stack_frame *frame,
map->payload.hashmap.buckets[bucket_no] = map->payload.hashmap.buckets[bucket_no] =
make_cons( make_cons( key, val ), make_cons( make_cons( key, val ),
map->payload.hashmap. map->payload.hashmap.buckets[bucket_no] );
buckets[bucket_no] );
} }
} }
} }

View file

@ -201,8 +201,8 @@ struct cons_pointer hashmap_put_all( struct cons_pointer mapp,
assoc = c_cdr( assoc ); assoc = c_cdr( assoc );
} }
} else if ( hashmapp( assoc ) ) { } else if ( hashmapp( assoc ) ) {
for (struct cons_pointer keys = hashmap_keys( assoc); !nilp( keys); for ( struct cons_pointer keys = hashmap_keys( assoc );
keys = c_cdr( keys)) { !nilp( keys ); keys = c_cdr( keys ) ) {
struct cons_pointer key = c_car( keys ); struct cons_pointer key = c_car( keys );
hashmap_put( mapp, key, hashmap_get( assoc, key ) ); hashmap_put( mapp, key, hashmap_get( assoc, key ) );
} }
@ -246,7 +246,8 @@ struct cons_pointer clone_hashmap( struct cons_pointer ptr ) {
result = result =
make_hashmap( from_pl.n_buckets, from_pl.hash_fn, make_hashmap( from_pl.n_buckets, from_pl.hash_fn,
from_pl.write_acl ); from_pl.write_acl );
struct vector_space_object const *to = pointer_to_vso( result ); struct vector_space_object const *to =
pointer_to_vso( result );
struct hashmap_payload to_pl = to->payload.hashmap; struct hashmap_payload to_pl = to->payload.hashmap;
for ( int i = 0; i < to_pl.n_buckets; i++ ) { for ( int i = 0; i < to_pl.n_buckets; i++ ) {
@ -349,8 +350,9 @@ struct cons_pointer c_assoc( struct cons_pointer key,
result = hashmap_get( entry_ptr, key ); result = hashmap_get( entry_ptr, key );
break; break;
default: default:
throw_exception( c_append( throw_exception( c_append
c_string_to_lisp_string( L"Store entry is of unknown type: " ), ( c_string_to_lisp_string
( L"Store entry is of unknown type: " ),
c_type( entry_ptr ) ), NIL ); c_type( entry_ptr ) ), NIL );
} }
} }
@ -362,9 +364,9 @@ struct cons_pointer c_assoc( struct cons_pointer key,
debug_print_object( c_type( store ), DEBUG_BIND ); debug_print_object( c_type( store ), DEBUG_BIND );
debug_print( L"`\n", DEBUG_BIND ); debug_print( L"`\n", DEBUG_BIND );
result = result =
throw_exception( throw_exception( c_append
c_append( ( c_string_to_lisp_string
c_string_to_lisp_string( L"Store is of unknown type: " ), ( L"Store is of unknown type: " ),
c_type( store ) ), NIL ); c_type( store ) ), NIL );
} }
@ -419,11 +421,11 @@ struct cons_pointer set( struct cons_pointer key, struct cons_pointer value,
debug_dump_object( store, DEBUG_BIND ); debug_dump_object( store, DEBUG_BIND );
debug_println( DEBUG_BIND ); debug_println( DEBUG_BIND );
debug_printf( DEBUG_BIND, L"set: store is %s\n`", lisp_string_to_c_string( c_type( store)) ); debug_printf( DEBUG_BIND, L"set: store is %s\n`",
lisp_string_to_c_string( c_type( store ) ) );
if ( nilp( value ) ) { if ( nilp( value ) ) {
result = store; result = store;
} } else if ( nilp( store ) || consp( store ) ) {
else if ( nilp( store ) || consp( store ) ) {
result = make_cons( make_cons( key, value ), store ); result = make_cons( make_cons( key, value ), store );
} else if ( hashmapp( store ) ) { } else if ( hashmapp( store ) ) {
debug_print( L"set: storing in hashmap\n", DEBUG_BIND ); debug_print( L"set: storing in hashmap\n", DEBUG_BIND );

View file

@ -446,8 +446,9 @@ c_apply( struct stack_frame *frame, struct cons_pointer frame_pointer,
result = next_pointer; result = next_pointer;
} else { } else {
result = result =
( *fn_cell.payload.special. ( *fn_cell.payload.
executable ) ( get_stack_frame( next_pointer ), special.executable ) ( get_stack_frame
( next_pointer ),
next_pointer, env ); next_pointer, env );
debug_print( L"Special form returning: ", DEBUG_EVAL ); debug_print( L"Special form returning: ", DEBUG_EVAL );
debug_print_object( result, DEBUG_EVAL ); debug_print_object( result, DEBUG_EVAL );
@ -1245,7 +1246,8 @@ lisp_exception( struct stack_frame *frame, struct cons_pointer frame_pointer,
struct cons_pointer env ) { struct cons_pointer env ) {
struct cons_pointer message = frame->arg[0]; struct cons_pointer message = frame->arg[0];
return exceptionp( message ) ? message : throw_exception( message, return exceptionp( message ) ? message : throw_exception( message,
frame->previous ); frame->
previous );
} }
/** /**
@ -1277,11 +1279,13 @@ struct cons_pointer lisp_repl( struct stack_frame *frame,
new_env = set( prompt_name, frame->arg[0], new_env ); new_env = set( prompt_name, frame->arg[0], new_env );
} }
if ( readp( frame->arg[1] ) ) { if ( readp( frame->arg[1] ) ) {
new_env = set( c_string_to_lisp_symbol(L"*in*"), frame->arg[1], new_env); new_env =
set( c_string_to_lisp_symbol( L"*in*" ), frame->arg[1], new_env );
input = frame->arg[1]; input = frame->arg[1];
} }
if ( readp( frame->arg[2] ) ) { if ( readp( frame->arg[2] ) ) {
new_env = set( c_string_to_lisp_symbol(L"*out*"), frame->arg[2], new_env); new_env =
set( c_string_to_lisp_symbol( L"*out*" ), frame->arg[2], new_env );
output = frame->arg[2]; output = frame->arg[2];
} }
@ -1426,13 +1430,14 @@ struct cons_pointer c_append( struct cons_pointer l1, struct cons_pointer l2 ) {
if ( pointer2cell( l1 ).tag.value == pointer2cell( l2 ).tag.value ) { if ( pointer2cell( l1 ).tag.value == pointer2cell( l2 ).tag.value ) {
if ( nilp( c_cdr( l1 ) ) ) { if ( nilp( c_cdr( l1 ) ) ) {
return return
make_string_like_thing( ( pointer2cell( l1 ).payload. make_string_like_thing( ( pointer2cell( l1 ).
string.character ), l2, payload.string.character ),
l2,
pointer2cell( l1 ).tag.value ); pointer2cell( l1 ).tag.value );
} else { } else {
return return
make_string_like_thing( ( pointer2cell( l1 ).payload. make_string_like_thing( ( pointer2cell( l1 ).
string.character ), payload.string.character ),
c_append( c_cdr( l1 ), l2 ), c_append( c_cdr( l1 ), l2 ),
pointer2cell( l1 ).tag.value ); pointer2cell( l1 ).tag.value );
} }

View file

@ -1,274 +0,0 @@
# State of Play
## 20260204
### Testing what is leaking memory
#### Analysis
If you just start up and immediately abort the current build of psse, you get:
> Allocation summary: allocated 19986; deallocated 245; not deallocated 19741.
Allocation summaries from the current unit tests give the following ranges of values:
| | Min | Max | |
| --------------- | ----- | ----- | ---- |
| Allocated | 19991 | 39009 | |
| Deallocated | 238 | 1952 | |
| Not deallocated | 19741 | 37057 | |
The numbers go up broadly in sinc with one another &mdash; that is to say, broadly, as the number allocated rises, so do both the numbers deallocated and the numbers not deallocated. But not exactly.
#### Strategy: what doesn't get cleaned up?
Write a test wrapper which reads a file of forms, one per line, from standard input, and passes each in turn to a fresh invocation of psse, reporting the form and the allocation summary.
```bash
#1/bin/bash
while IFS= read -r form; do
allocation=`echo ${form} | ../../target/psse 2>&1 | grep Allocation`
echo "* ${allocation}: ${form}"
done
```
So, from this:
* Allocation summary: allocated 19986; deallocated 245; not deallocated 19741.:
* Allocation summary: allocated 19990; deallocated 249; not deallocated 19741.: ()
* Allocation summary: allocated 20019; deallocated 253; not deallocated 19766.: nil
Allocating an empty list allocates four additional cells, all of which are deallocated. Allocating 'nil' allocates a further **29** cells, 25 of which are not deallocated. WTF?
Following further work I have this, showing the difference added to the base case of cells allocated, cells deallocated, and, most critically, cells not deallocated.
From this we see that reading and printing `nil` allocates an additional 33 cells, of which eight are not cleaned up. That's startling, and worrying.
But the next row shows us that reading and printing an empty list costs only four cells, each of which is cleaned up. Further down the table we see that an empty map is also correctly cleaned up. Where we're leaking memory is in reading (or printing, although I doubt this) symbols, either atoms, numbers, or keywords (I haven't yet tried strings, but I expect they're similar.)
| **Case** | **Delta Allocated** | **Delta Deallocated** | **Delta Not Deallocated** |
| --------------------------------- | ------------------- | --------------------- | ------------------------- |
| **Basecase** | 0 | 0 | 0 |
| **nil** | 33 | 8 | 25 |
| **()** | 4 | 4 | 0 |
| **(quote ())** | 39 | 2 | 37 |
| **(list )** | 37 | 12 | 25 |
| **(list 1)** | 47 | 14 | 33 |
| **(list 1 1)** | 57 | 16 | 41 |
| **(list 1 1 1)** | 67 | 18 | 49 |
| **(list 1 2 3)** | 67 | 18 | 49 |
| **(+)** | 36 | 10 | 26 |
| **(+ 1)** | 44 | 12 | 32 |
| **(+ 1 1)** | 53 | 14 | 39 |
| **(+ 1 1 1)** | 62 | 16 | 46 |
| **(+ 1 2 3)** | 62 | 16 | 46 |
| **(list 'a 'a 'a)** | 151 | 33 | 118 |
| **(list 'a 'b 'c)** | 151 | 33 | 118 |
| **(list :a :b :c)** | 121 | 15 | 106 |
| **(list :alpha :bravo :charlie)** | 485 | 15 | 470 |
| **{}** | 6 | 6 | 0 |
| **{:z 0}** | 43 | 10 | 33 |
| **{:zero 0}** | 121 | 10 | 111 |
| **{:z 0 :o 1}** | 80 | 11 | 69 |
| **{:zero 0 :one 1}** | 210 | 14 | 196 |
| **{:z 0 :o 1 :t 2}** | 117 | 12 | 105 |
Looking at the entries, we see that
1. each number read costs ten allocations, of which only two are successfully deallocated;
2. the symbol `list` costs 33 cells, of which 25 are not deallocated, whereas the symbol `+` costs only one cell fewer, and an additional cell is not deallocated. So it doesn't seem that cell allocation scales with the length of the symbol;
3. Keyword allocation does scale with the length of the keyword, apparently, since `(list :a :b :c)` allocates 121 and deallocates 15, while `(list :alpha :bravo :charlie)` allocates 485 and deallocates the same 15;
4. The fact that both those two deallocate 15, and a addition of three numbers `(+ 1 2 3)` or `(+ 1 1 1)` deallocates 16 suggest to me that the list structure is being fully reclaimed but atoms are not being.
5. The atom `'a` costs more to read than the keyword `:a` because the reader macro is expanding `'a` to `(quote a)` behind the scenes.
### The integer allocation bug
Looking at what happens when we read a single digit number, we get the following:
```
2
Entering make_integer
Allocated cell of type 'INTR' at 19, 507
make_integer: returning
INTR (1381256777) at page 19, offset 507 count 0
Integer cell: value 0, count 0
Entering make_integer
Allocated cell of type 'INTR' at 19, 508
make_integer: returning
INTR (1381256777) at page 19, offset 508 count 0
Integer cell: value 10, count 0
Entering make_integer
Allocated cell of type 'INTR' at 19, 509
make_integer: returning
INTR (1381256777) at page 19, offset 509 count 0
Integer cell: value 2, count 0
Entering make_integer
Allocated cell of type 'INTR' at 19, 510
make_integer: returning
INTR (1381256777) at page 19, offset 510 count 0
Integer cell: value 0, count 0
Entering make_integer
Allocated cell of type 'INTR' at 19, 506
make_integer: returning
INTR (1381256777) at page 19, offset 506 count 0
Integer cell: value 0, count 0
Entering make_integer
Allocated cell of type 'INTR' at 19, 505
make_integer: returning
INTR (1381256777) at page 19, offset 505 count 0
Integer cell: value 0, count 0
Entering make_integer
Allocated cell of type 'INTR' at 19, 504
make_integer: returning
INTR (1381256777) at page 19, offset 504 count 0
Integer cell: value 0, count 0
Allocated cell of type 'STRG' at 19, 503
Freeing cell STRG (1196577875) at page 19, offset 503 count 0
String cell: character '2' (50) with hash 0; next at page 0 offset 0, count 0
value: "2"
Freeing cell INTR (1381256777) at page 19, offset 504 count 0
Integer cell: value 2, count 0
2
Allocated cell of type 'SYMB' at 19, 504
Allocated cell of type 'SYMB' at 19, 503
Allocated cell of type 'SYMB' at 19, 502
Allocated cell of type 'SYMB' at 19, 501
Freeing cell SYMB (1112365395) at page 19, offset 501 count 0
Symbol cell: character '*' (42) with hash 485100; next at page 19 offset 502, count 0
value: *in*
Freeing cell SYMB (1112365395) at page 19, offset 502 count 0
Symbol cell: character 'i' (105) with hash 11550; next at page 19 offset 503, count 0
value: in*
Freeing cell SYMB (1112365395) at page 19, offset 503 count 0
Symbol cell: character 'n' (110) with hash 110; next at page 19 offset 504, count 0
value: n*
Freeing cell SYMB (1112365395) at page 19, offset 504 count 0
Symbol cell: character '*' (42) with hash 0; next at page 0 offset 0, count 0
value: *
```
Many things are worrying here.
1. The only thing being freed here is the symbol to which the read stream is bound &mdash; and I didn't see where that got allocated, but we shouldn't be allocating and tearing down a symbol for every read! This implies that when I create a string with `c_string_to_lisp_string`, I need to make damn sure that that string is deallocated as soon as I'm done with it &mdash; and wherever I'm dealing with symbols which will be referred to repeatedly in `C` code, I need either
1. to bind a global on the C side of the world, which will become messy;
2. or else write a hash function which returns, for a `C` string, the same value that the standard hashing function will return for the lexically equivalent `Lisp` string, so that I can search hashmap structures from C without having to allocate and deallocate a fresh copy of the `Lisp` string;
3. In reading numbers, I'm generating a fresh instance of `Lisp zero` and `Lisp ten`, each time `read_integer` is called, and I'm not deallocating them.
4. I am correctly deallocating the number I did read, though!
## 20260203
I'm consciously avoiding the bignum issue for now. My current thinking is that if the C code only handles 64 bit integers, and bignums have to be done in Lisp code, that's perfectly fine with me.
### Hashmaps, assoc lists, and generalised key/value stores
I now have the oblist working as a hashmap, and also hybrid assoc lists which incorporate hashmaps working. I don't 100% have consistent methods for reading stores which may be plain old assoc lists, new hybrid assoc lists, or hashmaps working but it isn't far off. This also takes me streets further towards doing hierarchies of hashmaps, allowing my namespace idea to work &mdash; and hybrid assoc lists provide a very sound basis for building environment structures.
Currently all hashmaps are mutable, and my doctrine is that that is fixable when access control lists are actually implemented.
#### assoc
The function `(assoc store key) => value` should be the standard way of getting a value out of a store.
#### put!
The function `(put! store key value) => store` should become the standard way of setting a value in a store (of course, if the store is an assoc list or an immutable map, a new store will be returned which holds the additional key/value binding).
### State of unit tests
Currently:
> Tested 45, passed 39, failed 6
But the failures are as follows:
```
unit-tests/bignum-add.sh => checking a bignum was created: Fail
unit-tests/bignum-add.sh => adding 1152921504606846977 to 1: Fail: expected 't', got 'nil'
unit-tests/bignum-add.sh => adding 1 to 1152921504606846977: Fail: expected 't', got 'nil'
unit-tests/bignum-add.sh => adding 1152921504606846977 to 1152921504606846977: Fail: expected 't', got 'nil'
unit-tests/bignum-add.sh => adding 10000000000000000000 to 10000000000000000000: Fail: expected 't', got 'nil'
unit-tests/bignum-add.sh => adding 1 to 1329227995784915872903807060280344576: Fail: expected 't', got 'nil'
unit-tests/bignum-add.sh => adding 1 to 3064991081731777716716694054300618367237478244367204352: Fail: expected 't', got 'nil'
unit-tests/bignum-expt.sh => (expt 2 60): Fail: expected '1152921504606846976', got '1'
unit-tests/bignum-expt.sh => (expt 2 61): Fail: expected '2305843009213693952', got '2'
unit-tests/bignum-expt.sh => (expt 2 64): Fail: expected '18446744073709551616', got '16'
unit-tests/bignum-expt.sh => (expt 2 65): Fail: expected '36893488147419103232', got '32'
unit-tests/bignum-print.sh => printing 1152921504606846976: Fail: expected '1152921504606846976', got '1'
unit-tests/bignum-print.sh => printing 1152921504606846977: Fail: expected '1152921504606846977', got '2'
unit-tests/bignum-print.sh => printing 1329227995784915872903807060280344576: Fail: expected '1329227995784915872903807060280344576', \n got '1151321504605245376'
unit-tests/bignum.sh => unit-tests/bignum.sh => Fail: expected '1,152,921,504,606,846,976', got '1'
unit-tests/bignum-subtract.sh => unit-tests/bignum-subtract.sh => subtracting 1 from 1152921504606846976: Fail: expected '1152921504606846975', got '0'
unit-tests/bignum-subtract.sh => subtracting 1 from 1152921504606846977: Fail: expected '1152921504606846976', got '1'
unit-tests/bignum-subtract.sh => subtracting 1 from 1152921504606846978: Fail: expected '1152921504606846977', got '2'
unit-tests/bignum-subtract.sh => subtracting 1152921504606846977 from 1: Fail: expected '-1152921504606846976', got '1'
unit-tests/bignum-subtract.sh => subtracting 10000000000000000000 from 20000000000000000000: Fail: expected '10000000000000000000', got '-376293541461622793'
unit-tests/memory.sh
```
In other words, all failures are in bignum arithmetic **except** that I still have a major memory leak due to not decrefing somewhere where I ought to.
### Zig
I've also experimented with autotranslating my C into Zig, but this failed. Although I don't think C is the right language for implementing my base Lisp in, it's what I've got; and until I can get some form of autotranslate to bootstrap me into some more modern systems language, I think I need to stick with it.
## 20250704
Right, I'm getting second and subsequent integer cells with negative values, which should not happen. This is probably the cause of (at least some of) the bignum problems. I need to find out why. This is (probably) fixable.
```lisp
:: (inspect 10000000000000000000)
INTR (1381256777) at page 3, offset 873 count 2
Integer cell: value 776627963145224192, count 2
BIGNUM! More at:
INTR (1381256777) at page 3, offset 872 count 1
Integer cell: value -8, count 1
```
Also, `print` is printing bignums wrong on ploughwright, but less wrong on mason, which implies a code difference. Investigate.
## 20250314
Thinking further about this, I think at least part of the problem is that I'm storing bignums as cons-space objects, which means that the integer representation I can store has to fit into the size of a cons pointer, which is 64 bits. Which means that to store integers larger than 64 bits I need chains of these objects.
If I stored bignums in vector space, this problem would go away (especially as I have not implemented vector space yet).
However, having bignums in vector space would cause a churn of non-standard-sized objects in vector space, which would mean much more frequent garbage collection, which has to be mark-and-sweep because unequal-sized objects, otherwise you get heap fragmentation.
So maybe I just have to put more work into debugging my cons-space bignums.
Bother, bother.
There are no perfect solutions.
However however, it's only the node that's short on vector space which has to pause to do a mark and sweep. It doesn't interrupt any other node, because their reference to the object will remain the same, even if it is the 'home node' of the object which is sweeping. So all the node has to do is set its busy flag, do GC, and clear its busy flag, The rest of the system can just be carrying on as normal.
So... maybe mark and sweep isn't the big deal I think it is?
## 20250313
OK, the 60 bit integer cell happens in `int128_to_integer` in `arith/integer.c`. It seems to be being done consistently; but there is no obvious reason. `MAX_INTEGER` is defined in `arith/peano.h`. I've changed both to use 63 bits, and this makes no change to the number of unit tests that fail.
With this change, `(fact 21)`, which was previously printing nothing, now prints a value, `11,891,611,015,076,642,816`. However, this value is definitively wrong, should be `51,090,942,171,709,440,000`. But, I hadn't fixed the shift in `integer_to_string`; have now... still no change in number of failed tests...
But `(fact 21)` gives a different wrong value, `4,974,081,987,435,560,960`. Factorial values returned by `fact` are correct (agree with SBCL running the same code) up to `(fact 20)`, with both 60 bit integer cells and 63 bit integer cells giving correct values.
Uhhhmmm... but I'd missed two other places where I'd had the number of significant bits as a numeric literal. Fixed those and now `(fact 21)` does not return a printable answer at all, although the internal representation is definitely wrong. So we may be seeing why I chose 60 bits.
Bother.
## 20250312
Printing of bignums definitely doesn't work; I'm not persuaded that reading of bignums works right either, and there are probably problems with bignum arithmetic too.
The internal memory representation of a number rolls over from one cell to two cells at 1152921504606846976, and I'm not at all certain why it does because this is neither 2<sup>63</sup> nor 2<sup>64</sup>.
| | | |
| -------------- | -------------------- | ---- |
| 2<sup>62</sup> | 4611686018427387904 | |
| 2<sup>63</sup> | 9223372036854775808 | |
| 2<sup>64</sup> | 18446744073709551616 | |
| Mystery number | 1152921504606846976 | |
In fact, our mystery number turns out (by inspection) to be 2<sup>60</sup>. But **why**?