1 // SDLang-D
2 // Written in the D programming language.
3 
4 module sdlang.token;
5 
6 import std.array;
7 import std.base64;
8 import std.conv;
9 import std.datetime;
10 import std.meta;
11 import std.range;
12 import std.string;
13 import std.traits;
14 import std.typetuple;
15 import std.variant;
16 
17 import sdlang.exception;
18 import sdlang.symbol;
19 import sdlang.util;
20 
21 /// DateTime doesn't support milliseconds, but SDLang's "Date Time" type does.
22 /// So this is needed for any SDL "Date Time" that doesn't include a time zone.
23 struct DateTimeFrac
24 {
25 	DateTime dateTime;
26 	Duration fracSecs;
27 	deprecated("Use fracSecs instead.") {
28 		@property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); }
29 		@property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; }
30 	}
31 }
32 
33 /++
34 If a "Date Time" literal in the SDL file has a time zone that's not found in
35 your system, you get one of these instead of a SysTime. (Because it's
36 impossible to indicate "unknown time zone" with `std.datetime.TimeZone`.)
37 
38 The difference between this and `DateTimeFrac` is that `DateTimeFrac`
39 indicates that no time zone was specified in the SDL at all, whereas
40 `DateTimeFracUnknownZone` indicates that a time zone was specified but
41 data for it could not be found on your system.
42 +/
43 struct DateTimeFracUnknownZone
44 {
45 	DateTime dateTime;
46 	Duration fracSecs;
47 	deprecated("Use fracSecs instead.") {
48 		@property FracSec fracSec() const { return FracSec.from!"hnsecs"(fracSecs.total!"hnsecs"); }
49 		@property void fracSec(FracSec v) { fracSecs = v.hnsecs.hnsecs; }
50 	}
51 	string timeZone;
52 
53 	bool opEquals(const DateTimeFracUnknownZone b) const
54 	{
55 		return opEquals(b);
56 	}
57 	bool opEquals(ref const DateTimeFracUnknownZone b) const
58 	{
59 		return
60 			this.dateTime == b.dateTime &&
61 			this.fracSecs  == b.fracSecs  &&
62 			this.timeZone == b.timeZone;
63 	}
64 }
65 
66 /++
67 SDLang's datatypes map to D's datatypes as described below.
68 Most are straightforward, but take special note of the date/time-related types.
69 
70 ---------------------------------------------------------------
71 Boolean:                       bool
72 Null:                          typeof(null)
73 Unicode Character:             dchar
74 Double-Quote Unicode String:   string
75 Raw Backtick Unicode String:   string
76 Integer (32 bits signed):      int
77 Long Integer (64 bits signed): long
78 Float (32 bits signed):        float
79 Double Float (64 bits signed): double
80 Decimal (128+ bits signed):    real
81 Binary (standard Base64):      ubyte[]
82 Time Span:                     Duration
83 
84 Date (with no time at all):           Date
85 Date Time (no timezone):              DateTimeFrac
86 Date Time (with a known timezone):    SysTime
87 Date Time (with an unknown timezone): DateTimeFracUnknownZone
88 ---------------------------------------------------------------
89 +/
90 alias ValueTypes = TypeTuple!(
91 	bool,
92 	string, dchar,
93 	int, long,
94 	float, double, real,
95 	Date, DateTimeFrac, SysTime, DateTimeFracUnknownZone, Duration,
96 	ubyte[],
97 	typeof(null),
98 );
99 
100 alias Value = Algebraic!( ValueTypes ); ///ditto
101 enum isValueType(T) = staticIndexOf!(T, ValueTypes) != -1;
102 
103 enum isSink(T) =
104 	isOutputRange!T &&
105 	is(ElementType!(T)[] == string);
106 
107 string toSDLString(T)(T value) if(is(T==Value) || isValueType!T)
108 {
109 	Appender!string sink;
110 	toSDLString(value, sink);
111 	return sink.data;
112 }
113 
114 /// Throws SDLangException if value is infinity, -infinity or NaN, because
115 /// those are not currently supported by the SDLang spec.
116 void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char))
117 {
118 	foreach(T; ValueTypes)
119 	{
120 		if(value.type == typeid(T))
121 		{
122 			toSDLString( value.get!T(), sink );
123 			return;
124 		}
125 	}
126 	
127 	throw new Exception("Internal SDLang-D error: Unhandled type of Value. Contains: "~value.toString());
128 }
129 
130 @("toSDLString on infinity and NaN")
131 unittest
132 {
133 	import std.exception;
134 	
135 	auto floatInf    = float.infinity;
136 	auto floatNegInf = -float.infinity;
137 	auto floatNaN    = float.nan;
138 
139 	auto doubleInf    = double.infinity;
140 	auto doubleNegInf = -double.infinity;
141 	auto doubleNaN    = double.nan;
142 
143 	auto realInf    = real.infinity;
144 	auto realNegInf = -real.infinity;
145 	auto realNaN    = real.nan;
146 
147 	assertNotThrown( toSDLString(0.0F) );
148 	assertNotThrown( toSDLString(0.0)  );
149 	assertNotThrown( toSDLString(0.0L) );
150 	
151 	assertThrown!ValidationException( toSDLString(floatInf) );
152 	assertThrown!ValidationException( toSDLString(floatNegInf) );
153 	assertThrown!ValidationException( toSDLString(floatNaN) );
154 
155 	assertThrown!ValidationException( toSDLString(doubleInf) );
156 	assertThrown!ValidationException( toSDLString(doubleNegInf) );
157 	assertThrown!ValidationException( toSDLString(doubleNaN) );
158 
159 	assertThrown!ValidationException( toSDLString(realInf) );
160 	assertThrown!ValidationException( toSDLString(realNegInf) );
161 	assertThrown!ValidationException( toSDLString(realNaN) );
162 	
163 	assertThrown!ValidationException( toSDLString(Value(floatInf)) );
164 	assertThrown!ValidationException( toSDLString(Value(floatNegInf)) );
165 	assertThrown!ValidationException( toSDLString(Value(floatNaN)) );
166 
167 	assertThrown!ValidationException( toSDLString(Value(doubleInf)) );
168 	assertThrown!ValidationException( toSDLString(Value(doubleNegInf)) );
169 	assertThrown!ValidationException( toSDLString(Value(doubleNaN)) );
170 
171 	assertThrown!ValidationException( toSDLString(Value(realInf)) );
172 	assertThrown!ValidationException( toSDLString(Value(realNegInf)) );
173 	assertThrown!ValidationException( toSDLString(Value(realNaN)) );
174 }
175 
176 void toSDLString(Sink)(typeof(null) value, ref Sink sink) if(isOutputRange!(Sink,char))
177 {
178 	sink.put("null");
179 }
180 
181 void toSDLString(Sink)(bool value, ref Sink sink) if(isOutputRange!(Sink,char))
182 {
183 	sink.put(value? "true" : "false");
184 }
185 
186 //TODO: Figure out how to properly handle strings/chars containing lineSep or paraSep
187 void toSDLString(Sink)(string value, ref Sink sink) if(isOutputRange!(Sink,char))
188 {
189 	sink.put('"');
190 	
191 	// This loop is UTF-safe
192 	foreach(char ch; value)
193 	{
194 		if     (ch == '\n') sink.put(`\n`);
195 		else if(ch == '\r') sink.put(`\r`);
196 		else if(ch == '\t') sink.put(`\t`);
197 		else if(ch == '\"') sink.put(`\"`);
198 		else if(ch == '\\') sink.put(`\\`);
199 		else
200 			sink.put(ch);
201 	}
202 
203 	sink.put('"');
204 }
205 
206 void toSDLString(Sink)(dchar value, ref Sink sink) if(isOutputRange!(Sink,char))
207 {
208 	sink.put('\'');
209 	
210 	if     (value == '\n') sink.put(`\n`);
211 	else if(value == '\r') sink.put(`\r`);
212 	else if(value == '\t') sink.put(`\t`);
213 	else if(value == '\'') sink.put(`\'`);
214 	else if(value == '\\') sink.put(`\\`);
215 	else
216 		sink.put(value);
217 
218 	sink.put('\'');
219 }
220 
221 void toSDLString(Sink)(int value, ref Sink sink) if(isOutputRange!(Sink,char))
222 {
223 	sink.put( "%s".format(value) );
224 }
225 
226 void toSDLString(Sink)(long value, ref Sink sink) if(isOutputRange!(Sink,char))
227 {
228 	sink.put( "%sL".format(value) );
229 }
230 
231 private void checkUnsupportedFloatingPoint(T)(T value) if(isFloatingPoint!T)
232 {
233 	import std.exception;
234 	import std.math;
235 	
236 	enforce!ValidationException(
237 		!isInfinity(value),
238 		"SDLang does not currently support infinity for floating-point types"
239 	);
240 
241 	enforce!ValidationException(
242 		!isNaN(value),
243 		"SDLang does not currently support NaN for floating-point types"
244 	);
245 }
246 
247 private string trimmedDecimal(string str)
248 {
249 	Appender!string sink;
250 	trimmedDecimal(str, sink);
251 	return sink.data;
252 }
253 
254 private void trimmedDecimal(Sink)(string str, ref Sink sink) if(isOutputRange!(Sink,char))
255 {
256 	// Special case
257 	if(str == ".")
258 	{
259 		sink.put("0");
260 		return;
261 	}
262 
263 	for(auto i=str.length-1; i>0; i--)
264 	{
265 		if(str[i] == '.')
266 		{
267 			// Trim up to here, PLUS trim trailing '.'
268 			sink.put(str[0..i]);
269 			return;
270 		}
271 		else if(str[i] != '0')
272 		{
273 			// Trim up to here
274 			sink.put(str[0..i+1]);
275 			return;
276 		}
277 	}
278 	
279 	// Nothing to trim
280 	sink.put(str);
281 }
282 
283 @("trimmedDecimal")
284 unittest
285 {
286 	assert(trimmedDecimal("123.456000") == "123.456");
287 	assert(trimmedDecimal("123.456")    == "123.456");
288 	assert(trimmedDecimal("123.000")    == "123");
289 	assert(trimmedDecimal("123.0")      == "123");
290 	assert(trimmedDecimal("123.")       == "123");
291 	assert(trimmedDecimal("123")        == "123");
292 	assert(trimmedDecimal("1.")         == "1");
293 	assert(trimmedDecimal("1")          == "1");
294 	assert(trimmedDecimal("0")          == "0");
295 	assert(trimmedDecimal(".")          == "0");
296 }
297 
298 void toSDLString(Sink)(float value, ref Sink sink) if(isOutputRange!(Sink,char))
299 {
300 	checkUnsupportedFloatingPoint(value);
301 	"%.10f".format(value).trimmedDecimal(sink);
302 	sink.put("F");
303 }
304 
305 void toSDLString(Sink)(double value, ref Sink sink) if(isOutputRange!(Sink,char))
306 {
307 	checkUnsupportedFloatingPoint(value);
308 	"%.30f".format(value).trimmedDecimal(sink);
309 	sink.put("D");
310 }
311 
312 void toSDLString(Sink)(real value, ref Sink sink) if(isOutputRange!(Sink,char))
313 {
314 	checkUnsupportedFloatingPoint(value);
315 	"%.90f".format(value).trimmedDecimal(sink);
316 	sink.put("BD");
317 }
318 
319 // Regression test: Issue #50
320 @("toSDLString: No scientific notation")
321 unittest
322 {
323 	import std.algorithm, sdlang.parser;
324 	auto tag = parseSource(`
325 	foo \
326 		420000000000000000000f \
327 		42000000000000000000000000000000000000d \
328 		420000000000000000000000000000000000000000000000000000000000000bd \
329 	`).getTag("foo");
330 	import std.stdio;
331 	writeln(tag.values[0].toSDLString);
332 	writeln(tag.values[1].toSDLString);
333 	writeln(tag.values[2].toSDLString);
334 	
335 	assert(!tag.values[0].toSDLString.canFind("+"));
336 	assert(!tag.values[0].toSDLString.canFind("-"));
337 	
338 	assert(!tag.values[1].toSDLString.canFind("+"));
339 	assert(!tag.values[1].toSDLString.canFind("-"));
340 	
341 	assert(!tag.values[2].toSDLString.canFind("+"));
342 	assert(!tag.values[2].toSDLString.canFind("-"));
343 }
344 
345 void toSDLString(Sink)(Date value, ref Sink sink) if(isOutputRange!(Sink,char))
346 {
347 	sink.put(to!string(value.year));
348 	sink.put('/');
349 	sink.put(to!string(cast(int)value.month));
350 	sink.put('/');
351 	sink.put(to!string(value.day));
352 }
353 
354 void toSDLString(Sink)(DateTimeFrac value, ref Sink sink) if(isOutputRange!(Sink,char))
355 {
356 	toSDLString(value.dateTime.date, sink);
357 	sink.put(' ');
358 	sink.put("%.2s".format(value.dateTime.hour));
359 	sink.put(':');
360 	sink.put("%.2s".format(value.dateTime.minute));
361 	
362 	if(value.dateTime.second != 0)
363 	{
364 		sink.put(':');
365 		sink.put("%.2s".format(value.dateTime.second));
366 	}
367 
368 	if(value.fracSecs != 0.msecs)
369 	{
370 		sink.put('.');
371 		sink.put("%.3s".format(value.fracSecs.total!"msecs"));
372 	}
373 }
374 
375 void toSDLString(Sink)(SysTime value, ref Sink sink) if(isOutputRange!(Sink,char))
376 {
377 	auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs);
378 	toSDLString(dateTimeFrac, sink);
379 	
380 	sink.put("-");
381 	
382 	auto tzString = value.timezone.name;
383 	
384 	// If name didn't exist, try abbreviation.
385 	// Note that according to std.datetime docs, on Windows the
386 	// stdName/dstName may not be properly abbreviated.
387 	version(Windows) {} else
388 	if(tzString == "")
389 	{
390 		auto tz = value.timezone;
391 		auto stdTime = value.stdTime;
392 		
393 		if(tz.hasDST())
394 			tzString = tz.dstInEffect(stdTime)? tz.dstName : tz.stdName;
395 		else
396 			tzString = tz.stdName;
397 	}
398 	
399 	if(tzString == "")
400 	{
401 		auto offset = value.timezone.utcOffsetAt(value.stdTime);
402 		sink.put("GMT");
403 
404 		if(offset < seconds(0))
405 		{
406 			sink.put("-");
407 			offset = -offset;
408 		}
409 		else
410 			sink.put("+");
411 		
412 		sink.put("%.2s".format(offset.split.hours));
413 		sink.put(":");
414 		sink.put("%.2s".format(offset.split.minutes));
415 	}
416 	else
417 		sink.put(tzString);
418 }
419 
420 void toSDLString(Sink)(DateTimeFracUnknownZone value, ref Sink sink) if(isOutputRange!(Sink,char))
421 {
422 	auto dateTimeFrac = DateTimeFrac(value.dateTime, value.fracSecs);
423 	toSDLString(dateTimeFrac, sink);
424 	
425 	sink.put("-");
426 	sink.put(value.timeZone);
427 }
428 
429 void toSDLString(Sink)(Duration value, ref Sink sink) if(isOutputRange!(Sink,char))
430 {
431 	if(value < seconds(0))
432 	{
433 		sink.put("-");
434 		value = -value;
435 	}
436 	
437 	auto days = value.total!"days"();
438 	if(days != 0)
439 	{
440 		sink.put("%s".format(days));
441 		sink.put("d:");
442 	}
443 
444 	sink.put("%.2s".format(value.split.hours));
445 	sink.put(':');
446 	sink.put("%.2s".format(value.split.minutes));
447 	sink.put(':');
448 	sink.put("%.2s".format(value.split.seconds));
449 
450 	if(value.split.msecs != 0)
451 	{
452 		sink.put('.');
453 		sink.put("%.3s".format(value.split.msecs));
454 	}
455 }
456 
457 void toSDLString(Sink)(ubyte[] value, ref Sink sink) if(isOutputRange!(Sink,char))
458 {
459 	sink.put('[');
460 	sink.put( Base64.encode(value) );
461 	sink.put(']');
462 }
463 
464 /// This only represents terminals. Nonterminals aren't
465 /// constructed since the AST is directly built during parsing.
466 struct Token
467 {
468 	Symbol symbol = sdlang.symbol.symbol!"Error"; /// The "type" of this token
469 	Location location;
470 	Value value; /// Only valid when `symbol` is `symbol!"Value"`, otherwise null
471 	string data; /// Original text from source
472 
473 	@disable this();
474 	this(Symbol symbol, Location location, Value value=Value(null), string data=null)
475 	{
476 		this.symbol   = symbol;
477 		this.location = location;
478 		this.value    = value;
479 		this.data     = data;
480 	}
481 	
482 	/// Tokens with differing symbols are always unequal.
483 	/// Tokens with differing values are always unequal.
484 	/// Tokens with differing Value types are always unequal.
485 	/// Member `location` is always ignored for comparison.
486 	/// Member `data` is ignored for comparison *EXCEPT* when the symbol is Ident.
487 	bool opEquals(Token b)
488 	{
489 		return opEquals(b);
490 	}
491 	bool opEquals(ref Token b) ///ditto
492 	{
493 		if(
494 			this.symbol     != b.symbol     ||
495 			this.value.type != b.value.type ||
496 			this.value      != b.value
497 		)
498 			return false;
499 		
500 		if(this.symbol == .symbol!"Ident")
501 			return this.data == b.data;
502 		
503 		return true;
504 	}
505 	
506 	bool matches(string symbolName)()
507 	{
508 		return this.symbol == .symbol!symbolName;
509 	}
510 }
511 
512 @("sdlang token")
513 unittest
514 {
515 	auto loc  = Location("", 0, 0, 0);
516 	auto loc2 = Location("a", 1, 1, 1);
517 
518 	assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc ));
519 	assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc2));
520 	assert(Token(symbol!":",  loc) == Token(symbol!":",  loc ));
521 	assert(Token(symbol!"EOL",loc) != Token(symbol!":",  loc ));
522 	assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),"\n"));
523 
524 	assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),";" ));
525 	assert(Token(symbol!"EOL",loc,Value(null),"A" ) == Token(symbol!"EOL",loc,Value(null),"B" ));
526 	assert(Token(symbol!":",  loc,Value(null),"A" ) == Token(symbol!":",  loc,Value(null),"BB"));
527 	assert(Token(symbol!"EOL",loc,Value(null),"A" ) != Token(symbol!":",  loc,Value(null),"A" ));
528 
529 	assert(Token(symbol!"Ident",loc,Value(null),"foo") == Token(symbol!"Ident",loc,Value(null),"foo"));
530 	assert(Token(symbol!"Ident",loc,Value(null),"foo") != Token(symbol!"Ident",loc,Value(null),"BAR"));
531 
532 	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"foo"));
533 	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc2,Value(null),"foo"));
534 	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"BAR"));
535 	assert(Token(symbol!"Value",loc,Value(   7),"foo") == Token(symbol!"Value",loc, Value(   7),"BAR"));
536 	assert(Token(symbol!"Value",loc,Value(   7),"foo") != Token(symbol!"Value",loc, Value( "A"),"foo"));
537 	assert(Token(symbol!"Value",loc,Value(   7),"foo") != Token(symbol!"Value",loc, Value(   2),"foo"));
538 	assert(Token(symbol!"Value",loc,Value(cast(int)7)) != Token(symbol!"Value",loc, Value(cast(long)7)));
539 	assert(Token(symbol!"Value",loc,Value(cast(float)1.2)) != Token(symbol!"Value",loc, Value(cast(double)1.2)));
540 }
541 
542 @("sdlang Value.toSDLString()")
543 unittest
544 {
545 	// Bool and null
546 	assert(Value(null ).toSDLString() == "null");
547 	assert(Value(true ).toSDLString() == "true");
548 	assert(Value(false).toSDLString() == "false");
549 	
550 	// Base64 Binary
551 	assert(Value(cast(ubyte[])"hello world".dup).toSDLString() == "[aGVsbG8gd29ybGQ=]");
552 
553 	// Integer
554 	assert(Value(cast( int) 7).toSDLString() ==  "7");
555 	assert(Value(cast( int)-7).toSDLString() == "-7");
556 	assert(Value(cast( int) 0).toSDLString() ==  "0");
557 
558 	assert(Value(cast(long) 7).toSDLString() ==  "7L");
559 	assert(Value(cast(long)-7).toSDLString() == "-7L");
560 	assert(Value(cast(long) 0).toSDLString() ==  "0L");
561 
562 	// Floating point
563 	import std.stdio;
564 	writeln(1.5f);
565 	writeln(Value(cast(float) 1.5).toSDLString());
566 	assert(Value(cast(float) 1.5).toSDLString() ==  "1.5F");
567 	assert(Value(cast(float)-1.5).toSDLString() == "-1.5F");
568 	assert(Value(cast(float)   0).toSDLString() ==    "0F");
569 	assert(Value(cast(float)0.25).toSDLString() == "0.25F");
570 
571 	assert(Value(cast(double) 1.5).toSDLString() ==  "1.5D");
572 	assert(Value(cast(double)-1.5).toSDLString() == "-1.5D");
573 	assert(Value(cast(double)   0).toSDLString() ==    "0D");
574 	assert(Value(cast(double)0.25).toSDLString() == "0.25D");
575 
576 	assert(Value(cast(real) 1.5).toSDLString() ==  "1.5BD");
577 	assert(Value(cast(real)-1.5).toSDLString() == "-1.5BD");
578 	assert(Value(cast(real)   0).toSDLString() ==    "0BD");
579 	assert(Value(cast(real)0.25).toSDLString() == "0.25BD");
580 
581 	// String
582 	assert(Value("hello"  ).toSDLString() == `"hello"`);
583 	assert(Value(" hello ").toSDLString() == `" hello "`);
584 	assert(Value(""       ).toSDLString() == `""`);
585 	assert(Value("hello \r\n\t\"\\ world").toSDLString() == `"hello \r\n\t\"\\ world"`);
586 	assert(Value("日本語").toSDLString() == `"日本語"`);
587 
588 	// Chars
589 	assert(Value(cast(dchar) 'A').toSDLString() ==  `'A'`);
590 	assert(Value(cast(dchar)'\r').toSDLString() == `'\r'`);
591 	assert(Value(cast(dchar)'\n').toSDLString() == `'\n'`);
592 	assert(Value(cast(dchar)'\t').toSDLString() == `'\t'`);
593 	assert(Value(cast(dchar)'\'').toSDLString() == `'\''`);
594 	assert(Value(cast(dchar)'\\').toSDLString() == `'\\'`);
595 	assert(Value(cast(dchar) '月').toSDLString() ==  `'月'`);
596 
597 	// Date
598 	assert(Value(Date( 2004,10,31)).toSDLString() == "2004/10/31");
599 	assert(Value(Date(-2004,10,31)).toSDLString() == "-2004/10/31");
600 
601 	// DateTimeFrac w/o Frac
602 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15))).toSDLString() == "2004/10/31 14:30:15");
603 	assert(Value(DateTimeFrac(DateTime(2004,10,31,   1, 2, 3))).toSDLString() == "2004/10/31 01:02:03");
604 	assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15))).toSDLString() == "-2004/10/31 14:30:15");
605 
606 	// DateTimeFrac w/ Frac
607 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 123.msecs)).toSDLString() == "2004/10/31 14:30:15.123");
608 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 120.msecs)).toSDLString() == "2004/10/31 14:30:15.120");
609 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 100.msecs)).toSDLString() == "2004/10/31 14:30:15.100");
610 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15),  12.msecs)).toSDLString() == "2004/10/31 14:30:15.012");
611 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15),   1.msecs)).toSDLString() == "2004/10/31 14:30:15.001");
612 	assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "-2004/10/31 14:30:15.123");
613 
614 	// DateTimeFracUnknownZone
615 	assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), 123.msecs, "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar");
616 
617 	// SysTime
618 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(0)             ))).toSDLString() == "2004/10/31 14:30:15-GMT+00:00");
619 	assert(Value(SysTime(DateTime(2004,10,31,  1, 2, 3), new immutable SimpleTimeZone( hours(0)             ))).toSDLString() == "2004/10/31 01:02:03-GMT+00:00");
620 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes(10) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:10");
621 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone(-hours(5)-minutes(30) ))).toSDLString() == "2004/10/31 14:30:15-GMT-05:30");
622 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes( 3) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:03");
623 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), 123.msecs, new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15.123-GMT+00:00");
624 
625 	// Duration
626 	assert( "12:14:42"         == Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs(  0)).toSDLString());
627 	assert("-12:14:42"         == Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs(  0)).toSDLString());
628 	assert( "00:09:12"         == Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs(  0)).toSDLString());
629 	assert( "00:00:01.023"     == Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23)).toSDLString());
630 	assert( "23d:05:21:23.532" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532)).toSDLString());
631 	assert( "23d:05:21:23.530" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530)).toSDLString());
632 	assert( "23d:05:21:23.500" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500)).toSDLString());
633 	assert("-23d:05:21:23.532" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532)).toSDLString());
634 	assert("-23d:05:21:23.500" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500)).toSDLString());
635 	assert( "23d:05:21:23"     == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(  0)).toSDLString());
636 }