Overriding methods with typedef arguments.

Veracity

Developer
Staff member
I have this data type:

Code:
typedef string [int] beach_layout;

If I try this:

Code:
string to_string( beach_layout layout )
{
...
}
I get this:

Code:
[color=red]Function 'to_string( string [int] )' defined multiple times (BeachComber.ash, line 226)[/color]
That's because an ASH map has a to_string() method:

Code:
[color=green]> ash string[int]a = { 1:"abc" };a.to_string()[/color]

Returned: aggregate string [int]
But I'm not actually trying to override the built-in array's version of to_string. I am trying to define one for my typedef.

Almost everywhere, a typedef is interchangeable with its base type. I'm wondering if it'd be worth it to make two passes over the symbol table when looking up the matching function when you pass in an object defined by a typedef: an exact match for the typedef and a match for the base type?

That grows exponentially with the number of typedefs you include in your argument list: 2, 2*2, 2*2*2, etc.
 

xKiv

Active member
That grows exponentially with the number of typedefs you include in your argument list: 2, 2*2, 2*2*2, etc.

How are you doing the symbol table lookup? As far as I can tell you are already walking the entire list of defined functions, so couldn't you "just" count the maximum matching "specific" arguments (typedefs) and then pick one of the ones that have the maximal count?
IOW, with
Code:
f(string[int], string[int], string[int]) {}; // variant 1
f(beach_layout, string[int], string[int]) {}; // variant 2
f(string[int], beach_layout, beach_layout) {}; // variant 3
f(beach_layout, string[int], beach_layout) {}; // variant 4
f(string[int], string[int], beach_layout) {}; // variant 5
defining
Code:
string [int] a;
beach_layout bl;
and calling f(bl, a, a); you would pick variant 2 (3,4,5 do not match because they require third argument to be beach_layout; 1 has too few matches)

More interesting would be a case where two different "degradations" match, most trivial case:
Code:
f(beach_layout, string[int]) {};
f(string[int], beach_layout) {};
f(bl, bl); // which variant to pick here? based on order of definition, or make it a compile-time error?
 

Veracity

Developer
Staff member
It's been a long time since I looked closely at how we search the symbol table. I am sure that you are correct that we make a single pass through the (nested scope) symbol tables to pick the "correct" match. The error message I cited was at compile time, when we defined a function with a typedef parameter.

Hmm. It appears the function I am overriding is this one from vprops.ash:

Code:
string to_string( string [int] list_val )
{
    return to_string( list_val, "|" );
}
Since importing vcon.ash is like literally inserting the text of the file into script with the "import", its global symbols go into the global symbol table of the importing script, defining "string to_string( beach_layout layout )" is, in fact, overriding a function in the same scope. I can define to_string inside another scope like this:

Code:
import <BeachComber.ash>;

beach_layout test()
{
    string to_string( beach_layout layout )
    {
	return layout.beach_layout_to_string();
    }

    beach_layout layout = to_beach_layout( "3:rrrrrrrtrr,4:rrtrrrrrrc,5:trrcrrrrrr,6:trrrrrrrtr,7:rrrrrrrrrr,8:rrrrrrrrrr,9:rcrrrrrrrr,10:rrrrrrrrrr" );
    string [int] raw = layout;
    print( raw.to_string() );
    print( layout.to_string() );
    return layout;
}
Which results in this:

Code:
[color=green]> ash import<bct.ash>;test();[/color]

3:rrrrrrrtrr,4:rrtrrrrrrc,5:trrcrrrrrr,6:trrrrrrrtr,7:rrrrrrrrrr,8:rrrrrrrrrr,9:rcrrrrrrrr,10:rrrrrrrrrr
3:rrrrrrrtrr,4:rrtrrrrrrc,5:trrcrrrrrr,6:trrrrrrrtr,7:rrrrrrrrrr,8:rrrrrrrrrr,9:rcrrrrrrrr,10:rrrrrrrrrr
Returned: aggregate string [int]
3 => rrrrrrrtrr
4 => rrtrrrrrrc
5 => trrcrrrrrr
6 => trrrrrrrtr
7 => rrrrrrrrrr
8 => rrrrrrrrrr
9 => rcrrrrrrrr
10 => rrrrrrrrrr
Notice that the to_string with a "beach_layout" argument is being used for "raw" - which is not a typedef variable, but is the raw underlying map type.

So this would require two changes:

1) At function definition time, don't reduce typedefs to base types when checking for multiple definitions in a scope.
2) At function call time, make one pass over nested symbol tables, as before, but pick the "best" match, given typedefs. If there are multiple matches - per your last example - probably best to have a compile error, rather than just picking one and having the coder wonder what happened. Although they deserve to be confused, in my opinion. :)
 

Veracity

Developer
Staff member
Well... I have an implemntation which does what I want. It was an (intentional) side effect of changes I'm making to make varargs work better.

I whacked the code in the ASH Parser which looks up which named defined function matches a function call with a set of typed parameters.
I make repeated passes over the symbol table doing 3 kinds of checks:

EXACT - the type of the parameter == the type of the value
BASE - the base type of the parameter == the base type of the value (which is what it did before)
COERCE - is the type of the value coerceable to the type of the parameter. (ditto)

I do this looking for functions with and without varargs
I do this for the current scope and for the functions in the runtime library.

End result, I have vargs working for both user defined and built-in functions, which can be invoked either with an array of values or as multiple values which are made into an array.
And the EXACT check does what I wanted for typedefs.

Code:
typedef string [int] beach_layout;

string to_string( beach_layout layout )
{
    buffer value;
    foreach row, squares in layout {
	if ( value.length() > 0 ) {
	    value.append( "," );
	}
	value.append( row );
	value.append( ":" );
	value.append( squares );
    }
    return value.to_string();
}

string get_layout()
{
    return get_property( "_beachLayout" );
}

beach_layout to_beach_layout( string squares )
{
    beach_layout layout;
    foreach i, row_data in squares.split_string( "," ) {
	int colon = row_data.index_of( ":" );
	if ( colon != -1 ) {
	    int row = row_data.substring( 0, colon ).to_int();
	    string squares = row_data.substring( colon + 1 );
	    layout[ row ] = squares;
	}
    }
    return layout;
}

beach_layout get_beach_layout()
{
    return get_layout().to_beach_layout();
}

print( get_layout() );
print( get_layout().to_beach_layout().to_string() );
yields:

Code:
[color=green]> tdtest.ash[/color]

3:rrrrrrcrrr,4:rrrrrrrrrr,5:rrrrrrrrcr,6:rrrrrrrrrr,7:rrrrrrrrrr,8:crrrrrrrrr,9:rrrrrrrrrr,10:rrrrrrrrrr
3:rrrrrrcrrr,4:rrrrrrrrrr,5:rrrrrrrrcr,6:rrrrrrrrrr,7:rrrrrrrrrr,8:crrrrrrrrr,9:rrrrrrrrrr,10:rrrrrrrrrr
I'll run some of my big scripts after rollover, and if nothing broke, I'll submit it and go back to working on other stuff for varargs...
 
Top