Hi everyone,
Here is an updated version which is slightly more powerful. It allows for labels which allows for recursive types and includes a syntax for signalling whether a field can be one of two types. If you are using mappings as a substitute for classes something like this is probably need to verify that the mapping data is formatted correctly prior to use. You can obviously do this manually but it's probably easier to do this using some sort of automatic procedure like the one shown below.
Regards,
Silenus.
// Silenus 2008-09-08
private mixed _type_check_map(mapping,mapping,mapping,string array);
private
mixed
_type_check(mapping defs, mixed spec, mixed v, string array path)
{
string error;
int flag;
switch( typeof(spec) )
{
case "string":
if( !defs[spec] )
{
if( spec != typeof(v) )
return "*Type mismatch for " + implode(path,"/") +
". Expected: " + spec + ", Got: " + typeof( v ) + ".";
}
else if( stringp( error = _type_check_map(defs,defs[spec] ,v,path) ) )
{
return error;
}
break;
case "function":
if( stringp( error = evaluate(spec,v)))
return "*At : " + implode(path, "/") + ". " + error;
break;
case "mapping":
if( stringp( error = _type_check_map(defs, spec, v, path)))
return error;
break;
case "array":
if( !sizeof(spec) )
error("*Poorly formatted specification mapping. Error at: " + implode(path,"/") + ".");
if( spec[0] == "array of" && sizeof(spec) == 2)
{
if( !arrayp(v) ) return "*Array expected at: " + implode(path,"/") + ".";
foreach(mixed e in v)
{
if(stringp(error = _type_check(defs, spec[1], e, path)))
return "*Array element type error: " + error;
}
}
else if( spec[0] == "mapping of" && sizeof(spec[0]) == 3)
{
if( !mapp(v) ) return "*Mapping expected at: " + implode(path,"/") + ".";
foreach(mixed k, mixed val in v)
{
if(spec[1] != typeof(k))
return "*Type mismatch in key in mapping at: " + implode(path,"/") + ". Expected: " +
spec[1] +", Got: " + typeof(k) + ".";
else if(stringp(error = _type_check(defs, spec[2], val, path)))
return "*Mapping value type error: " + error;
}
}
else
{
error = "";
flag = 0;
foreach(mixed k in spec)
{
if(!stringp( error += _type_check(defs, k, v, path)))
{
flag = 1;
break;
}
}
if ( !flag ) return error;
}
break;
default:
error("*Poorly formatted specification mapping.");
}
return 1;
}
private
mixed
_type_check_map(mapping defs, mapping m1, mapping m2, string array path)
{
string error;
if( (sizeof( keys(m1) - keys(m2) ) || sizeof( keys(m2) - keys(m1) )) && !m1["*"] )
return "*Keys in mapping do not match at level " + implode(path,"/") +
". Missing :" + implode( keys(m1) - keys(m2), ",") + "." +
"Extra :" + implode( keys(m2) - keys(m1), ",") + ".";
if( m1["*"] )
if( sizeof(m1) != 1 )
return "*Specification mapping at level"+ implode(path,"/") +"with wildcard can only contain a single key";
else
{
foreach(mixed k, mixed v in m2)
{
if( stringp( error = _type_check(defs, m1["*"], v, path + ({k}) ) ) )
return error;
}
return 1;
}
foreach(mixed k, mixed v in m1)
{
if( stringp( error = _type_check(defs, v, m2[k], path + ({k}) ) ) )
return error;
}
return 1;
}
/*
Accepts a specification and data mapping and validates the data mapping
conforms to the type specification specified by the specification. This
procedure works for non-recursive (finite) specifications only.
see test() for example
*/
mixed
type_check_map(mapping m1, mapping m2)
{
return _type_check_map(m1["#defines#"], m1["#spec#"], m2, ({}));
}
#ifdef DEBUG
int test()
{
return type_check_map(
// specification
([ "#defines#" : ([]),
"#spec#" :
([ "name" : "string",
"hp" : "int",
"properties" :
([
"no attack" : (: ( (intp($1) && ($1 == 0 || $1 == 1)) ? 1 : "Must be either 0 or 1." ) :),
"race" : "string"
]),
"limbs" : ([ "*" : ([ "max_hp" : "int"]) ])
]) ]),
// data
([ "name" : "Silenus",
"hp" : 100,
"properties" :
([
"no attack" : 1,
"race" : "elf"
]),
"limbs" : ([ "head" : ([ "max_hp" : 100 ]), "torso" : ([ "max_hp" : 100 ]) ])
])
);
}
#endif