1 module dud.sdlang.value;
2 
3 import std.datetime : DateTime, Duration, Date;
4 
5 /// DateTime doesn't support milliseconds, but SDL's "Date Time" type does.
6 /// So this is needed for any SDL "Date Time" that doesn't include a time zone.
7 struct DateTimeFrac {
8 @safe pure:
9 	this(DateTime dt, Duration fs) {
10 		this.dateTime = dt;
11 		this.fracSecs = fs;
12 	}
13 
14 	this(DateTime dt) {
15 		this.dateTime = dt;
16 	}
17 
18 	DateTime dateTime;
19 	Duration fracSecs;
20 }
21 
22 /++
23 If a "Date Time" literal in the SDL file has a time zone that's not found in
24 your system, you get one of these instead of a SysTime. (Because it's
25 impossible to indicate "unknown time zone" with 'std.datetime.TimeZone'.)
26 
27 The difference between this and 'DateTimeFrac' is that 'DateTimeFrac'
28 indicates that no time zone was specified in the SDL at all, whereas
29 'DateTimeFracUnknownZone' indicates that a time zone was specified but
30 data for it could not be found on your system.
31 +/
32 struct DateTimeFracUnknownZone {
33 @safe pure:
34 	DateTime dateTime;
35 	Duration fracSecs;
36 	string timeZone;
37 
38 	bool opEquals(const DateTimeFracUnknownZone b) const {
39 		return opEquals(b);
40 	}
41 
42 	bool opEquals(ref const DateTimeFracUnknownZone b) const {
43 		return this.dateTime == b.dateTime && this.fracSecs  == b.fracSecs
44 			&& this.timeZone == b.timeZone;
45 	}
46 }
47 
48 enum ValueType {
49 	boolean,
50 	int32,
51 	int64,
52 	float32,
53 	float64,
54 	float128,
55 	decimal128,
56 	date,
57 	datetime,
58 	datetimeTZ,
59 	duration,
60 	binary,
61 	str,
62 	char_,
63 	null_
64 }
65 
66 private union Data {
67 	long integral;
68 	double floating64;
69 	real floating128;
70 	bool boolean;
71 	Duration duration;
72 	Date date;
73 	DateTimeFrac datetime; // datetimeTZ
74 	DateTimeFracUnknownZone datetimeUZ; //datetime
75 	ubyte[] binary;
76 	string str;
77 	dchar character;
78 }
79 
80 struct Value {
81 @safe pure:
82 	ValueType type = ValueType.null_;
83 	Data data;
84 
85 	this(Duration i) @trusted {
86 		this.type = ValueType.duration;
87 		this.data.duration = i;
88 	}
89 
90 	this(dchar i) @trusted {
91 		this.type = ValueType.char_;
92 		this.data.character = i;
93 	}
94 
95 	this(string i) @trusted {
96 		this.type = ValueType.str;
97 		this.data.str = i;
98 	}
99 
100 	this(ubyte[] i) @trusted {
101 		this.type = ValueType.binary;
102 		this.data.binary = i;
103 	}
104 
105 	this(DateTimeFracUnknownZone i) @trusted {
106 		this.type = ValueType.datetime;
107 		this.data.datetimeUZ = i;
108 	}
109 
110 	this(DateTimeFrac i) @trusted {
111 		this.type = ValueType.datetimeTZ;
112 		this.data.datetime = i;
113 	}
114 
115 	this(Date i) @trusted {
116 		this.type = ValueType.date;
117 		this.data.date = i;
118 	}
119 
120 	this(bool i) @trusted {
121 		this.type = ValueType.boolean;
122 		this.data.boolean = i;
123 	}
124 
125 	this(int i) @trusted {
126 		this.type = ValueType.int32;
127 		this.data.integral = i;
128 	}
129 
130 	this(long i) @trusted {
131 		this.type = ValueType.int64;
132 		this.data.integral = i;
133 	}
134 
135 	this(float i) @trusted {
136 		this.type = ValueType.float32;
137 		this.data.floating64 = i;
138 	}
139 
140 	this(double i) @trusted {
141 		this.type = ValueType.float64;
142 		this.data.floating64 = i;
143 	}
144 
145 	this(real i) @trusted {
146 		this.type = ValueType.float128;
147 		this.data.floating128 = i;
148 	}
149 
150 	T get(T)() const pure @safe {
151 		import std.algorithm.searching : canFind;
152 		import std.conv : to;
153 		import std.exception : enforce;
154 		import std.traits : isFloatingPoint, isIntegral, isSomeString;
155 		import std.format : format;
156 		static if(isSomeString!T) {
157 			enforce(this.type == ValueType.str,
158 				format("Can not get '%s' when the type is '%s'", T.stringof,
159 					this.type));
160 			return () @trusted { return this.data.str; }();
161 		} else static if(is(T == ubyte[])) {
162 			enforce(this.type == ValueType.binary,
163 				format("Can not get '%s' when the type is '%s'", T.stringof,
164 					this.type));
165 			return this.data.binary;
166 		} else static if(is(T == dchar)) {
167 			enforce(this.type == ValueType.char_,
168 				format("Can not get '%s' when the type is '%s'", T.stringof,
169 					this.type));
170 			return this.data.character;
171 		} else static if(is(T == Duration)) {
172 			enforce(this.type == ValueType.duration,
173 				format("Can not get '%s' when the type is '%s'", T.stringof,
174 					this.type));
175 			return this.data.duration;
176 		} else static if(is(T == DateTimeFrac)) {
177 			enforce(this.type == ValueType.datetimeTZ,
178 				format("Can not get '%s' when the type is '%s'", T.stringof,
179 					this.type));
180 			return this.data.datetime;
181 		} else static if(is(T == DateTimeFracUnknownZone)) {
182 			enforce(this.type == ValueType.datetime,
183 				format("Can not get '%s' when the type is '%s'", T.stringof,
184 					this.type));
185 			return this.data.datetimeUZ;
186 		} else static if(is(T == bool)) {
187 			enforce(this.type == ValueType.boolean,
188 				format("Can not get '%s' when the type is '%s'", T.stringof,
189 					this.type));
190 			return this.data.boolean;
191 		} else static if(isIntegral!T) {
192 			enum tt =
193 				[ ValueType.int32, ValueType.int64, ValueType.float32 ];
194 			enforce(canFind(tt, this.type),
195 				format("Can not get '%s' when the type is '%s'", T.stringof,
196 					this.type));
197 			switch(this.type) {
198 				case ValueType.int32:
199 					goto case;
200 				case ValueType.int64:
201 					return to!T(this.data.integral);
202 				default:
203 					assert(false, "We should never reach this");
204 			}
205 			assert(false, "We should never reach either");
206 		} else static if(isFloatingPoint!T) {
207 			enum tt =
208 				[ ValueType.int32, ValueType.int64, ValueType.float32
209 				, ValueType.float64, ValueType.float128, ValueType.decimal128
210 				];
211 
212 			enforce(canFind(tt, this.type),
213 				format("Can not get '%s' when the type is '%s'", T.stringof,
214 					this.type));
215 			switch(this.type) {
216 				case ValueType.int32:
217 					goto case;
218 				case ValueType.int64:
219 					return to!T(this.data.integral);
220 				case ValueType.float32:
221 					goto case;
222 				case ValueType.float64:
223 					return to!T(this.data.floating64);
224 				case ValueType.float128:
225 					return to!T(this.data.floating128);
226 				case ValueType.decimal128:
227 					return to!T(this.data.floating128);
228 				default:
229 					assert(false, "We should never reach this");
230 			}
231 			assert(false, "We should never reach either");
232 		} else {
233 			static assert(false, format("getting a %s is not handled",
234 				T.stringof));
235 		}
236 	}
237 }