diff options
Diffstat (limited to 'src/sdlang/token.d')
-rw-r--r-- | src/sdlang/token.d | 135 |
1 files changed, 90 insertions, 45 deletions
diff --git a/src/sdlang/token.d b/src/sdlang/token.d index 908d4a3..0a5b2fd 100644 --- a/src/sdlang/token.d +++ b/src/sdlang/token.d @@ -7,15 +7,18 @@ import std.array; import std.base64; import std.conv; import std.datetime; +import std.meta; import std.range; import std.string; +import std.traits; import std.typetuple; import std.variant; +import sdlang.exception; import sdlang.symbol; import sdlang.util; -/// DateTime doesn't support milliseconds, but SDL's "Date Time" type does. +/// DateTime doesn't support milliseconds, but SDLang's "Date Time" type does. /// So this is needed for any SDL "Date Time" that doesn't include a time zone. struct DateTimeFrac { @@ -30,11 +33,11 @@ struct DateTimeFrac /++ If a "Date Time" literal in the SDL file has a time zone that's not found in your system, you get one of these instead of a SysTime. (Because it's -impossible to indicate "unknown time zone" with 'std.datetime.TimeZone'.) +impossible to indicate "unknown time zone" with `std.datetime.TimeZone`.) -The difference between this and 'DateTimeFrac' is that 'DateTimeFrac' +The difference between this and `DateTimeFrac` is that `DateTimeFrac` indicates that no time zone was specified in the SDL at all, whereas -'DateTimeFracUnknownZone' indicates that a time zone was specified but +`DateTimeFracUnknownZone` indicates that a time zone was specified but data for it could not be found on your system. +/ struct DateTimeFracUnknownZone @@ -61,9 +64,10 @@ struct DateTimeFracUnknownZone } /++ -SDL's datatypes map to D's datatypes as described below. +SDLang's datatypes map to D's datatypes as described below. Most are straightforward, but take special note of the date/time-related types. +--------------------------------------------------------------- Boolean: bool Null: typeof(null) Unicode Character: dchar @@ -81,8 +85,9 @@ Date (with no time at all): Date Date Time (no timezone): DateTimeFrac Date Time (with a known timezone): SysTime Date Time (with an unknown timezone): DateTimeFracUnknownZone +--------------------------------------------------------------- +/ -alias TypeTuple!( +alias ValueTypes = TypeTuple!( bool, string, dchar, int, long, @@ -90,41 +95,24 @@ alias TypeTuple!( Date, DateTimeFrac, SysTime, DateTimeFracUnknownZone, Duration, ubyte[], typeof(null), -) ValueTypes; +); -alias Algebraic!( ValueTypes ) Value; ///ditto +alias Value = Algebraic!( ValueTypes ); ///ditto +enum isValueType(T) = staticIndexOf!(T, ValueTypes) != -1; -template isSDLSink(T) -{ - enum isSink = - isOutputRange!T && - is(ElementType!(T)[] == string); -} +enum isSink(T) = + isOutputRange!T && + is(ElementType!(T)[] == string); -string toSDLString(T)(T value) if( - is( T : Value ) || - is( T : bool ) || - is( T : string ) || - is( T : dchar ) || - is( T : int ) || - is( T : long ) || - is( T : float ) || - is( T : double ) || - is( T : real ) || - is( T : Date ) || - is( T : DateTimeFrac ) || - is( T : SysTime ) || - is( T : DateTimeFracUnknownZone ) || - is( T : Duration ) || - is( T : ubyte[] ) || - is( T : typeof(null) ) -) +string toSDLString(T)(T value) if(is(T==Value) || isValueType!T) { Appender!string sink; toSDLString(value, sink); return sink.data; } +/// Throws SDLangException if value is infinity, -infinity or NaN, because +/// those are not currently supported by the SDLang spec. void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char)) { foreach(T; ValueTypes) @@ -139,6 +127,52 @@ void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char)) throw new Exception("Internal SDLang-D error: Unhandled type of Value. Contains: "~value.toString()); } +@("toSDLString on infinity and NaN") +unittest +{ + import std.exception; + + auto floatInf = float.infinity; + auto floatNegInf = -float.infinity; + auto floatNaN = float.nan; + + auto doubleInf = double.infinity; + auto doubleNegInf = -double.infinity; + auto doubleNaN = double.nan; + + auto realInf = real.infinity; + auto realNegInf = -real.infinity; + auto realNaN = real.nan; + + assertNotThrown( toSDLString(0.0F) ); + assertNotThrown( toSDLString(0.0) ); + assertNotThrown( toSDLString(0.0L) ); + + assertThrown!ValidationException( toSDLString(floatInf) ); + assertThrown!ValidationException( toSDLString(floatNegInf) ); + assertThrown!ValidationException( toSDLString(floatNaN) ); + + assertThrown!ValidationException( toSDLString(doubleInf) ); + assertThrown!ValidationException( toSDLString(doubleNegInf) ); + assertThrown!ValidationException( toSDLString(doubleNaN) ); + + assertThrown!ValidationException( toSDLString(realInf) ); + assertThrown!ValidationException( toSDLString(realNegInf) ); + assertThrown!ValidationException( toSDLString(realNaN) ); + + assertThrown!ValidationException( toSDLString(Value(floatInf)) ); + assertThrown!ValidationException( toSDLString(Value(floatNegInf)) ); + assertThrown!ValidationException( toSDLString(Value(floatNaN)) ); + + assertThrown!ValidationException( toSDLString(Value(doubleInf)) ); + assertThrown!ValidationException( toSDLString(Value(doubleNegInf)) ); + assertThrown!ValidationException( toSDLString(Value(doubleNaN)) ); + + assertThrown!ValidationException( toSDLString(Value(realInf)) ); + assertThrown!ValidationException( toSDLString(Value(realNegInf)) ); + assertThrown!ValidationException( toSDLString(Value(realNaN)) ); +} + void toSDLString(Sink)(typeof(null) value, ref Sink sink) if(isOutputRange!(Sink,char)) { sink.put("null"); @@ -194,18 +228,37 @@ void toSDLString(Sink)(long value, ref Sink sink) if(isOutputRange!(Sink,char)) sink.put( "%sL".format(value) ); } +private void checkUnsupportedFloatingPoint(T)(T value) if(isFloatingPoint!T) +{ + import std.exception; + import std.math; + + enforce!ValidationException( + !isInfinity(value), + "SDLang does not currently support infinity for floating-point types" + ); + + enforce!ValidationException( + !isNaN(value), + "SDLang does not currently support NaN for floating-point types" + ); +} + void toSDLString(Sink)(float value, ref Sink sink) if(isOutputRange!(Sink,char)) { + checkUnsupportedFloatingPoint(value); sink.put( "%.10sF".format(value) ); } void toSDLString(Sink)(double value, ref Sink sink) if(isOutputRange!(Sink,char)) { + checkUnsupportedFloatingPoint(value); sink.put( "%.30sD".format(value) ); } void toSDLString(Sink)(real value, ref Sink sink) if(isOutputRange!(Sink,char)) { + checkUnsupportedFloatingPoint(value); sink.put( "%.30sBD".format(value) ); } @@ -334,7 +387,7 @@ struct Token { Symbol symbol = sdlang.symbol.symbol!"Error"; /// The "type" of this token Location location; - Value value; /// Only valid when 'symbol' is symbol!"Value", otherwise null + Value value; /// Only valid when `symbol` is `symbol!"Value"`, otherwise null string data; /// Original text from source @disable this(); @@ -349,8 +402,8 @@ struct Token /// Tokens with differing symbols are always unequal. /// Tokens with differing values are always unequal. /// Tokens with differing Value types are always unequal. - /// Member 'location' is always ignored for comparison. - /// Member 'data' is ignored for comparison *EXCEPT* when the symbol is Ident. + /// Member `location` is always ignored for comparison. + /// Member `data` is ignored for comparison *EXCEPT* when the symbol is Ident. bool opEquals(Token b) { return opEquals(b); @@ -376,13 +429,9 @@ struct Token } } -version(sdlangUnittest) +@("sdlang token") unittest { - import std.stdio; - writeln("Unittesting sdlang token..."); - stdout.flush(); - auto loc = Location("", 0, 0, 0); auto loc2 = Location("a", 1, 1, 1); @@ -410,13 +459,9 @@ unittest assert(Token(symbol!"Value",loc,Value(cast(float)1.2)) != Token(symbol!"Value",loc, Value(cast(double)1.2))); } -version(sdlangUnittest) +@("sdlang Value.toSDLString()") unittest { - import std.stdio; - writeln("Unittesting sdlang Value.toSDLString()..."); - stdout.flush(); - // Bool and null assert(Value(null ).toSDLString() == "null"); assert(Value(true ).toSDLString() == "true"); |