1 ///
2 module serialport.exception;
3 
4 import std.conv : text;
5 
6 import serialport.types;
7 
8 ///
9 class ParseModeException : Exception
10 {
11     this(string msg, string file=__FILE__, size_t line=__LINE__)
12         @safe pure nothrow @nogc
13     { super(msg, file, line); } 
14 }
15 
16 /// General
17 class SerialPortException : Exception
18 {
19     string port;
20     private this() @safe pure nothrow @nogc { super(""); }
21 }
22 
23 /// Unsupported config
24 class UnsupportedException : SerialPortException
25 { private this() @safe pure nothrow @nogc { super(); } }
26 
27 ///
28 class PortClosedException : SerialPortException
29 { private this() @safe pure nothrow @nogc { super(); } }
30 
31 ///
32 class TimeoutException : SerialPortException
33 { private this() @safe pure nothrow @nogc { super(); } }
34 
35 ///
36 class SysCallException : SerialPortException
37 {
38     /// sys call name
39     string fnc;
40     /// errno or GetLastError
41     int err;
42     private this() @safe pure nothrow @nogc { super(); }
43 }
44 
45 ///
46 class ReadException : SysCallException
47 { private this() @safe pure nothrow @nogc { super(); } }
48 
49 ///
50 class WriteException : SysCallException
51 { private this() @safe pure nothrow @nogc { super(); } }
52 
53 private E setFields(E: SerialPortException)(E e, string port, string msg,
54                                               string file, size_t line)
55 {
56     if (e is null) // assert(0) not omit on optimize by compiler
57         assert(0, "setField get null exception object");
58     e.port = port;
59     e.msg = msg;
60     e.file = file;
61     e.line = line;
62     return e;
63 }
64 
65 import std.format;
66 
67 private enum preallocated;
68 private enum prealloc_prefix = "prealloc";
69 
70 private mixin template throwSPEMix(E, string defaultMsg="")
71     if (is(E: SerialPortException))
72 {
73     enum string name = E.stringof;
74     mixin(`
75     @preallocated private %1$s %2$s%1$s;
76     void throw%1$s(string port, string msg="%3$s",
77                    string file=__FILE__, size_t line=__LINE__) @nogc
78     { throw %2$s%1$s.setFields(port, msg, file, line); }
79     `.format(name, prealloc_prefix, (defaultMsg.length ? defaultMsg : name))
80     );
81 }
82 
83 private enum fmtSPSCEMsgFmt = "call '%s' (%s) failed: error %d";
84 
85 private string fmtSPSCEMsg(string port, string fnc, int err) @nogc
86 {
87     import core.stdc.stdio : sprintf;
88     import std.algorithm : min;
89     import core.stdc.string : memcpy, memset;
90 
91     enum SZ = 256;
92 
93     static char[SZ] port_buf;
94     static char[SZ] fnc_buf;
95     static char[SZ*3] buf;
96 
97     memset(port_buf.ptr, 0, SZ);
98     memset(fnc_buf.ptr, 0, SZ);
99     memset(buf.ptr, 0, SZ*3);
100     memcpy(port_buf.ptr, port.ptr, min(port.length, SZ));
101     memcpy(fnc_buf.ptr, fnc.ptr, min(fnc.length, SZ));
102     auto n = sprintf(buf.ptr, fmtSPSCEMsgFmt, fnc_buf.ptr, port_buf.ptr, err);
103     return cast(string)buf[0..n];
104 }
105 
106 unittest
107 {
108     import std.format : format;
109 
110     static auto fmtSPSCEMsgGC(string port, string fnc, int err)
111     { return format!fmtSPSCEMsgFmt(fnc, port, err); }
112 
113     void test(string port, string fnc, int err)
114     {
115         auto trg = fmtSPSCEMsg(port, fnc, err);
116         auto tst = fmtSPSCEMsgGC(port, fnc, err);
117         if (trg != tst) assert(0, "not equals:\n%s\n%s".format(trg, tst));
118     }
119 
120     test("/dev/ttyUSB0", "open", 2);
121     test("/very/very/very/very/very/very/very/very/very/very/big/path/to/com/port/device/dev",
122          "veryVeryVeryVeryLongFunctionName12345678901234567890123456789012345678901234567890",
123          int.max);
124     test("", "", 0);
125 }
126 
127 private mixin template throwSPSCEMix(E)
128     if (is(E: SysCallException))
129 {
130     enum name = E.stringof;
131     mixin(`
132     @preallocated private %1$s %2$s%1$s;
133     void throw%1$s(string port, string fnc, int err, string msg="",
134                     string file=__FILE__, size_t line=__LINE__) @nogc
135     { 
136         if (msg.length == 0)
137             msg = fmtSPSCEMsg(port, fnc, err);
138         auto e = %2$s%1$s.setFields(port, msg, file, line);
139         e.fnc = fnc;
140         e.err = err;
141         throw e;
142     }
143     `.format(name, prealloc_prefix)
144     );
145 }
146 
147 static this()
148 {
149     // can't use origin getSymbolsByUDA because
150     // https://issues.dlang.org/show_bug.cgi?id=20054
151     static if (__VERSION__ < 2088)
152     {
153         import std.traits : getSymbolsByUDA;
154         alias plist = getSymbolsByUDA!(mixin(__MODULE__), preallocated);
155     }
156     else
157     {
158         import std.meta : AliasSeq;
159 
160         alias plist = AliasSeq!(
161             preallocSerialPortException,
162             preallocPortClosedException,
163             preallocTimeoutException,
164             preallocSysCallException,
165             preallocReadException,
166             preallocWriteException,
167             preallocUnsupported
168         );
169     }
170 
171     static foreach (sym; plist) sym = new typeof(sym);
172 }
173 
174 mixin throwSPEMix!SerialPortException;
175 mixin throwSPEMix!PortClosedException;
176 mixin throwSPEMix!TimeoutException;
177 
178 mixin throwSPSCEMix!SysCallException;
179 mixin throwSPSCEMix!ReadException;
180 mixin throwSPSCEMix!WriteException;
181 
182 import serialport.types;
183 import core.stdc.stdio;
184 
185 private char[1024] UEMPB;
186 
187 @preallocated
188 private UnsupportedException preallocUnsupported;
189 
190 void throwUnsupportedException(string port, int baudrate,
191                                string file=__FILE__, size_t line=__LINE__) @nogc
192 {
193     auto ln = sprintf(UEMPB.ptr, "unsupported baudrate: %d", baudrate);
194     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
195 }
196 
197 void throwUnsupportedException(string port, DataBits dbits,
198                           string file=__FILE__, size_t line=__LINE__) @nogc
199 {
200     auto ln = sprintf(UEMPB.ptr, "unsupported data bits: %d", cast(int)dbits);
201     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
202 }
203 
204 void throwUnsupportedException(string port, StopBits sbits,
205                            string file=__FILE__, size_t line=__LINE__) @nogc
206 {
207     string str;
208     final switch (sbits) with (StopBits)
209     {
210         case one: str = "1\0"; break;
211         case two: str = "2\0"; break;
212         case onePointFive: str = "1.5\0"; break;
213     }
214 
215     auto ln = sprintf(UEMPB.ptr, "unsupported stop bits: %s", str.ptr);
216     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
217 }
218 
219 void throwUnsupportedException(string port, Parity parity,
220                            string file=__FILE__, size_t line=__LINE__) @nogc
221 {
222     string str;
223     final switch (parity) with (Parity)
224     {
225         case none: str = "none\0"; break;
226         case even: str = "even\0"; break;
227         case odd:  str = "odd\0"; break;
228     }
229     auto ln = sprintf(UEMPB.ptr, "unsupported parity: %s", str.ptr);
230     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
231 }