Index: cpp/frams/util/extvalue.cpp
===================================================================
--- cpp/frams/util/extvalue.cpp	(revision 326)
+++ cpp/frams/util/extvalue.cpp	(revision 333)
@@ -13,4 +13,5 @@
 #include <common/Convert.h>
 #include <climits>
+#include <errno.h>
 
 #ifndef NO_BARRIER
@@ -290,23 +291,34 @@
 }
 
-static int longsign(paInt x)
-{
-if (x<0) return -1;
-if (x>0) return 1;
-return 0;
-}
-
-static int compareNull(const ExtValue& v)
-{
-switch(v.type)
-	{
-	case TDouble: return v.getDouble()!=0.0;
-	case TInt: return v.getInt()?1:0;
-	case TString: return 1;
-	default: return !v.isNull();
-	}
-}
-
-int ExtValue::compare(const ExtValue& src) const
+static ExtValue::CompareResult longsign(paInt x)
+{
+if (x<0) return ExtValue::ResultLower;
+if (x>0) return ExtValue::ResultHigher;
+return ExtValue::ResultEqual;
+}
+
+static ExtValue::CompareResult compareNull(const ExtValue& v)
+{
+if (v.isNull()) return ExtValue::ResultEqualUnordered;
+if ((v.getType()==TInt)&&(v.getInt()==0)) return ExtValue::ResultUnequal_RelaxedEqual;
+return ExtValue::ResultUnequal_RelaxedUnequal; //comparing anything else with null is valid but null is neither higher nor lower than numbers or strings
+}
+
+static ExtValue::CompareResult compareFloat(double a,double b)
+{
+double t=a-b;
+if (t<0) return ExtValue::ResultLower;
+else if (t>0) return ExtValue::ResultHigher;
+return ExtValue::ResultEqual;
+}
+
+static ExtValue::CompareResult compareString(const SString &a,const SString &b)
+{
+const char* s1=(const char*)a;
+const char* s2=(const char*)b;
+return longsign(strcmp(s1,s2));
+}
+
+ExtValue::CompareResult ExtValue::compare(const ExtValue& src) const
 {
 if (isNull())
@@ -316,36 +328,98 @@
 switch(type)
 	{
+
 	case TInt:
-	{
-	paInt t=src.getInt();
-	if (idata()>0)
-		{if (t>0) return longsign(idata()-t); else return +1;}
-	else
-		{if (t<=0) return longsign(idata()-t); else return -1;}
-	}
+
+		if (src.getType()==TInt)
+			{
+			paInt t=src.getInt();
+			if (idata()>0)
+				{if (t>0) return longsign(idata()-t); else return ResultHigher;}
+			else
+				{if (t<=0) return longsign(idata()-t); else return ResultLower;}
+			}
+		else if (src.getType()==TDouble)
+			return compareFloat((double)idata(),src.getDouble());
+		else
+			return ResultMismatch;//comparing numbers with other things is invalid
+		break;
+
 	case TDouble:
-		{
-		double t=ddata()-src.getDouble();
-		if (t<0) return -1;
-		else if (t>0) return 1;
-		return 0;
-		}
+		if ((src.getType()==TDouble)||(src.getType()==TInt))
+			return compareFloat(getDouble(),src.getDouble());
+		else
+			return ResultMismatch;
+		break;
+
 	case TString:
-	{
-	SString t=src.getString();
-	SString& t2=sdata();
-	const char* s1=(const char*)t2;
-	const char* s2=(const char*)t;
-	return longsign(strcmp(s1,s2));
-	}
+		if (src.getType()==TString)
+			return compareString(sdata(),src.getString());
+		else
+			return ResultMismatch;
+		break;
+
 	case TObj: 
 	{
 	if (src.type==TObj)
-		return !(odata()==src.odata());
-	return 1;
+		return odata()==src.odata() ? ResultEqualUnordered : ResultUnequal_RelaxedUnequal;
+	if ((src.type==TInt)&&(src.getInt()==0))
+		return ResultMismatch_RelaxedUnequal;
+	return ResultMismatch;
 	}
 	default:;
 	}
-return 1;
+return ResultMismatch;
+}
+
+ExtValue::CmpMessageHandler ExtValue::default_cmp_message;
+ExtValue::CmpContext ExtValue::default_cmp_context={NULL,NULL,&default_cmp_message};
+
+const char* ExtValue::cmp_op_names[]={"==","!=",">=","<=",">","<","~=","!~",NULL};
+
+void ExtValue::CmpMessageHandler::cmpMessage(SString& text)
+{
+FMprintf("ExtValue","interpretCompare",FMLV_ERROR,"%s",(const char*)text);
+}
+
+int ExtValue::interpretCompare(CmpOperator op,CompareResult result,CmpContext *context)
+{
+int err=ResultUnequal_RelaxedEqual;//error when ResultUnequal_RelaxedEqual or higher (not comparable)
+int ret=0;
+switch (op)
+	{
+	case CmpEQ: ret=(result==ResultEqual)||(result==ResultEqualUnordered); err=ResultMismatch_RelaxedUnequal; break;
+	case CmpNE: ret=!((result==ResultEqual)||(result==ResultEqualUnordered)); err=ResultMismatch_RelaxedUnequal; break;
+	case CmpGT: ret=(result==ResultHigher); err=ResultEqualUnordered; break;
+	case CmpGE: ret=(result==ResultEqual)||(result==ResultHigher); err=ResultEqualUnordered; break;
+	case CmpLT: ret=(result==ResultLower); err=ResultEqualUnordered; break;
+	case CmpLE: ret=(result==ResultEqual)||(result==ResultLower); err=ResultEqualUnordered; break;
+	case CmpREQ: ret=(result==ResultEqual)||(result==ResultEqualUnordered)||(result==ResultUnequal_RelaxedEqual); err=ResultMismatch; break;
+	case CmpRNE: ret=!((result==ResultEqual)||(result==ResultEqualUnordered)||(result==ResultUnequal_RelaxedEqual)); err=ResultMismatch; break;
+	default:;
+	}
+if (result>=err)
+	{
+	if (context)
+		{
+		SString msg="Type mismatch";
+		if (context->v1 && context->v2)
+			{
+			const char* opname=cmp_op_names[op-CmpFIRST];
+			msg+=": ";
+			if (context->v1->isNull())
+				msg+="null ";
+			else
+				msg+=SString::sprintf("%s '%s' ",(const char*)context->v1->typeDescription(),(const char*)context->v1->getString());
+			msg+=opname;
+			if (context->v2->isNull())
+				msg+=" null";
+			else
+				msg+=SString::sprintf(" %s '%s'",(const char*)context->v2->typeDescription(),(const char*)context->v2->getString());
+			}
+		context->handler->cmpMessage(msg);
+		}
+	ret=-1;
+	}
+return ret;
 }
 
@@ -814,5 +888,8 @@
 		return ret+1;
 	else
+		{
+		FMprintf("ExtValue","deserialize",FMLV_ERROR,"Missing '\"' in string: '%s'",ret);
 		return NULL;
+		}
 	}
 else if (*in=='[')
@@ -832,4 +909,9 @@
 			p=ret;
 			if (*p==',') p++;
+			else if (*p!=']')
+				{
+				FMprintf("ExtValue","deserialize",FMLV_ERROR,"Missing ',' in Vector: '%s'",p);
+				return NULL;
+				}
 			}
 		else
@@ -855,5 +937,5 @@
 		if ((!ret)||(args[1].getType()!=TString)) {p=NULL;break;}
 		p=ret;
-		if (*p!=':') {p=NULL;break;}
+		if (*p!=':') {FMprintf("ExtValue","deserialize",FMLV_ERROR,"Missing ':' in Dictionary: '%s'",p);p=NULL;break;}
 		p++;
 		ret=args[0].deserialize(p);
@@ -862,4 +944,9 @@
 		dic->p_set(args,&dummy_ret);
 		if (*p==',') p++;
+		else if (*p!='}')
+			{
+			FMprintf("ExtValue","deserialize",FMLV_ERROR,"Missing ',' in Dictionary: '%s'",p);
+			return NULL;
+			}
 		}
 	setObject(o);
@@ -899,4 +986,5 @@
 			}
 		}
+	FMprintf("ExtValue","deserialize",FMLV_ERROR,"Invalid reference: '%s'",in-1);
 	return NULL;
 	}
@@ -948,4 +1036,5 @@
 	return ret;
 	}
+FMprintf("ExtValue","deserialize",FMLV_ERROR,"Bad syntax: '%s'",in);
 setEmpty();
 return NULL;
@@ -968,5 +1057,7 @@
 ExtValue ExtValue::getExtType()
 {
-if (getType()!=TObj) return ExtValue((paInt)getType());
+static const char* typenames[]={"null","int","float","string","","invalid"};
+if (getType()!=TObj)
+	return ExtValue(typenames[(int)getType()]);
 ExtObject& o=odata();
 return ExtValue(SString(o.isEmpty()?"":o.interfaceName()));
Index: cpp/frams/util/extvalue.h
===================================================================
--- cpp/frams/util/extvalue.h	(revision 326)
+++ cpp/frams/util/extvalue.h	(revision 333)
@@ -136,5 +136,30 @@
 	static ExtValue invalid() { ExtValue v; v.setInvalid(); return v; }
 	static const ExtValue& empty() { static const ExtValue v; return v; }
-	int compare(const ExtValue& src) const;
+	static const ExtValue& zero() { static const ExtValue v(0); return v; }
+
+	enum CompareResult
+	{
+	 ResultLower=-1, ResultEqual=0, ResultHigher=1,
+	 ResultEqualUnordered,
+	 ResultUnequal_RelaxedEqual,
+	 ResultUnequal_RelaxedUnequal,
+	 ResultMismatch_RelaxedUnequal,
+	 ResultMismatch
+	};
+	// performs all script value comparisons.
+	// relaxed comparison (internal use only, not available in scripts) works like regular == with additional null~=0, notnull!~0
+	// and is used for pseudo-boolean conversion allowing for expressions like "if (null) ..."
+	CompareResult compare(const ExtValue& src) const;
+
+	enum CmpOperator { CmpFIRST,CmpEQ=CmpFIRST,CmpNE,CmpGE,CmpLE,CmpGT,CmpLT,/*relaxed (not)equal*/CmpREQ,CmpRNE };
+	static const char* cmp_op_names[];
+	class CmpMessageHandler { public: virtual void cmpMessage(SString& text); };
+	struct CmpContext { const ExtValue *v1,*v2; CmpMessageHandler *handler; };
+	static CmpMessageHandler default_cmp_message;
+	static CmpContext default_cmp_context;
+	// interpret compare() result, optional context controls error messages
+	// @return 0=false, 1=true, -1=undefined (null in script)
+	static int interpretCompare(CmpOperator op,CompareResult result,CmpContext *context=&default_cmp_context);
+
 	int operator==(const ExtValue& src) const;
 	void operator+=(const ExtValue& src);
@@ -154,5 +179,5 @@
 	void setInvalid() { setEmpty(); type = TInvalid; }
 	bool makeUnique() { return (type == TObj) && odata().makeUnique(); } //< @return false if nothing has changed
-	ExtPType getType() { return type; }
+	ExtPType getType() const { return type; }
 	void *getObjectTarget(const char* classname, bool warn = true) const;
 	void setInt(paInt v) { if (type != TInt) setri(v); else idata() = v; }
