- // SDLang-D
- // Written in the D programming language.
-
- module dub.internal.sdlang.token;
-
- version (Have_sdlang_d) public import sdlang.token;
- else:
-
- import std.array;
- import std.base64;
- import std.conv;
- import std.datetime;
- import std.range;
- import std.string;
- import std.typetuple;
-
- import dub.internal.dyaml.stdsumtype;
-
- import dub.internal.sdlang.symbol;
- import dub.internal.sdlang.util;
-
- /// DateTime doesn't support milliseconds, but SDL's "Date Time" type does.
- /// So this is needed for any SDL "Date Time" that doesn't include a time zone.
- struct DateTimeFrac
- {
- this(DateTime dt, Duration fs) { this.dateTime = dt; this.fracSecs = fs; }
-
- DateTime dateTime;
- Duration fracSecs;
- }
-
- /++
- 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'.)
-
- 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
- data for it could not be found on your system.
- +/
- struct DateTimeFracUnknownZone
- {
- DateTime dateTime;
- Duration fracSecs;
- string timeZone;
-
- bool opEquals(const DateTimeFracUnknownZone b) const
- {
- return opEquals(b);
- }
- bool opEquals(ref const DateTimeFracUnknownZone b) const
- {
- return
- this.dateTime == b.dateTime &&
- this.fracSecs == b.fracSecs &&
- this.timeZone == b.timeZone;
- }
- }
-
- /++
- SDL's data-types 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
- Double-Quote Unicode String: string
- Raw Backtick Unicode String: string
- Integer (32 bits signed): int
- Long Integer (64 bits signed): long
- Float (32 bits signed): float
- Double Float (64 bits signed): double
- Decimal (128+ bits signed): real
- Binary (standard Base64): ubyte[]
- Time Span: Duration
-
- 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!(
- typeof(null),
- bool,
- string, dchar,
- int, long,
- float, double, real,
- Date, DateTimeFrac, SysTime, DateTimeFracUnknownZone, Duration,
- ubyte[],
- ) ValueTypes;
-
- alias SumType!ValueTypes Value; /// ditto
-
- template isSDLSink(T)
- {
- enum isSink =
- 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) )
- )
- {
- Appender!string sink;
- toSDLString(value, sink);
- return sink.data;
- }
-
- void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- value.match!(v => toSDLString(v, sink));
- }
-
- void toSDLString(Sink)(typeof(null) value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put("null");
- }
-
- void toSDLString(Sink)(bool value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put(value? "true" : "false");
- }
-
- //TODO: Figure out how to properly handle strings/chars containing lineSep or paraSep
- void toSDLString(Sink)(string value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put('"');
-
- // This loop is UTF-safe
- foreach(char ch; value)
- {
- if (ch == '\n') sink.put(`\n`);
- else if(ch == '\r') sink.put(`\r`);
- else if(ch == '\t') sink.put(`\t`);
- else if(ch == '\"') sink.put(`\"`);
- else if(ch == '\\') sink.put(`\\`);
- else
- sink.put(ch);
- }
-
- sink.put('"');
- }
-
- void toSDLString(Sink)(dchar value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put('\'');
-
- if (value == '\n') sink.put(`\n`);
- else if(value == '\r') sink.put(`\r`);
- else if(value == '\t') sink.put(`\t`);
- else if(value == '\'') sink.put(`\'`);
- else if(value == '\\') sink.put(`\\`);
- else
- sink.put(value);
-
- sink.put('\'');
- }
-
- void toSDLString(Sink)(int value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put( "%s".format(value) );
- }
-
- void toSDLString(Sink)(long value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put( "%sL".format(value) );
- }
-
- void toSDLString(Sink)(float value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put( "%.10sF".format(value) );
- }
-
- void toSDLString(Sink)(double value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put( "%.30sD".format(value) );
- }
-
- void toSDLString(Sink)(real value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put( "%.30sBD".format(value) );
- }
-
- void toSDLString(Sink)(Date value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put(to!string(value.year));
- sink.put('/');
- sink.put(to!string(cast(int)value.month));
- sink.put('/');
- sink.put(to!string(value.day));
- }
-
- void toSDLString(Sink)(DateTimeFrac value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- toSDLString(value.dateTime.date, sink);
- sink.put(' ');
- sink.put("%.2s".format(value.dateTime.hour));
- sink.put(':');
- sink.put("%.2s".format(value.dateTime.minute));
-
- if(value.dateTime.second != 0)
- {
- sink.put(':');
- sink.put("%.2s".format(value.dateTime.second));
- }
-
- if(value.fracSecs.total!"msecs" != 0)
- {
- sink.put('.');
- sink.put("%.3s".format(value.fracSecs.total!"msecs"));
- }
- }
-
- void toSDLString(Sink)(SysTime value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs);
- toSDLString(dateTimeFrac, sink);
-
- sink.put("-");
-
- auto tzString = value.timezone.name;
-
- // If name didn't exist, try abbreviation.
- // Note that according to std.datetime docs, on Windows the
- // stdName/dstName may not be properly abbreviated.
- version(Windows) {} else
- if(tzString == "")
- {
- auto tz = value.timezone;
- auto stdTime = value.stdTime;
-
- if(tz.hasDST())
- tzString = tz.dstInEffect(stdTime)? tz.dstName : tz.stdName;
- else
- tzString = tz.stdName;
- }
-
- if(tzString == "")
- {
- auto offset = value.timezone.utcOffsetAt(value.stdTime);
- sink.put("GMT");
-
- if(offset < seconds(0))
- {
- sink.put("-");
- offset = -offset;
- }
- else
- sink.put("+");
-
- long hours, minutes;
- offset.split!("hours", "minutes")(hours, minutes);
-
- sink.put("%.2s".format(hours));
- sink.put(":");
- sink.put("%.2s".format(minutes));
- }
- else
- sink.put(tzString);
- }
-
- void toSDLString(Sink)(DateTimeFracUnknownZone value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- auto dateTimeFrac = DateTimeFrac(value.dateTime, value.fracSecs);
- toSDLString(dateTimeFrac, sink);
-
- sink.put("-");
- sink.put(value.timeZone);
- }
-
- void toSDLString(Sink)(Duration value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- if(value < seconds(0))
- {
- sink.put("-");
- value = -value;
- }
-
- auto days = value.total!"days"();
- if(days != 0)
- {
- sink.put("%s".format(days));
- sink.put("d:");
- }
-
- long hours, minutes, seconds, msecs;
- value.split!("hours", "minutes", "seconds", "msecs")(hours, minutes, seconds, msecs);
-
- sink.put("%.2s".format(hours));
- sink.put(':');
- sink.put("%.2s".format(minutes));
- sink.put(':');
- sink.put("%.2s".format(seconds));
-
- if(msecs != 0)
- {
- sink.put('.');
- sink.put("%.3s".format(msecs));
- }
- }
-
- void toSDLString(Sink)(ubyte[] value, ref Sink sink) if(isOutputRange!(Sink,char))
- {
- sink.put('[');
- sink.put( Base64.encode(value) );
- sink.put(']');
- }
-
- /// This only represents terminals. Non-terminals aren't
- /// constructed since the AST is directly built during parsing.
- struct Token
- {
- Symbol symbol = dub.internal.sdlang.symbol.symbol!"Error"; /// The "type" of this token
- Location location;
- Value value; /// Only valid when 'symbol' is symbol!"Value", otherwise null
- string data; /// Original text from source
-
- @disable this();
- this(Symbol symbol, Location location, Value value=Value(null), string data=null)
- {
- this.symbol = symbol;
- this.location = location;
- this.value = value;
- this.data = data;
- }
-
- /// 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.
- bool opEquals(Token b)
- {
- return opEquals(b);
- }
- bool opEquals(ref Token b) ///ditto
- {
- if(
- this.symbol != b.symbol ||
- this.value != b.value
- )
- return false;
-
- if(this.symbol == .symbol!"Ident")
- return this.data == b.data;
-
- return true;
- }
-
- bool matches(string symbolName)()
- {
- return this.symbol == .symbol!symbolName;
- }
- }
-
- version(sdlangUnittest)
- unittest
- {
- import std.stdio;
- writeln("Unittesting sdlang token...");
- stdout.flush();
-
- auto loc = Location("", 0, 0, 0);
- auto loc2 = Location("a", 1, 1, 1);
-
- assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc ));
- assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc2));
- assert(Token(symbol!":", loc) == Token(symbol!":", loc ));
- assert(Token(symbol!"EOL",loc) != Token(symbol!":", loc ));
- assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),"\n"));
-
- assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),";" ));
- assert(Token(symbol!"EOL",loc,Value(null),"A" ) == Token(symbol!"EOL",loc,Value(null),"B" ));
- assert(Token(symbol!":", loc,Value(null),"A" ) == Token(symbol!":", loc,Value(null),"BB"));
- assert(Token(symbol!"EOL",loc,Value(null),"A" ) != Token(symbol!":", loc,Value(null),"A" ));
-
- assert(Token(symbol!"Ident",loc,Value(null),"foo") == Token(symbol!"Ident",loc,Value(null),"foo"));
- assert(Token(symbol!"Ident",loc,Value(null),"foo") != Token(symbol!"Ident",loc,Value(null),"BAR"));
-
- assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"foo"));
- assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc2,Value(null),"foo"));
- assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"BAR"));
- assert(Token(symbol!"Value",loc,Value( 7),"foo") == Token(symbol!"Value",loc, Value( 7),"BAR"));
- assert(Token(symbol!"Value",loc,Value( 7),"foo") != Token(symbol!"Value",loc, Value( "A"),"foo"));
- assert(Token(symbol!"Value",loc,Value( 7),"foo") != Token(symbol!"Value",loc, Value( 2),"foo"));
- assert(Token(symbol!"Value",loc,Value(cast(int)7)) != Token(symbol!"Value",loc, Value(cast(long)7)));
- assert(Token(symbol!"Value",loc,Value(cast(float)1.2)) != Token(symbol!"Value",loc, Value(cast(double)1.2)));
- }
-
- version(sdlangUnittest)
- unittest
- {
- import std.stdio;
- writeln("Unittesting sdlang Value.toSDLString()...");
- stdout.flush();
-
- // Bool and null
- assert(Value(null ).toSDLString() == "null");
- assert(Value(true ).toSDLString() == "true");
- assert(Value(false).toSDLString() == "false");
-
- // Base64 Binary
- assert(Value(cast(ubyte[])"hello world".dup).toSDLString() == "[aGVsbG8gd29ybGQ=]");
-
- // Integer
- assert(Value(cast( int) 7).toSDLString() == "7");
- assert(Value(cast( int)-7).toSDLString() == "-7");
- assert(Value(cast( int) 0).toSDLString() == "0");
-
- assert(Value(cast(long) 7).toSDLString() == "7L");
- assert(Value(cast(long)-7).toSDLString() == "-7L");
- assert(Value(cast(long) 0).toSDLString() == "0L");
-
- // Floating point
- assert(Value(cast(float) 1.5).toSDLString() == "1.5F");
- assert(Value(cast(float)-1.5).toSDLString() == "-1.5F");
- assert(Value(cast(float) 0).toSDLString() == "0F");
-
- assert(Value(cast(double) 1.5).toSDLString() == "1.5D");
- assert(Value(cast(double)-1.5).toSDLString() == "-1.5D");
- assert(Value(cast(double) 0).toSDLString() == "0D");
-
- assert(Value(cast(real) 1.5).toSDLString() == "1.5BD");
- assert(Value(cast(real)-1.5).toSDLString() == "-1.5BD");
- assert(Value(cast(real) 0).toSDLString() == "0BD");
-
- // String
- assert(Value("hello" ).toSDLString() == `"hello"`);
- assert(Value(" hello ").toSDLString() == `" hello "`);
- assert(Value("" ).toSDLString() == `""`);
- assert(Value("hello \r\n\t\"\\ world").toSDLString() == `"hello \r\n\t\"\\ world"`);
- assert(Value("日本語").toSDLString() == `"日本語"`);
-
- // Chars
- assert(Value(cast(dchar) 'A').toSDLString() == `'A'`);
- assert(Value(cast(dchar)'\r').toSDLString() == `'\r'`);
- assert(Value(cast(dchar)'\n').toSDLString() == `'\n'`);
- assert(Value(cast(dchar)'\t').toSDLString() == `'\t'`);
- assert(Value(cast(dchar)'\'').toSDLString() == `'\''`);
- assert(Value(cast(dchar)'\\').toSDLString() == `'\\'`);
- assert(Value(cast(dchar) '月').toSDLString() == `'月'`);
-
- // Date
- assert(Value(Date( 2004,10,31)).toSDLString() == "2004/10/31");
- assert(Value(Date(-2004,10,31)).toSDLString() == "-2004/10/31");
-
- // DateTimeFrac w/o Frac
- assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15))).toSDLString() == "2004/10/31 14:30:15");
- assert(Value(DateTimeFrac(DateTime(2004,10,31, 1, 2, 3))).toSDLString() == "2004/10/31 01:02:03");
- assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15))).toSDLString() == "-2004/10/31 14:30:15");
-
- // DateTimeFrac w/ Frac
- assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "2004/10/31 14:30:15.123");
- assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 120.msecs)).toSDLString() == "2004/10/31 14:30:15.120");
- assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 100.msecs)).toSDLString() == "2004/10/31 14:30:15.100");
- assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 12.msecs)).toSDLString() == "2004/10/31 14:30:15.012");
- assert(Value(DateTimeFrac(DateTime(2004,10,31, 14,30,15), 1.msecs)).toSDLString() == "2004/10/31 14:30:15.001");
- assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "-2004/10/31 14:30:15.123");
-
- // DateTimeFracUnknownZone
- assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), 123.msecs, "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar");
-
- // SysTime
- 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");
- 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");
- 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");
- 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");
- 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");
- 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");
-
- // Duration
- assert( "12:14:42" == Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs( 0)).toSDLString());
- assert("-12:14:42" == Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs( 0)).toSDLString());
- assert( "00:09:12" == Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs( 0)).toSDLString());
- assert( "00:00:01.023" == Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23)).toSDLString());
- assert( "23d:05:21:23.532" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532)).toSDLString());
- assert( "23d:05:21:23.530" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530)).toSDLString());
- assert( "23d:05:21:23.500" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500)).toSDLString());
- assert("-23d:05:21:23.532" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532)).toSDLString());
- assert("-23d:05:21:23.500" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500)).toSDLString());
- assert( "23d:05:21:23" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs( 0)).toSDLString());
- }