| /* |
| * 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 ); |
| } |