1 // =================================
  2 // Copyright (c) 2022 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System.Concepts;
  7 using System.Collections;
  8 
  9 namespace System
 10 {
 11     public class ConversionException : Exception
 12     {
 13         public nothrow ConversionException(const string& message) : base(message)
 14         {
 15         }
 16     }
 17 
 18     public void ThrowConversionException(const string& message)
 19     {
 20         throw ConversionException(message);
 21     }
 22 
 23     public nothrow string ToString<IU>(I x) where I is SignedInteger and U is UnsignedInteger and ExplicitlyConvertible<IU> and ExplicitlyConvertible<Ubyte>
 24     {
 25         string s;
 26         U u = 0u;
 27         bool neg = x < 0;
 28         if (neg)
 29         {
 30             u = -cast<U>(x);
 31         }
 32         else
 33         {
 34             u = cast<U>(x);
 35         }
 36         do
 37         {
 38             s.Append(cast<char>(cast<byte>('0') + cast<byte>(u % 10u)));
 39             u = u / 10u;
 40         }
 41         while (u != 0u);
 42         if (neg)
 43         {
 44             s.Append('-');
 45         }
 46         Reverse(s.Begin()s.End());
 47         return s;
 48     }
 49 
 50     public nothrow string ToString<U>(U x) where U is UnsignedInteger and ExplicitlyConvertible<Ubyte>
 51     {
 52         string s;
 53         do
 54         {
 55             s.Append(cast<char>(cast<byte>('0') + cast<byte>(x % 10u)));
 56             x = x / 10u;
 57         }
 58         while (x != 0u);
 59         Reverse(s.Begin()s.End());
 60         return s;
 61     }
 62 
 63     public nothrow string ToString(sbyte x)
 64     {
 65         return ToString(cast<int>(x));
 66     }
 67 
 68     public nothrow string ToString(byte x)
 69     {
 70         return ToString(cast<uint>(x));
 71     }
 72 
 73     public nothrow string ToString(short x)
 74     {
 75         return ToString(cast<int>(x));
 76     }
 77 
 78     public nothrow string ToString(ushort x)
 79     {
 80         return ToString(cast<uint>(x));
 81     }
 82 
 83     public nothrow string ToString(int x)
 84     {
 85         return ToString<intuint>(x);
 86     }
 87 
 88     public nothrow string ToString(uint x)
 89     {
 90         return ToString<uint>(x);
 91     }
 92 
 93     public nothrow string ToString(long x)
 94     {
 95         return ToString<longulong>(x);
 96     }
 97 
 98     public nothrow string ToString(ulong x)
 99     {
100         return ToString<ulong>(x);
101     }
102 
103     public nothrow string ToString(float f)
104     {
105         return ToString(cast<double>(f));
106     }
107 
108     public nothrow string ToString(double xint maxNumDecimals)
109     {
110         return ToString(x0maxNumDecimals);
111     }
112 
113     public nothrow string ToString(double x)
114     {
115         return ToString(x15);
116     }
117 
118     public nothrow string ToString(double xint minNumDecimalsint maxNumDecimals)
119     {
120         string result;
121         if (x < 0)
122         {
123             x = -x;
124             result.Append('-');
125         }
126         result.Append(ToString(cast<int>(x)));
127         double d = x - cast<int>(x);
128         if (d > 0 || minNumDecimals > 0)
129         {
130             result.Append('.');
131             for (int i = 0; (d > 0 || i < minNumDecimals) && i < maxNumDecimals; ++i;)
132             {
133                 d = 10 * d;
134                 int digit = cast<int>(d) % 10;
135                 result.Append(cast<char>(cast<int>('0') + digit));
136                 d = d - cast<int>(d);
137             }
138         }
139         return result;
140     }
141 
142     public nothrow string ToString(char c)
143     {
144         return string(c);
145     }
146     
147     public string ToString(wchar c)
148     {
149         wstring s(c);
150         return ToUtf8(s);
151     }
152     
153     public string ToString(uchar c)
154     {
155         ustring s(c);
156         return ToUtf8(s);
157     }
158 
159     public nothrow string ToString(bool b)
160     {
161         if (b)
162         {
163             return "true";
164         }
165         return "false";
166     }
167 
168     public inline nothrow char HexChar(byte nibble)
169     {
170         const char* hex = "0123456789ABCDEF";
171         return hex[nibble];
172     }
173 
174     public nothrow string ToHexString<U>(U x) where U is UnsignedInteger and ExplicitlyConvertible<Ubyte>
175     {
176         string s;
177         long n = sizeof(x);
178         for (long i = 0; i < n; ++i;)
179         {
180             byte b = cast<byte>(x & 0xFFu);
181             s.Append(HexChar(b & 0x0Fu));
182             s.Append(HexChar(b >> 4u));
183             x = x >> 8u;
184         }
185         Reverse(s.Begin()s.End());
186         return s;
187     }
188 
189     public nothrow string ToHexString(byte b)
190     {
191         string s;
192         s.Append(HexChar(b >> 4u));
193         s.Append(HexChar(b & 0x0Fu));
194         return s;
195     }
196 
197     public nothrow string ToHexString(ushort u)
198     {
199         return ToHexString<ushort>(u);
200     }
201 
202     public nothrow string ToHexString(uint u)
203     {
204         return ToHexString<uint>(u);
205     }
206 
207     public nothrow string ToHexString(ulong u)
208     {
209         return ToHexString<ulong>(u);
210     }
211     
212     public nothrow string ToOctalString(ulong x)
213     {
214         return ToOctalString(x0);
215     }
216 
217     public nothrow string ToOctalString(ulong xint minDigits)
218     {
219         string s;
220         ++minDigits;
221         do
222         {
223             s.Append(cast<char>(cast<int>(x & 7u) + cast<int>('0')));
224             x = x >> 3u;
225             if (minDigits > 0)
226             {
227                 --minDigits;
228             }
229         }
230         while (minDigits != 0 || x != 0u);
231         if (s[s.Length() - 1] != '0')
232         {
233             s.Append('0');
234         }
235         Reverse(s.Begin()s.End());
236         return s;
237     }
238     
239     public string ToCsv(const List<string>& strings)
240     {
241         string result;
242         bool first = true;
243         for (const string& s : strings)
244         {
245             if (s.Find(',') != -1)
246             {
247                 ThrowConversionException("cannot convert string '" + s + "' to CSV: string contains comma");
248             }
249             if (first)
250             {
251                 first = false;
252             }
253             else
254             {
255                 result.Append(',');
256             }
257             result.Append(s);
258         }
259         return result;
260     }
261     
262     public nothrow bool ParseSigned<T>(const string& sT& x) where T is SignedInteger
263     {
264         x = cast<T>(0);
265         if (s.IsEmpty()) return false;
266         bool negative = false;
267         int state = 0;
268         for (char c : s)
269         {
270             switch (state)
271             {
272                 case 0:
273                 {
274                     if (c == '+')
275                     {
276                         state = 1;
277                     }
278                     else if (c == '-')
279                     {
280                         negative = true;
281                         state = 1;
282                     }
283                     else if (c >= '0' && c <= '9')
284                     {
285                         x = cast<T>(c) - cast<T>('0');
286                         state = 1;
287                     }
288                     else
289                     {
290                         return false;
291                     }
292                     break;
293                 }
294                 case 1:
295                 {
296                     if (c >= '0' && c <= '9')
297                     {
298                         x = 10 * x + cast<T>(c) - cast<T>('0');
299                     }
300                     else
301                     {
302                         return false;
303                     }
304                     break;
305                 }
306             }
307         }
308         if (state != 1)
309         {
310             return false;
311         }
312         else
313         {
314             if (negative)
315             {
316                 x = -x;
317             }
318             return true;
319         }
320     }
321 
322     public nothrow bool ParseUnsigned<T>(const string& sT& x) where T is UnsignedInteger
323     {
324         x = cast<T>(0u);
325         if (s.IsEmpty())
326         {
327             return false;
328         }
329         int state = 0;
330         for (char c : s)
331         {
332             switch (state)
333             {
334                 case 0:
335                 {
336                     if (c == '+')
337                     {
338                         state = 1;
339                     }
340                     else if (c >= '0' && c <= '9')
341                     {
342                         x = cast<T>(c) - cast<T>('0');
343                         state = 1;
344                     }
345                     else
346                     {
347                         return false;
348                     }
349                     break;
350                 }
351                 case 1:
352                 {
353                     if (c >= '0' && c <= '9')
354                     {
355                         x = 10u * x + cast<T>(c) - cast<T>('0');
356                     }
357                     else
358                     {
359                         return false;
360                     }
361                     break;
362                 }
363             }
364         }
365         if (state != 1)
366         {
367             return false;
368         }
369         return true;
370     }
371 
372     public nothrow bool ParseHex<T>(const string& sT& x) where T is UnsignedInteger
373     {
374         x = cast<T>(0u);
375         if (s.IsEmpty())
376         {
377             return false;
378         }
379         for (char c : s)
380         {
381             if (c >= '0' && c <= '9')
382             {
383                 x = 16u * x + (cast<T>(c) - cast<T>('0'));
384             }
385             else if (c >= 'a' && c <= 'f')
386             {
387                 x = 16u * x + 10u + (cast<T>(c) - cast<T>('a'));
388             }
389             else if (c >= 'A' && c <= 'F')
390             {
391                 x = 16u * x + 10u + (cast<T>(c) - cast<T>('A'));
392             }
393             else
394             {
395                 return false;
396             }
397         }
398         return true;
399     }
400 
401     public nothrow bool ParseFloating<T>(const string& sT& x)
402     {
403         x = cast<T>(0.0);
404         if (s.IsEmpty())
405         {
406             return false;
407         }
408         bool negative = false;
409         int start = 0;
410         if (s[0] == '+')
411         {
412             ++start;
413         }
414         else if (s[0] == '-')
415         {
416             negative = true;
417             ++start;
418         }
419         int state = 0;
420         T d = cast<T>(10.0);
421         int exponent = 0;
422         bool negativeExponent = false;
423         long n = s.Length();
424         for (long i = start; i < n; ++i;)
425         {
426             char c = s[i];
427             switch (state)
428             {
429                 case 0:
430                 {
431                     if (c >= '0' && c <= '9')
432                     {
433                         x = 10 * x + (cast<int>(c) - cast<int>('0'));
434                     }
435                     else if (c == '.')
436                     {
437                         state = 1;
438                     }
439                     else if (c == 'e' || c == 'E')
440                     {
441                         state = 2;
442                     }
443                     else
444                     {
445                         return false;
446                     }
447                     break;
448                 }
449                 case 1:
450                 {
451                     if (c >= '0' && c <= '9')
452                     {
453                         x = x + (cast<int>(c) - cast<int>('0')) / d;
454                         d = 10 * d;
455                     }
456                     else if (c == 'e' || c == 'E')
457                     {
458                         state = 2;
459                     }
460                     else
461                     {
462                         return false;
463                     }
464                     break;
465                 }
466                 case 2:
467                 {
468                     if (c == '+')
469                     {
470                         state = 3;
471                     }
472                     else if (c == '-')
473                     {
474                         negativeExponent = true;
475                         state = 3;
476                     }
477                     else if (c >= '0' && c <= '9')
478                     {
479                         exponent = cast<int>(c) - cast<int>('0');
480                         state = 3;
481                     }
482                     else
483                     {
484                         return false;
485                     }
486                     break;
487                 }
488                 case 3:
489                 {
490                     if (c >= '0' && c <= '9')
491                     {
492                         exponent = 10 * exponent + (cast<int>(c) - cast<int>('0'));
493                     }
494                     else
495                     {
496                         return false;
497                     }
498                     break;
499                 }
500             }
501         }
502         if (negative)
503         {
504             x = -x;
505         }
506         if (exponent != 0)
507         {
508             if (negativeExponent)
509             {
510                 exponent = -exponent;
511             }
512             x = x * cast<T>(pow(10.0exponent));
513         }
514         return true;
515     }
516 
517     public nothrow bool ParseSByte(const string& ssbyte& x)
518     {
519         return ParseSigned<sbyte>(sx);
520     }
521 
522     public sbyte ParseSByte(const string& s)
523     {
524         sbyte x;
525         if (ParseSByte(sx))
526         {
527             return x;
528         }
529         else
530         {
531             ThrowConversionException("cannot parse sbyte from string '" + s + "'");
532         }
533         return 0;
534     }
535 
536     public nothrow bool ParseByte(const string& sbyte& x)
537     {
538         return ParseUnsigned<byte>(sx);
539     }
540 
541     public byte ParseByte(const string& s)
542     {
543         byte x;
544         if (ParseByte(sx))
545         {
546             return x;
547         }
548         else
549         {
550             ThrowConversionException("cannot parse byte from string '" + s + "'");
551         }
552         return 0u;
553     }
554 
555     public nothrow bool ParseShort(const string& sshort& x)
556     {
557         return ParseSigned<short>(sx);
558     }
559 
560     public short ParseShort(const string& s)
561     {
562         short x;
563         if (ParseShort(sx))
564         {
565             return x;
566         }
567         else
568         {
569             ThrowConversionException("cannot parse short from string '" + s + "'");
570         }
571         return 0;
572     }
573 
574     public nothrow bool ParseUShort(const string& sushort& x)
575     {
576         return ParseUnsigned<ushort>(sx);
577     }
578 
579     public ushort ParseUShort(const string& s)
580     {
581         ushort x;
582         if (ParseUShort(sx))
583         {
584             return x;
585         }
586         else
587         {
588             ThrowConversionException("cannot parse ushort from string '" + s + "'");
589         }
590         return 0u;
591     }
592 
593     public nothrow bool ParseInt(const string& sint& x)
594     {
595         return ParseSigned<int>(sx);
596     }
597 
598     public int ParseInt(const string& s)
599     {
600         int x;
601         if (ParseInt(sx))
602         {
603             return x;
604         }
605         else
606         {
607             ThrowConversionException("cannot parse int from string '" + s + "'");
608         }
609         return 0;
610     }
611 
612     public nothrow bool ParseUInt(const string& suint& x)
613     {
614         return ParseUnsigned<uint>(sx);
615     }
616 
617     public uint ParseUInt(const string& s)
618     {
619         uint x;
620         if (ParseUInt(sx))
621         {
622             return x;
623         }
624         else
625         {
626             ThrowConversionException("cannot parse uint from string '" + s + "'");
627         }
628         return 0u;
629     }
630 
631     public nothrow bool ParseLong(const string& slong& x)
632     {
633         return ParseSigned<long>(sx);
634     }
635 
636     public long ParseLong(const string& s)
637     {
638         long x;
639         if (ParseLong(sx))
640         {
641             return x;
642         }
643         else
644         {
645             ThrowConversionException("cannot parse long from string '" + s + "'");
646         }
647         return 0;
648     }
649 
650     public nothrow bool ParseULong(const string& sulong& x)
651     {
652         return ParseUnsigned<ulong>(sx);
653     }
654 
655     public ulong ParseULong(const string& s)
656     {
657         ulong x;
658         if (ParseULong(sx))
659         {
660             return x;
661         }
662         else
663         {
664             ThrowConversionException("cannot parse ulong from string '" + s + "'");
665         }
666         return 0u;
667     }
668 
669     public nothrow bool ParseFloat(const string& sfloat& x)
670     {
671         return ParseFloating<float>(sx);
672     }
673 
674     public float ParseFloat(const string& s)
675     {
676         float x;
677         if (ParseFloat(sx))
678         {
679             return x;
680         }
681         else
682         {
683             ThrowConversionException("cannot parse float from string '" + s + "'");
684         }
685         return 0.0f;
686     }
687 
688     public nothrow bool ParseDouble(const string& sdouble& x)
689     {
690         return ParseFloating<double>(sx);
691     }
692 
693     public double ParseDouble(const string& s)
694     {
695         double x;
696         if (ParseDouble(sx))
697         {
698             return x;
699         }
700         else
701         {
702             ThrowConversionException("cannot parse double from string '" + s + "'");
703         }
704         return 0.0;
705     }
706 
707     public nothrow bool ParseBool(const string& sbool& x)
708     {
709         if (s == "true")
710         {
711             x = true;
712             return true;
713         }
714         else if (s == "false")
715         {
716             x = false;
717             return true;
718         }
719         else
720         {
721             x = false;
722             return false;
723         }
724     }
725 
726     public bool ParseBool(const string& s)
727     {
728         bool x;
729         if (ParseBool(sx))
730         {
731             return x;
732         }
733         else
734         {
735             ThrowConversionException("cannot parse bool from string '" + s + "'");
736         }
737         return false;
738     }
739 
740     public nothrow bool ParseHexByte(const string& sbyte& x)
741     {
742         return ParseHex<byte>(sx);
743     }
744 
745     public byte ParseHexByte(const string& s)
746     {
747         byte x;
748         if (ParseHexByte(sx))
749         {
750             return x;
751         }
752         else
753         {
754             ThrowConversionException("cannot parse hex byte from string '" + s + "'");
755         }
756         return 0u;
757     }
758 
759     public nothrow bool ParseHexUShort(const string& sushort& x)
760     {
761         return ParseHex<ushort>(sx);
762     }
763 
764     public ushort ParseHexUShort(const string& s)
765     {
766         ushort x;
767         if (ParseHexUShort(sx))
768         {
769             return x;
770         }
771         else
772         {
773             ThrowConversionException("cannot parse hex ushort from string '" + s + "'");
774         }
775         return 0u;
776     }
777 
778     public nothrow bool ParseHexUInt(const string& suint& x)
779     {
780         return ParseHex<uint>(sx);
781     }
782 
783     public uint ParseHexUInt(const string& s)
784     {
785         uint x;
786         if (ParseHexUInt(sx))
787         {
788             return x;
789         }
790         else
791         {
792             ThrowConversionException("cannot parse hex uint from string '" + s + "'");
793         }
794         return 0u;
795     }
796 
797     public nothrow bool ParseHexULong(const string& sulong& x)
798     {
799         return ParseHex<ulong>(sx);
800     }
801 
802     public ulong ParseHexULong(const string& s)
803     {
804         ulong x;
805         if (ParseHexULong(sx))
806         {
807             return x;
808         }
809         else
810         {
811             ThrowConversionException("cannot parse hex ulong from string '" + s + "'");
812         }
813         return 0u;
814     }
815     
816     public nothrow bool ParseOctal(const string& sulong& x)
817     {
818         x = 0u;
819         for (char c : s)
820         {
821             if (c >= '0' && c <= '7')
822             {
823                 x = 8u * x + cast<ulong>(cast<int>(c) - cast<int>('0'));
824             }
825             else
826             {
827                 return false;
828             }
829         }
830         return true;
831     }
832     
833     public nothrow ulong ParseOctal(const string& s)
834     {
835         ulong x = 0u;
836         if (ParseOctal(sx))
837         {
838             return x;
839         }
840         else
841         {
842             ThrowConversionException("cannot parse octal from string '" + s + "'");
843         }
844         return 0u;
845     }
846     
847     public nothrow bool ParseDate(const string& dateStrDate& date)
848     {
849         int dateEnd = 0;
850         return ParseDate(dateStrdatedateEnd);
851     }
852     
853     public nothrow bool ParseDate(const string& dateStrDate& dateint& dateEnd)
854     {
855         short year;
856         if (dateStr.Length() < 4 + 2 + 2)
857         {
858             return false;
859         }
860         if (!ParseShort(dateStr.Substring(04)year))
861         {
862             return false;
863         }
864         int monthStart = 4;
865         if (dateStr[4] == '-')
866         {
867             ++monthStart;
868         }
869         if (dateStr.Length() < monthStart + 2)
870         {
871             return false;
872         }
873         sbyte month;
874         if (!ParseSByte(dateStr.Substring(monthStart2)month))
875         {
876             return false;
877         }
878         if (month < 1 || month > 12)
879         {
880             return false;
881         }
882         int dayStart = monthStart + 2;
883         if (dateStr[dayStart] == '-')
884         {
885             ++dayStart;
886         }
887         if (dateStr.Length() < dayStart + 2)
888         {
889             return false;
890         }
891         sbyte day;
892         if (!ParseSByte(dateStr.Substring(dayStart2)day))
893         {
894             return false;
895         }
896         if (day < 1 || day > 31)
897         {
898             return false;
899         }
900         dateEnd = dayStart + 2;
901         date = Date(yearcast<Month>(month)day);
902         return true;
903     }
904     
905     public Date ParseDate(const string& s)
906     {
907         Date date;
908         if (!ParseDate(sdate))
909         {
910             ThrowConversionException("cannot parse date from string '" + s + "': not in format YYYY-MM-DD or YYYYMMDD");
911         }
912         return date;
913     }
914     
915     public bool ParseDateTime(const string& dateTimeStrDateTime& dateTime)
916     {
917         Date date;
918         int dateEnd = 0;
919         if (!ParseDate(dateTimeStrdatedateEnd))
920         {
921             return false;
922         }
923         int hours = 0;
924         int mins = 0;
925         int secs = 0;
926         if (dateTimeStr.Length() > dateEnd)
927         {
928             if (dateTimeStr[dateEnd] == 'T')
929             {
930                 int hoursStart = dateEnd + 1;
931                 if (!ParseInt(dateTimeStr.Substring(hoursStart2)hours))
932                 {
933                     return false;
934                 }
935                 if (hours < 0 || hours > 24)
936                 {
937                     return false;
938                 }
939                 if (dateTimeStr.Length() > hoursStart + 2)
940                 {
941                     int minsStart = hoursStart + 2;
942                     if (dateTimeStr[minsStart] == ':')
943                     {
944                         ++minsStart;
945                     }
946                     if (!ParseInt(dateTimeStr.Substring(minsStart2)mins))
947                     {
948                         return false;
949                     }
950                     if (mins < 0 || mins >= 60)
951                     {
952                         return false;
953                     }
954                     if (dateTimeStr.Length() > minsStart + 2)
955                     {
956                         int secsStart = minsStart + 2;
957                         if (dateTimeStr[secsStart] == ':')
958                         {
959                             ++secsStart;
960                         }
961                         if (!ParseInt(dateTimeStr.Substring(secsStart2)secs))
962                         {
963                             return false;
964                         }
965                         if (secs < 0 || secs > 60)
966                         {
967                             return false;
968                         }
969                     }
970                 }
971             }
972         }
973         int totalSecs = hours * 3600 + mins * 60 + secs;
974         dateTime = DateTime(datetotalSecs);
975         return true;
976     }
977 
978     public DateTime ParseDateTime(const string& s)
979     {
980         DateTime dateTime;
981         if (!ParseDateTime(sdateTime))
982         {
983             ThrowConversionException("cannot parse date time from string '" + s + "': not in format YYYY[-]MM[-]DD or YYYY[-]MM[-]DDTHH[[:]MM[[:]SS]]");
984         }
985         return dateTime;
986     }
987     
988     public List<string> ParseCSV(const string& csv)
989     {
990         return csv.Split(',');
991     }
992 }