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     e.port = port;
57     e.msg = msg;
58     e.file = file;
59     e.line = line;
60     return e;
61 }
62 
63 import std.format;
64 
65 private enum preallocated;
66 private enum prealloc_prefix = "prealloc";
67 
68 private mixin template throwSPEMix(E, string defaultMsg="")
69     if (is(E: SerialPortException))
70 {
71     enum string name = E.stringof;
72     mixin(`
73     @preallocated private %1$s %2$s%1$s;
74     void throw%1$s(string port, string msg="%3$s",
75                    string file=__FILE__, size_t line=__LINE__) @nogc
76     { throw %2$s%1$s.setFields(port, msg, file, line); }
77     `.format(name, prealloc_prefix, (defaultMsg.length ? defaultMsg : name))
78     );
79 }
80 
81 private enum fmtSPSCEMsgFmt = "call '%s' (%s) failed: error %d";
82 
83 private string fmtSPSCEMsg(string port, string fnc, int err) @nogc
84 {
85     import core.stdc.stdio : sprintf;
86     import std.algorithm : min;
87     import core.stdc.string : memcpy, memset;
88 
89     enum SZ = 256;
90 
91     static char[SZ] port_buf;
92     static char[SZ] fnc_buf;
93     static char[SZ*3] buf;
94 
95     memset(port_buf.ptr, 0, SZ);
96     memset(fnc_buf.ptr, 0, SZ);
97     memset(buf.ptr, 0, SZ*3);
98     memcpy(port_buf.ptr, port.ptr, min(port.length, SZ));
99     memcpy(fnc_buf.ptr, fnc.ptr, min(fnc.length, SZ));
100     auto n = sprintf(buf.ptr, fmtSPSCEMsgFmt, fnc_buf.ptr, port_buf.ptr, err);
101     return cast(string)buf[0..n];
102 }
103 
104 unittest
105 {
106     import std.format : format;
107 
108     static auto fmtSPSCEMsgGC(string port, string fnc, int err)
109     { return format!fmtSPSCEMsgFmt(fnc, port, err); }
110 
111     void test(string port, string fnc, int err)
112     {
113         auto trg = fmtSPSCEMsg(port, fnc, err);
114         auto tst = fmtSPSCEMsgGC(port, fnc, err);
115         if (trg != tst) assert(0, "not equals:\n%s\n%s".format(trg, tst));
116     }
117 
118     test("/dev/ttyUSB0", "open", 2);
119     test("/very/very/very/very/very/very/very/very/very/very/big/path/to/com/port/device/dev",
120          "veryVeryVeryVeryLongFunctionName12345678901234567890123456789012345678901234567890",
121          int.max);
122     test("", "", 0);
123 }
124 
125 private mixin template throwSPSCEMix(E)
126     if (is(E: SysCallException))
127 {
128     enum name = E.stringof;
129     mixin(`
130     @preallocated private %1$s %2$s%1$s;
131     void throw%1$s(string port, string fnc, int err, string msg="",
132                     string file=__FILE__, size_t line=__LINE__) @nogc
133     { 
134         if (msg.length == 0)
135             msg = fmtSPSCEMsg(port, fnc, err);
136         auto e = %2$s%1$s.setFields(port, msg, file, line);
137         e.fnc = fnc;
138         e.err = err;
139         throw e;
140     }
141     `.format(name, prealloc_prefix)
142     );
143 }
144 
145 static this()
146 {
147     import std.traits : getSymbolsByUDA;
148     static foreach (sym; getSymbolsByUDA!(mixin(__MODULE__), preallocated))
149         sym = new typeof(sym);
150 }
151 
152 mixin throwSPEMix!SerialPortException;
153 mixin throwSPEMix!PortClosedException;
154 mixin throwSPEMix!TimeoutException;
155 
156 mixin throwSPSCEMix!SysCallException;
157 mixin throwSPSCEMix!ReadException;
158 mixin throwSPSCEMix!WriteException;
159 
160 
161 import serialport.types;
162 import core.stdc.stdio;
163 
164 private char[1024] UEMPB;
165 
166 @preallocated
167 private UnsupportedException preallocUnsupported;
168 
169 void throwUnsupportedException(string port, int baudrate,
170                                string file=__FILE__, size_t line=__LINE__) @nogc
171 {
172     auto ln = sprintf(UEMPB.ptr, "unsupported baudrate: %d", baudrate);
173     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
174 }
175 
176 void throwUnsupportedException(string port, DataBits dbits,
177                           string file=__FILE__, size_t line=__LINE__) @nogc
178 {
179     auto ln = sprintf(UEMPB.ptr, "unsupported data bits: %d", cast(int)dbits);
180     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
181 }
182 
183 void throwUnsupportedException(string port, StopBits sbits,
184                            string file=__FILE__, size_t line=__LINE__) @nogc
185 {
186     string str;
187     final switch (sbits) with (StopBits)
188     {
189         case one: str = "1\0"; break;
190         case two: str = "2\0"; break;
191         case onePointFive: str = "1.5\0"; break;
192     }
193 
194     auto ln = sprintf(UEMPB.ptr, "unsupported stop bits: %s", str.ptr);
195     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
196 }
197 
198 void throwUnsupportedException(string port, Parity parity,
199                            string file=__FILE__, size_t line=__LINE__) @nogc
200 {
201     string str;
202     final switch (parity) with (Parity)
203     {
204         case none: str = "none\0"; break;
205         case even: str = "even\0"; break;
206         case odd:  str = "odd\0"; break;
207     }
208     auto ln = sprintf(UEMPB.ptr, "unsupported parity: %s", str.ptr);
209     throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line);
210 }