From f2c14abcdd49cb8a75590b49aa4df0f2b142281b Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Sat, 10 May 2003 15:03:54 +0000 Subject: [PATCH] Type checking for argument lists [SVN r18378] --- src/engine/compile.c | 169 +++++++++++++++++++++++++++++++++---------- src/kernel/class.jam | 41 ++++++++++- 2 files changed, 172 insertions(+), 38 deletions(-) diff --git a/src/engine/compile.c b/src/engine/compile.c index ac0dd70af..4b368890e 100644 --- a/src/engine/compile.c +++ b/src/engine/compile.c @@ -581,6 +581,91 @@ static void argument_error( char* message, RULE* rule, FRAME* frame, LIST* arg ) exit(1); } +/* define delimiters for type check elements in argument lists (and + * return type specifications, eventually) + */ +# define TYPE_OPEN_DELIM '[' +# define TYPE_CLOSE_DELIM ']' + +/* is_type_name - true iff the given string represents a type check + * specification + */ +static int +is_type_name( char* s ) +{ + return s[0] == TYPE_OPEN_DELIM + && s[strlen(s) - 1] == TYPE_CLOSE_DELIM; +} + +/* + * arg_modifier - if the next element of formal is a single character, + * return that; return 0 otherwise. Used to extract "*+?" modifiers + * from argument lists. + */ +static char +arg_modifier( LIST* formal ) +{ + if ( formal->next ) + { + char *next = formal->next->string; + if ( next && next[0] != 0 && next[1] == 0 ) + return next[0]; + } + return 0; +} + +/* + * type_check - checks that each element of values satisfies the + * requirements of type_name. + * + * caller - the frame of the rule calling the rule whose + * arguments are being checked + * + * called - the rule being called + * + * arg_name - a list element containing the name of the argument + * being checked + */ +static void +type_check( char* type_name, LIST *values, FRAME* caller, RULE* called, LIST* arg_name ) +{ + static module *typecheck = 0; + + /* if nothing to check, bail now */ + if ( !values || !type_name ) + return; + + if ( !typecheck ) + typecheck = bindmodule(".typecheck"); + + /* if the checking rule can't be found, also bail */ + { + RULE checker_, *checker = &checker_; + + checker->name = type_name; + if ( !hashcheck( typecheck->rules, (HASHDATA**)&checker ) ) + return; + } + + while ( values != 0 ) + { + LIST *error; + FRAME frame[1]; + frame_init( frame ); + frame->module = typecheck; + frame->prev = caller; + + /* Prepare the argument list */ + lol_add( frame->args, list_new( L0, values->string ) ); + error = evaluate_rule( type_name, frame ); + + if ( error ) + argument_error( error->string, called, caller, arg_name ); + + frame_free( frame ); + values = values->next; + } +} /* * collect_arguments() - local argument checking and collection @@ -601,54 +686,64 @@ collect_arguments( RULE* rule, FRAME* frame ) int n; for ( n = 0; n < max ; ++n ) { - LIST *formal = lol_get( all_formal, n ); LIST *actual = lol_get( all_actual, n ); + char *type_name = 0; - while ( formal ) + LIST *formal; + for ( formal = lol_get( all_formal, n ); formal; formal = formal->next ) { char* name = formal->string; - char modifier = 0; - LIST* value = 0; - /* Stop now if a variable number of arguments are specified */ - if ( name[0] == '*' && name[1] == 0 ) - return locals; - - if ( formal->next ) + if ( is_type_name(name) ) { - char *next = formal->next->string; - if ( next && next[0] != 0 && next[1] == 0 ) - modifier = next[0]; - } - - if ( !actual && modifier != '?' && modifier != '*' ) - { - argument_error( "missing argument", rule, frame, formal ); - } + if ( type_name ) + argument_error( "missing argument name before type name:", rule, frame, formal ); + + if ( !formal->next ) + argument_error( "missing argument name after type name:", rule, frame, formal ); - switch ( modifier ) + type_name = formal->string; + } + else { - case '+': - case '*': - value = list_copy( 0, actual ); - actual = 0; - /* skip an extra element for the modifier */ - formal = formal->next; - break; - case '?': - /* skip an extra element for the modifier */ - formal = formal->next; - /* fall through */ - default: - if ( actual ) /* in case actual is missing */ + LIST* value = 0; + char modifier; + LIST* arg_name = formal; /* hold the argument name for type checking */ + + /* Stop now if a variable number of arguments are specified */ + if ( name[0] == '*' && name[1] == 0 ) + return locals; + + modifier = arg_modifier( formal ); + + if ( !actual && modifier != '?' && modifier != '*' ) + argument_error( "missing argument", rule, frame, formal ); + + switch ( modifier ) { - value = list_new( 0, actual->string ); - actual = actual->next; + case '+': + case '*': + value = list_copy( 0, actual ); + actual = 0; + /* skip an extra element for the modifier */ + formal = formal->next; + break; + case '?': + /* skip an extra element for the modifier */ + formal = formal->next; + /* fall through */ + default: + if ( actual ) /* in case actual is missing */ + { + value = list_new( 0, actual->string ); + actual = actual->next; + } } - } - locals = addsettings( locals, 0, name, value ); - formal = formal->next; + locals = addsettings( locals, 0, name, value ); + type_check( type_name, value, frame, rule, arg_name ); + type_name = 0; + } } if ( actual ) diff --git a/src/kernel/class.jam b/src/kernel/class.jam index 73abe23e3..65259b97e 100644 --- a/src/kernel/class.jam +++ b/src/kernel/class.jam @@ -137,6 +137,7 @@ rule class ( } classes += $(name) ; + IMPORT class : typecheck : .typecheck : [$(name)] ; # Each class is assigned a new module which acts as a namespace # for its rules and normal instance variables. @@ -284,12 +285,21 @@ rule is-a ( : type # The type to test for. ) { - if [ MATCH "(object\\()[^@]+\\)@.*" : $(instance) ] + if [ is-instance $(instance) ] { return [ class.is-derived [ modules.peek $(instance) : __class__ ] : $(type) ] ; } } +local rule typecheck ( x ) +{ + local class-name = [ MATCH "^\\[(.*)\\]$" : [ BACKTRACE 1 ] ] ; + if ! [ is-a $(x) : $(class-name) ] + { + return "Expected an instance of "$(class-name)" but got \""$(x)"\" for argument" ; + } +} + rule __test__ ( ) { module class.__test__ @@ -395,6 +405,14 @@ rule __test__ ( ) } class derived2 : myclass ; + local rule derived2a ( ) + { + derived2.__init__ ; + } + class derived2a : derived2 ; + + local rule expect_derived2 ( [derived2] x ) { } + local rule bad_subclass ( ) { # fails to call base class __init__ function @@ -405,6 +423,27 @@ rule __test__ ( ) local b = [ new derived1 4 ] ; local c = [ new derived2 ] ; local d = [ new derived2 ] ; + local e = [ new derived2a ] ; + + expect_derived2 $(d) ; + expect_derived2 $(e) ; + + # argument checking is set up to call exit(1) directly on + # failure, and we can't hijack that with try, so we'd better + # not do this test by default. We could fix this by having + # errors look up and invoke the EXIT rule instead; EXIT can be + # hijacked ;-) + if --fail-typecheck in [ modules.peek : ARGV ] + { + try ; + { + expect_derived2 $(a) ; + } + catch + "Expected an instance of derived2 but got" instead + ; + } + try ; {