blob: 21e35d5ab706fd9306bbab7aa80719da9a29104a [file] [log] [blame]
/*
* Copyright 2013 Steven Watanabe
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
#include "../object.h"
#include "../lists.h"
#include "../modules.h"
#include "../rules.h"
#include "../variable.h"
#include "../native.h"
#include "../compile.h"
#include "../mem.h"
#include "../constants.h"
#include "string.h"
struct ps_map_entry
{
struct ps_map_entry * next;
LIST * key;
OBJECT * value;
};
struct ps_map
{
struct ps_map_entry * * table;
size_t table_size;
size_t num_elems;
};
static unsigned list_hash(LIST * key)
{
unsigned int hash = 0;
LISTITER iter = list_begin( key ), end = list_end( key );
for ( ; iter != end; ++iter )
{
hash = hash * 2147059363 + object_hash( list_item( iter ) );
}
return hash;
}
static int list_equal( LIST * lhs, LIST * rhs )
{
LISTITER lhs_iter, lhs_end, rhs_iter;
if ( list_length( lhs ) != list_length( rhs ) )
{
return 0;
}
lhs_iter = list_begin( lhs );
lhs_end = list_end( lhs );
rhs_iter = list_begin( rhs );
for ( ; lhs_iter != lhs_end; ++lhs_iter, ++rhs_iter )
{
if ( ! object_equal( list_item( lhs_iter ), list_item( rhs_iter ) ) )
{
return 0;
}
}
return 1;
}
static void ps_map_init( struct ps_map * map )
{
size_t i;
map->table_size = 2;
map->num_elems = 0;
map->table = BJAM_MALLOC( map->table_size * sizeof( struct ps_map_entry * ) );
for ( i = 0; i < map->table_size; ++i )
{
map->table[ i ] = NULL;
}
}
static void ps_map_destroy( struct ps_map * map )
{
size_t i;
for ( i = 0; i < map->table_size; ++i )
{
struct ps_map_entry * pos;
for ( pos = map->table[ i ]; pos; )
{
struct ps_map_entry * tmp = pos->next;
BJAM_FREE( pos );
pos = tmp;
}
}
BJAM_FREE( map->table );
}
static void ps_map_rehash( struct ps_map * map )
{
struct ps_map old = *map;
size_t i;
map->table = BJAM_MALLOC( map->table_size * 2 * sizeof( struct ps_map_entry * ) );
map->table_size *= 2;
for ( i = 0; i < map->table_size; ++i )
{
map->table[ i ] = NULL;
}
for ( i = 0; i < old.table_size; ++i )
{
struct ps_map_entry * pos;
for ( pos = old.table[ i ]; pos; )
{
struct ps_map_entry * tmp = pos->next;
unsigned hash_val = list_hash( pos->key );
unsigned bucket = hash_val % map->table_size;
pos->next = map->table[ bucket ];
map->table[ bucket ] = pos;
pos = tmp;
}
}
BJAM_FREE( old.table );
}
static struct ps_map_entry * ps_map_insert(struct ps_map * map, LIST * key)
{
unsigned hash_val = list_hash( key );
unsigned bucket = hash_val % map->table_size;
struct ps_map_entry * pos;
for ( pos = map->table[bucket]; pos ; pos = pos->next )
{
if ( list_equal( pos->key, key ) )
return pos;
}
if ( map->num_elems >= map->table_size )
{
ps_map_rehash( map );
bucket = hash_val % map->table_size;
}
pos = BJAM_MALLOC( sizeof( struct ps_map_entry ) );
pos->next = map->table[bucket];
pos->key = key;
pos->value = 0;
map->table[bucket] = pos;
++map->num_elems;
return pos;
}
static struct ps_map all_property_sets;
LIST * property_set_create( FRAME * frame, int flags )
{
LIST * properties = lol_get( frame->args, 0 );
LIST * sorted = list_sort( properties );
LIST * unique = list_unique( sorted );
struct ps_map_entry * pos = ps_map_insert( &all_property_sets, unique );
list_free( sorted );
if ( pos->value )
{
list_free( unique );
return list_new( object_copy( pos->value ) );
}
else
{
OBJECT * rulename = object_new( "new" );
OBJECT * varname = object_new( "self.raw" );
LIST * val = call_rule( rulename, frame,
list_new( object_new( "property-set" ) ), 0 );
LISTITER iter, end;
object_free( rulename );
pos->value = list_front( val );
var_set( bindmodule( pos->value ), varname, unique, VAR_SET );
object_free( varname );
for ( iter = list_begin( unique ), end = list_end( unique ); iter != end; ++iter )
{
const char * str = object_str( list_item( iter ) );
if ( str[ 0 ] != '<' || ! strchr( str, '>' ) )
{
string message[ 1 ];
string_new( message );
string_append( message, "Invalid property: '" );
string_append( message, str );
string_append( message, "'" );
rulename = object_new( "errors.error" );
call_rule( rulename, frame,
list_new( object_new( message->value ) ), 0 );
/* unreachable */
string_free( message );
object_free( rulename );
}
}
return val;
}
}
/* binary search for the property value */
LIST * property_set_get( FRAME * frame, int flags )
{
OBJECT * varname = object_new( "self.raw" );
LIST * props = var_get( frame->module, varname );
const char * name = object_str( list_front( lol_get( frame->args, 0 ) ) );
size_t name_len = strlen( name );
LISTITER begin, end;
LIST * result = L0;
object_free( varname );
/* Assumes random access */
begin = list_begin( props ), end = list_end( props );
while ( 1 )
{
ptrdiff_t diff = (end - begin);
LISTITER mid = begin + diff / 2;
int res;
if ( diff == 0 )
{
return L0;
}
res = strncmp( object_str( list_item( mid ) ), name, name_len );
if ( res < 0 )
{
begin = mid + 1;
}
else if ( res > 0 )
{
end = mid;
}
else /* We've found the property */
{
/* Find the beginning of the group */
LISTITER tmp = mid;
while ( tmp > begin )
{
--tmp;
res = strncmp( object_str( list_item( tmp ) ), name, name_len );
if ( res != 0 )
{
++tmp;
break;
}
}
begin = tmp;
/* Find the end of the group */
tmp = mid + 1;
while ( tmp < end )
{
res = strncmp( object_str( list_item( tmp ) ), name, name_len );
if ( res != 0 ) break;
++tmp;
}
end = tmp;
break;
}
}
for ( ; begin != end; ++begin )
{
result = list_push_back( result,
object_new( object_str( list_item( begin ) ) + name_len ) );
}
return result;
}
/* binary search for the property value */
LIST * property_set_contains_features( FRAME * frame, int flags )
{
OBJECT * varname = object_new( "self.raw" );
LIST * props = var_get( frame->module, varname );
LIST * features = lol_get( frame->args, 0 );
LIST * result = L0;
LISTITER features_iter = list_begin( features );
LISTITER features_end = list_end( features ) ;
object_free( varname );
for ( ; features_iter != features_end; ++features_iter )
{
const char * name = object_str( list_item( features_iter ) );
size_t name_len = strlen( name );
LISTITER begin, end;
/* Assumes random access */
begin = list_begin( props ), end = list_end( props );
while ( 1 )
{
ptrdiff_t diff = (end - begin);
LISTITER mid = begin + diff / 2;
int res;
if ( diff == 0 )
{
/* The feature is missing */
return L0;
}
res = strncmp( object_str( list_item( mid ) ), name, name_len );
if ( res < 0 )
{
begin = mid + 1;
}
else if ( res > 0 )
{
end = mid;
}
else /* We've found the property */
{
break;
}
}
}
return list_new( object_copy( constant_true ) );
}
void init_property_set()
{
{
char const * args[] = { "raw-properties", "*", 0 };
declare_native_rule( "property-set", "create", args, property_set_create, 1 );
}
{
char const * args[] = { "feature", 0 };
declare_native_rule( "class@property-set", "get", args, property_set_get, 1 );
}
{
char const * args[] = { "features", "*", 0 };
declare_native_rule( "class@property-set", "contains-features", args, property_set_contains_features, 1 );
}
ps_map_init( &all_property_sets );
}
void property_set_done()
{
ps_map_destroy( &all_property_sets );
}