1 module nopromote;
2 
3 import std.stdio;
4 import std.traits;
5 import std.format;
6 
7 
8 private
9 pragma(LDC_inline_ir)
10     R inlineIR(string s, R, P...)(P);
11 
12 
13 /// Encapsulate a basic type. This wrapper will never return a value larger than the values operated on.
14 /// Otherwise it behaves like the underlying integer using a simple `alias this`.
15 struct NoPromote(T) {
16     enum string SIZE = (){
17         import std.conv: to;
18         return (T.sizeof*8).to!string;
19     }();
20 
21     static assert (isIntegral!T);
22     T base;
23     alias base this;
24     
25 
26     this(TT)(TT value) {
27         base = cast(typeof(base)) value;
28     }
29     
30     auto opAssign(R)(R value) {
31         base = cast(typeof(base)) value;
32         return this;
33     }
34 
35 
36     auto opBinary(string op, TT)(const NoPromote!TT rhs) {
37         /// Non commutative operations one might consider prioritizing the left hand side. 
38         /// I decided that this should absolutely be the case for shifts.
39         /// The jury is still out on / and %.
40         static if (op == "<<" || op == ">>" || op == ">>>") {
41             return opLLVM!(op)(this, cast(typeof(this)) rhs);
42         }
43 
44         /// The next two ensure that the largest type that the operands already share is used.
45         /// It prefers the signed/unsignedness of the left hand side.
46         else static if(typeof(this).sizeof >= typeof(rhs).sizeof) {
47             return opLLVM!(op)(this, cast(typeof(this)) rhs);
48         }
49         else static if(typeof(this).sizeof < typeof(rhs).sizeof) {
50             return opLLVM!(op)(cast(typeof(rhs)) this, rhs);
51         }
52         
53         else
54         return opLLVM!(op)(this, rhs);
55     }
56 
57 
58     auto opBinaryRight(string op, U)(const NoPromote!U lhs) {
59         return lhs.opBinary!(op)(this);
60     }
61 
62     
63     auto opBinary(string op, R)(const R rhs) {
64         static if (isIntegral!R) {
65             return this.opBinary!(op)(cast(NoPromote!R) rhs);
66         }
67     }
68 
69 
70     auto opBinaryRight(string op, L)(const L lhs) {
71         static if (isIntegral!L) {
72             return (cast(NoPromote!L) lhs).opBinary!(op)(this);
73         }
74     }
75 
76     
77 }
78 
79 
80 /// 8 bit signed integer.
81 alias i8 = NoPromote!byte;
82 /// 8 bit unsigned integer.
83 alias u8 = NoPromote!ubyte;
84 /// 16 bit signed integer.
85 alias i16 = NoPromote!short;
86 /// 16 bit unsigned integer.
87 alias u16 = NoPromote!ushort;
88 /// 32 bit signed integer.
89 alias i32 = NoPromote!int;
90 /// 32 bit unsigned integer.
91 alias u32 = NoPromote!uint;
92 /// 64 bit signed integer.
93 alias i64 = NoPromote!long;
94 /// 64 bit unsigned integer.
95 alias u64 = NoPromote!ulong;
96 
97 
98 unittest {
99     i8 a = 115;
100     int i = 10;
101     writeln("byte ", typeof(a + 5).stringof);
102     writeln("int ",  typeof(a + i).stringof);
103 }
104 
105 
106 private
107 auto opLLVM(string op, T)(NoPromote!T lhs, NoPromote!T rhs) {
108             enum string OPERATION = (){
109         static if (isIntegral!T) switch (op) {
110             case "+": return "add";
111             case "-": return "sub";
112             case "*": return "mul";
113             static if (isSigned!T) {
114             case "/":  return "sdiv";
115             case "%":  return "srem";
116             }else{
117             case "/":  return "udiv";
118             case "%":  return "urem";
119             }
120             case "<<":  return "shl";
121             case ">>":  return "lshr";
122             case ">>>": return "ashr";
123             case "&": return "and";
124             case "|": return "or";
125             case "^": return "xor";
126             default: assert(0);
127         }
128         static if (isFloatingPoint!T) switch (op) {
129             case "+": return "fadd";
130             case "-": return "fsub";
131             case "*": return "fmul";
132             case "/": return "fdiv";
133             case "%": return "frem";
134             // case "==": 
135             default: assert(0);
136         }
137     }();
138     import std.conv: to;
139     static if (isIntegral!T)
140         enum string TYPE = (){
141             return "i"~(T.sizeof*8).to!string;
142         }();
143     else
144         enum string TYPE = (){
145             return T.stringof;
146         }();
147 
148     return inlineIR!(`
149         %r = `~OPERATION~` `~TYPE~` %0, %1
150         ret `~TYPE~` %r
151     `, T)(lhs.base, rhs.base);
152 }