├── README.md ├── arsd-webassembly ├── arsd │ ├── color.d │ ├── simpleaudio.d │ ├── simpledisplay.d │ └── webassembly.d ├── core │ ├── arsd │ │ ├── aa.d │ │ ├── memory_allocation.d │ │ ├── objectutils.d │ │ └── utf_decoding.d │ └── internal │ │ └── utf.d ├── object.d └── std │ ├── random.d │ └── stdio.d ├── build.bat ├── fail.d ├── hello.d ├── server ├── Makefile ├── serve.d ├── webassembly-core.js └── webassembly-skeleton.html ├── test_runtime.d └── tetris.d /README.md: -------------------------------------------------------------------------------- 1 | This repo contains my webassembly stuff, intended for use with ldc. 2 | 3 | arsd-webassembly is the library code, including partial source ports 4 | of some libraries I use and a minimal druntime for use on the web. 5 | You should compile with these modules instead of the real libraries. 6 | 7 | server is a little web server and the other bridge code in javascript 8 | and html. Of course you don't need to use my webserver. 9 | 10 | This is EXTREMELY MINIMAL. I only wrote what I needed for my demo. Your 11 | use cases will probably not work. 12 | -------------------------------------------------------------------------------- /arsd-webassembly/arsd/color.d: -------------------------------------------------------------------------------- 1 | module arsd.color; 2 | 3 | char toHex(int a) { 4 | if(a < 10) 5 | return cast(char) (a + '0'); 6 | else 7 | return cast(char) (a - 10 + 'a'); 8 | } 9 | 10 | struct Color { 11 | int r, g, b, a; 12 | this(int r, int g, int b, int a = 255) { 13 | this.r = r; 14 | this.g = g; 15 | this.b = b; 16 | this.a = a; 17 | } 18 | 19 | void toTempString(char[] data) { 20 | data[0] = '#'; 21 | data[1] = toHex(r >> 4); 22 | data[2] = toHex(r & 0x0f); 23 | data[3] = toHex(g >> 4); 24 | data[4] = toHex(g & 0x0f); 25 | data[5] = toHex(b >> 4); 26 | data[6] = toHex(b & 0x0f); 27 | } 28 | 29 | static Color fromHsl(double h, double s, double l, double a = 255) { 30 | h = h % 360; 31 | 32 | double C = (1 - absInternal(2 * l - 1)) * s; 33 | 34 | double hPrime = h / 60; 35 | 36 | double X = C * (1 - absInternal(hPrime % 2 - 1)); 37 | 38 | double r, g, b; 39 | 40 | if(h is double.nan) 41 | r = g = b = 0; 42 | else if (hPrime >= 0 && hPrime < 1) { 43 | r = C; 44 | g = X; 45 | b = 0; 46 | } else if (hPrime >= 1 && hPrime < 2) { 47 | r = X; 48 | g = C; 49 | b = 0; 50 | } else if (hPrime >= 2 && hPrime < 3) { 51 | r = 0; 52 | g = C; 53 | b = X; 54 | } else if (hPrime >= 3 && hPrime < 4) { 55 | r = 0; 56 | g = X; 57 | b = C; 58 | } else if (hPrime >= 4 && hPrime < 5) { 59 | r = X; 60 | g = 0; 61 | b = C; 62 | } else if (hPrime >= 5 && hPrime < 6) { 63 | r = C; 64 | g = 0; 65 | b = X; 66 | } 67 | 68 | double m = l - C / 2; 69 | 70 | r += m; 71 | g += m; 72 | b += m; 73 | 74 | return Color( 75 | cast(int)(r * 255), 76 | cast(int)(g * 255), 77 | cast(int)(b * 255), 78 | cast(int)(a)); 79 | } 80 | 81 | static immutable Color white = Color(255, 255, 255, 255); 82 | static immutable Color black = Color(0, 0, 0, 255); 83 | static immutable Color red = Color(255, 0, 0, 255); 84 | static immutable Color blue = Color(0, 0, 255, 255); 85 | static immutable Color green = Color(0, 255, 0, 255); 86 | static immutable Color yellow = Color(255, 255, 0, 255); 87 | static immutable Color teal = Color(0, 255, 255, 255); 88 | static immutable Color purple = Color(255, 0, 255, 255); 89 | static immutable Color gray = Color(127, 127, 127, 255); 90 | static immutable Color transparent = Color(0, 0, 0, 0); 91 | } 92 | 93 | struct Point { 94 | int x; 95 | int y; 96 | 97 | pure const nothrow @safe: 98 | 99 | Point opBinary(string op)(in Point rhs) @nogc { 100 | return Point(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y")); 101 | } 102 | 103 | Point opBinary(string op)(int rhs) @nogc { 104 | return Point(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs")); 105 | } 106 | 107 | } 108 | 109 | struct Size { 110 | int width; 111 | int height; 112 | } 113 | 114 | 115 | nothrow @safe @nogc pure 116 | double absInternal(double a) { return a < 0 ? -a : a; } 117 | 118 | struct Rectangle { 119 | int left; /// 120 | int top; /// 121 | int right; /// 122 | int bottom; /// 123 | 124 | pure const nothrow @safe @nogc: 125 | 126 | /// 127 | this(int left, int top, int right, int bottom) { 128 | this.left = left; 129 | this.top = top; 130 | this.right = right; 131 | this.bottom = bottom; 132 | } 133 | 134 | /// 135 | this(in Point upperLeft, in Point lowerRight) { 136 | this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); 137 | } 138 | 139 | /// 140 | this(in Point upperLeft, in Size size) { 141 | this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height); 142 | } 143 | 144 | /// 145 | @property Point upperLeft() { 146 | return Point(left, top); 147 | } 148 | 149 | /// 150 | @property Point upperRight() { 151 | return Point(right, top); 152 | } 153 | 154 | /// 155 | @property Point lowerLeft() { 156 | return Point(left, bottom); 157 | } 158 | 159 | /// 160 | @property Point lowerRight() { 161 | return Point(right, bottom); 162 | } 163 | 164 | /// 165 | @property Point center() { 166 | return Point((right + left) / 2, (bottom + top) / 2); 167 | } 168 | 169 | /// 170 | @property Size size() { 171 | return Size(width, height); 172 | } 173 | 174 | /// 175 | @property int width() { 176 | return right - left; 177 | } 178 | 179 | /// 180 | @property int height() { 181 | return bottom - top; 182 | } 183 | 184 | /// Returns true if this rectangle entirely contains the other 185 | bool contains(in Rectangle r) { 186 | return contains(r.upperLeft) && contains(r.lowerRight); 187 | } 188 | 189 | /// ditto 190 | bool contains(in Point p) { 191 | return (p.x >= left && p.x < right && p.y >= top && p.y < bottom); 192 | } 193 | 194 | /// Returns true of the two rectangles at any point overlap 195 | bool overlaps(in Rectangle r) { 196 | // the -1 in here are because right and top are exclusive 197 | return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top); 198 | } 199 | 200 | /++ 201 | Returns a Rectangle representing the intersection of this and the other given one. 202 | 203 | History: 204 | Added July 1, 2021 205 | +/ 206 | Rectangle intersectionOf(in Rectangle r) { 207 | auto tmp = Rectangle(max(left, r.left), max(top, r.top), min(right, r.right), min(bottom, r.bottom)); 208 | if(tmp.left >= tmp.right || tmp.top >= tmp.bottom) 209 | tmp = Rectangle.init; 210 | 211 | return tmp; 212 | } 213 | } 214 | 215 | private int max(int a, int b) @nogc nothrow pure @safe { 216 | return a >= b ? a : b; 217 | } 218 | private int min(int a, int b) @nogc nothrow pure @safe { 219 | return a <= b ? a : b; 220 | } 221 | 222 | 223 | enum arsd_jsvar_compatible = "arsd_jsvar_compatible"; 224 | class MemoryImage {} 225 | -------------------------------------------------------------------------------- /arsd-webassembly/arsd/simpleaudio.d: -------------------------------------------------------------------------------- 1 | module arsd.simpleaudio; 2 | 3 | struct AudioOutputThread { 4 | this(int) {} 5 | void start() {} 6 | void beep(int = 0) {} 7 | void boop(int = 0) {} 8 | } 9 | -------------------------------------------------------------------------------- /arsd-webassembly/arsd/simpledisplay.d: -------------------------------------------------------------------------------- 1 | module arsd.simpledisplay; 2 | 3 | public import arsd.color; 4 | 5 | import arsd.webassembly; 6 | 7 | //shared static this() { eval("hi there"); } 8 | 9 | // the js bridge is SO EXPENSIVE we have to minimize using it. 10 | 11 | class SimpleWindow { 12 | this(int width, int height, string title = "D Application") { 13 | this.width = width; 14 | this.height = height; 15 | 16 | element = eval!NativeHandle(q{ 17 | var s = document.getElementById("screen"); 18 | var canvas = document.createElement("canvas"); 19 | canvas.addEventListener("contextmenu", function(event) { event.preventDefault(); }); 20 | canvas.setAttribute("width", $0); 21 | canvas.setAttribute("height", $1); 22 | canvas.setAttribute("title", $2); 23 | s.appendChild(canvas); 24 | return canvas; 25 | }, width, height, title); 26 | 27 | canvasContext = eval!NativeHandle(q{ 28 | return $0.getContext("2d"); 29 | }, element); 30 | } 31 | 32 | NativeHandle element; 33 | NativeHandle canvasContext; 34 | int width; 35 | int height; 36 | 37 | void close() { 38 | eval(q{ clearInterval($0); }, intervalId); 39 | intervalId = 0; 40 | } 41 | 42 | void delegate() onClosing; 43 | 44 | ScreenPainter draw() { 45 | return ScreenPainter(this); 46 | } 47 | 48 | int intervalId; 49 | 50 | void eventLoop(T...)(int timeout, T t) { 51 | foreach(arg; t) { 52 | static if(is(typeof(arg) : void delegate())) { 53 | sdpy_timer = arg; 54 | } else static if(is(typeof(arg) : void delegate(KeyEvent))) { 55 | sdpy_key = arg; 56 | } else static if(is(typeof(arg) : void delegate(MouseEvent))) { 57 | sdpy_mouse = arg; 58 | } else static assert(0, typeof(arg).stringof); 59 | } 60 | 61 | if(timeout) 62 | intervalId = eval!int(q{ 63 | return setInterval(function(a) { exports.sdpy_timer_trigger(); }, $0); 64 | }, timeout); 65 | 66 | eval(q{ 67 | function translate(key) { 68 | var k = 0; 69 | switch(key) { 70 | case "[": k = 1; break; 71 | case "]": k = 2; break; 72 | case "Left": case "ArrowLeft": k = 3; break; 73 | case "Right": case "ArrowRight": k = 4; break; 74 | case "Down": case "ArrowDown": k = 5; break; 75 | case "Up": case "ArrowUp": k = 6; break; 76 | case " ": k = 7; break; 77 | // "Enter", "Esc" / "Escape" 78 | default: k = 0; 79 | } 80 | return k; 81 | } 82 | document.body.addEventListener("keydown", function(event) { 83 | exports.sdpy_key_trigger(1, translate(event.key)); 84 | event.preventDefault(); 85 | }, true); 86 | document.body.addEventListener("keyup", function(event) { 87 | exports.sdpy_key_trigger(0, translate(event.key)); 88 | event.preventDefault(); 89 | }, true); 90 | $0.addEventListener("mousedown", function(event) { 91 | exports.sdpy_mouse_trigger(1, event.button, event.offsetX, event.offsetY); 92 | }, true); 93 | $0.addEventListener("mouseup", function(event) { 94 | exports.sdpy_mouse_trigger(0, event.button); 95 | }, true); 96 | }, element); 97 | 98 | } 99 | } 100 | 101 | void delegate() sdpy_timer; 102 | void delegate(KeyEvent) sdpy_key; 103 | void delegate(MouseEvent) sdpy_mouse; 104 | 105 | export extern(C) void sdpy_timer_trigger() { 106 | sdpy_timer(); 107 | } 108 | export extern(C) void sdpy_key_trigger(int pressed, int key) { 109 | KeyEvent ke; 110 | ke.pressed = pressed ? true : false; 111 | ke.key = key; 112 | if(sdpy_key) 113 | sdpy_key(ke); 114 | } 115 | export extern(C) void sdpy_mouse_trigger(int pressed, int button, int x, int y) { 116 | MouseEvent me; 117 | me.type = pressed ? MouseEventType.buttonPressed : MouseEventType.buttonReleased; 118 | switch(button) { 119 | case 0: 120 | me.button = MouseButton.left; 121 | break; 122 | case 1: 123 | me.button = MouseButton.middle; 124 | break; 125 | case 2: 126 | me.button = MouseButton.right; 127 | break; 128 | default: 129 | } 130 | me.x = x; 131 | me.y = y; 132 | if(sdpy_mouse) 133 | sdpy_mouse(me); 134 | 135 | } 136 | 137 | 138 | // push arguments in reverse order then push the command 139 | enum canvasRender = q{ 140 | }; 141 | 142 | struct ScreenPainter { 143 | this(SimpleWindow window) { 144 | // no need to arc here tbh 145 | this.w = window.width; 146 | this.h = window.height; 147 | 148 | this.element = NativeHandle(window.element.handle, false); 149 | this.context = NativeHandle(window.canvasContext.handle, false); 150 | } 151 | @disable this(this); // for now... 152 | NativeHandle element; 153 | NativeHandle context; 154 | 155 | private int w, h; 156 | 157 | void clear() { 158 | addCommand(1); 159 | } 160 | 161 | void outlineColor(Color c) { 162 | char[7] data; 163 | c.toTempString(data[]); 164 | addCommand(2, 7, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); 165 | return; 166 | //context.properties.strokeStyle!string = cast(immutable)(data[]); 167 | } 168 | void fillColor(Color c) { 169 | char[7] data; 170 | c.toTempString(data[]); 171 | addCommand(3, 7, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); 172 | return; 173 | //context.properties.fillStyle!string = cast(immutable)(data[]); 174 | } 175 | 176 | void drawPolygon(Point[] points) { 177 | addCommand(8); 178 | addCommand(cast(double) points.length); 179 | foreach(point; points) { 180 | push(cast(double) point.x); 181 | push(cast(double) point.y); 182 | } 183 | } 184 | 185 | void drawRectangle(Point p, int w, int h) { 186 | addCommand(4, p.x, p.y, w, h); 187 | } 188 | void drawRectangle(Point p, Size s) { 189 | drawRectangle(p, s.width, s.height); 190 | } 191 | void drawText(Point p, in char[] txt, Point lowerRight = Point(0, 0), uint alignment = 0) { 192 | // FIXME use the new system 193 | addCommand(5, p.x, p.y + 16, txt.length); 194 | foreach(c; txt) 195 | push(cast(double) c); 196 | return; 197 | eval(q{ 198 | var context = $0; 199 | context.font = "18px sans-serif"; 200 | context.strokeText($1, $2, $3 + 16); 201 | }, context, txt, p.x, p.y); 202 | } 203 | 204 | void drawCircle(Point upperLeft, int diameter) { 205 | addCommand(6, upperLeft.x + diameter / 2, upperLeft.y + diameter / 2, diameter / 2); 206 | } 207 | 208 | void drawLine(Point p1, Point p2) { 209 | drawLine(p1.x, p1.y, p2.x, p2.y); 210 | } 211 | 212 | void drawLine(int x1, int y1, int x2, int y2) { 213 | addCommand(7, x1, y1, x2, y2); 214 | } 215 | 216 | private: 217 | void addCommand(T...)(int cmd, T args) { 218 | push(cmd); 219 | foreach(arg; args) { 220 | push(arg); 221 | } 222 | } 223 | 224 | // 50ish % on ronaroids total cpu without this 225 | // with it, we at like 16% 226 | static __gshared double[] commandStack; 227 | size_t commandStackPosition; 228 | 229 | void push(T)(T t) { 230 | if(commandStackPosition == commandStack.length) { 231 | commandStack.length = commandStack.length + 1024; 232 | commandStack.assumeUniqueReference(); 233 | } 234 | 235 | commandStack[commandStackPosition++] = t; 236 | } 237 | 238 | ~this() { 239 | executeCanvasCommands(this.context.handle, this.commandStack.ptr, commandStackPosition); 240 | } 241 | } 242 | 243 | extern(C) void executeCanvasCommands(int handle, double* start, size_t len); 244 | 245 | struct KeyEvent { 246 | int key; 247 | bool pressed; 248 | } 249 | 250 | enum MouseEventType : int { 251 | motion = 0, /// The mouse moved inside the window 252 | buttonPressed = 1, /// A mouse button was pressed or the wheel was spun 253 | buttonReleased = 2, /// A mouse button was released 254 | } 255 | 256 | struct MouseEvent { 257 | MouseEventType type; 258 | int x; 259 | int y; 260 | int dx; 261 | int dy; 262 | 263 | MouseButton button; 264 | int modifierState; 265 | } 266 | 267 | enum MouseButton : int { 268 | none = 0, 269 | left = 1, /// 270 | right = 2, /// 271 | middle = 4, /// 272 | wheelUp = 8, /// 273 | wheelDown = 16, /// 274 | backButton = 32, /// often found on the thumb and used for back in browsers 275 | forwardButton = 64, /// often found on the thumb and used for forward in browsers 276 | } 277 | 278 | enum TextAlignment : uint { 279 | Left = 0, /// 280 | Center = 1, /// 281 | Right = 2, /// 282 | 283 | VerticalTop = 0, /// 284 | VerticalCenter = 4, /// 285 | VerticalBottom = 8, /// 286 | } 287 | 288 | enum Key { 289 | LeftBracket = 1, 290 | RightBracket, 291 | Left, 292 | Right, 293 | Down, 294 | Up, 295 | Space 296 | } 297 | 298 | enum MouseCursor { cross } 299 | 300 | class OperatingSystemFont {} 301 | enum UsingSimpledisplayX11 = false; 302 | enum SimpledisplayTimerAvailable = false; 303 | 304 | 305 | class Sprite{} 306 | 307 | enum bool OpenGlEnabled = false; 308 | 309 | alias ScreenPainterImplementation = ScreenPainter; 310 | 311 | mixin template ExperimentalTextComponent() { 312 | class TextLayout { 313 | 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /arsd-webassembly/arsd/webassembly.d: -------------------------------------------------------------------------------- 1 | /+ 2 | This is the D interface to my webassembly javascript bridge. 3 | +/ 4 | module arsd.webassembly; 5 | 6 | struct AcquireArgument { 7 | int type; 8 | const(void)* ptr; 9 | int length; 10 | } 11 | 12 | // the basic bridge functions defined in webassembly-core.js { 13 | 14 | @trusted @nogc pure nothrow 15 | { 16 | extern(C) void retain(int); 17 | extern(C) void release(int); 18 | extern(C) int acquire(int returnType, string callingModuleName, string code, AcquireArgument[] arguments); 19 | extern(C) void abort(); 20 | extern(C) int monotimeNow(); 21 | } 22 | 23 | 24 | // } 25 | 26 | export extern(C) int invoke_d_array_delegate(size_t ptr, size_t funcptr, ubyte[] arg) { 27 | void delegate(in ubyte[] arr) dg; 28 | 29 | dg.ptr = cast(void*) ptr; 30 | dg.funcptr = cast(typeof(dg.funcptr)) funcptr; 31 | 32 | dg(arg); 33 | return 0; 34 | 35 | }; 36 | 37 | /++ 38 | Evaluates the given code in Javascript. The arguments are available in JS as $0, $1, $2, .... 39 | The `this` object in the evaluated code is set to an object representing the D module that 40 | you can store some stuff in across calls without having to hit the global namespace. 41 | 42 | Note that if you want to return a value from javascript, you MUST use the return keyword 43 | in the script string. 44 | 45 | Wrong: `eval!NativeHandle("document");` 46 | 47 | Right: `eval!NativeHandle("return document");` 48 | +/ 49 | template eval(T = void) { 50 | T eval(Args...)(string code, Args args, string callingModuleName = __MODULE__) @trusted @nogc pure { 51 | AcquireArgument[Args.length] aa; 52 | foreach(idx, ref arg; args) { 53 | // FIXME: some other type for unsigned.... 54 | static if(is(typeof(arg) : const int)) { 55 | aa[idx].type = 0; 56 | aa[idx].ptr = cast(void*) arg; 57 | aa[idx].length = arg.sizeof; 58 | } else static if(is(immutable typeof(arg) == immutable string)) { 59 | aa[idx].type = 1; 60 | aa[idx].ptr = arg.ptr; 61 | aa[idx].length = arg.length; 62 | } else static if(is(immutable typeof(arg) == immutable NativeHandle)) { 63 | aa[idx].type = 2; 64 | aa[idx].ptr = cast(void*) arg.handle; 65 | aa[idx].length = NativeHandle.sizeof; 66 | } else static if(is(typeof(arg) : const float)) { 67 | aa[idx].type = 3; 68 | aa[idx].ptr = cast(void*) &arg; 69 | aa[idx].length = arg.sizeof; 70 | } else static if(is(immutable typeof(arg) == immutable ubyte[])) { 71 | aa[idx].type = 4; 72 | aa[idx].ptr = arg.ptr; 73 | aa[idx].length = arg.length; 74 | /* 75 | } else static if(is(typeof(arg) == delegate)) { 76 | aa[idx].type = 5; 77 | aa[idx].ptr = cast(void*) &arg; 78 | aa[idx].length = arg.sizeof; 79 | */ 80 | } else { 81 | static assert(0); 82 | } 83 | } 84 | static if(is(T == void)) 85 | acquire(0, callingModuleName, code, aa[]); 86 | else static if(is(T == int)) 87 | return acquire(1, callingModuleName, code, aa[]); 88 | else static if(is(T == float)) 89 | return *cast(float*) cast(void*) acquire(2, callingModuleName, code, aa[]); 90 | else static if(is(T == NativeHandle)) 91 | return NativeHandle(acquire(3, callingModuleName, code, aa[])); 92 | else static if(is(T == string)) { 93 | auto ptr = cast(int*) acquire(7, callingModuleName, code, aa[]); 94 | auto len = *ptr; 95 | ptr++; 96 | return (cast(immutable(char)*) ptr)[0 .. len]; 97 | } 98 | else static assert(0); 99 | } 100 | } 101 | 102 | // and do some opDispatch on the native things to call their methods and it should look p cool 103 | 104 | struct NativeHandle { 105 | @trusted @nogc pure: 106 | 107 | int handle; 108 | bool arc; 109 | this(int handle, bool arc = true) { 110 | this.handle = handle; 111 | this.arc = arc; 112 | } 113 | 114 | this(this) { 115 | if(arc) retain(handle); 116 | } 117 | 118 | ~this() { 119 | if(arc) release(handle); 120 | } 121 | 122 | // never store these, they don't affect the refcount 123 | PropertiesHelper properties() { 124 | return PropertiesHelper(handle); 125 | } 126 | 127 | // never store these, they don't affect the refcount 128 | MethodsHelper methods() { 129 | return MethodsHelper(handle); 130 | 131 | } 132 | } 133 | 134 | struct MethodsHelper { 135 | @trusted @nogc pure: 136 | @disable this(); 137 | @disable this(this); 138 | 139 | int handle; 140 | private this(int handle) { this.handle = handle; } 141 | 142 | template opDispatch(string name) { 143 | template opDispatch(T = NativeHandle) 144 | { 145 | T opDispatch(Args...)(Args args, string callingModuleName = __MODULE__) @trusted @nogc pure 146 | { 147 | return eval!T(q{ 148 | return $0[$1].apply($0, Array.prototype.slice.call(arguments, 2)); 149 | }, NativeHandle(this.handle, false), name, args, callingModuleName); 150 | } 151 | } 152 | } 153 | 154 | } 155 | struct PropertiesHelper { 156 | @trusted @nogc pure: 157 | @disable this(); 158 | @disable this(this); 159 | 160 | int handle; 161 | private this(int handle) { this.handle = handle; } 162 | 163 | template opDispatch(string name) { 164 | template opDispatch(T = NativeHandle) { 165 | T opDispatch() { 166 | return eval!T(q{ 167 | return $0[$1]; 168 | }, NativeHandle(this.handle, false), name); 169 | } 170 | 171 | void opDispatch(T value) { 172 | return eval!void(q{ 173 | return $0[$1] = $2; 174 | }, NativeHandle(this.handle, false), name, value); 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /arsd-webassembly/core/arsd/aa.d: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of associative arrays. 3 | * 4 | * Copyright: Copyright Digital Mars 2000 - 2015. 5 | * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 | * Authors: Martin Nowak 7 | * Source: $(DRUNTIMESRC rt/_aaA.d) 8 | */ 9 | module core.arsd.aa; 10 | 11 | /// AA version for debuggers, bump whenever changing the layout 12 | extern (C) immutable int _aaVersion = 1; 13 | 14 | import core.internal.hash; 15 | import core.arsd.memory_allocation; 16 | 17 | uint min(uint a, uint b) { return a < b ? a : b; } 18 | uint max(uint a, uint b) { return a > b ? a : b; } 19 | 20 | // grow threshold 21 | private enum GROW_NUM = 4; 22 | private enum GROW_DEN = 5; 23 | // shrink threshold 24 | private enum SHRINK_NUM = 1; 25 | private enum SHRINK_DEN = 8; 26 | // grow factor 27 | private enum GROW_FAC = 4; 28 | // growing the AA doubles it's size, so the shrink threshold must be 29 | // smaller than half the grow threshold to have a hysteresis 30 | static assert(GROW_FAC * SHRINK_NUM * GROW_DEN < GROW_NUM * SHRINK_DEN); 31 | // initial load factor (for literals), mean of both thresholds 32 | private enum INIT_NUM = (GROW_DEN * SHRINK_NUM + GROW_NUM * SHRINK_DEN) / 2; 33 | private enum INIT_DEN = SHRINK_DEN * GROW_DEN; 34 | 35 | private enum INIT_NUM_BUCKETS = 8; 36 | // magic hash constants to distinguish empty, deleted, and filled buckets 37 | private enum HASH_EMPTY = 0; 38 | private enum HASH_DELETED = 0x1; 39 | private enum HASH_FILLED_MARK = size_t(1) << 8 * size_t.sizeof - 1; 40 | 41 | // The compiler uses `void*` for its prototypes. 42 | // Don't wrap in a struct to maintain ABI compatibility. 43 | alias AA = Impl*; 44 | 45 | private bool empty(scope const AA impl) pure nothrow @nogc 46 | { 47 | return impl is null || !impl.length; 48 | } 49 | 50 | private struct Impl 51 | { 52 | private: 53 | this(scope const TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) 54 | { 55 | keysz = cast(uint) ti.key.size; 56 | valsz = cast(uint) ti.value.size; 57 | buckets = allocBuckets(sz); 58 | firstUsed = cast(uint) buckets.length; 59 | valoff = cast(uint) talign(keysz, ti.value.talign); 60 | 61 | import core.arsd.objectutils : hasPostblit; 62 | 63 | if (hasPostblit(cast()ti.key)) 64 | flags |= Flags.keyHasPostblit; 65 | if ((ti.key.flags | ti.value.flags) & 1) 66 | flags |= Flags.hasPointers; 67 | 68 | entryTI = fakeEntryTI(this, ti.key, ti.value); 69 | } 70 | 71 | Bucket[] buckets; 72 | uint used; 73 | uint deleted; 74 | TypeInfo_Struct entryTI; 75 | uint firstUsed; 76 | immutable uint keysz; 77 | immutable uint valsz; 78 | immutable uint valoff; 79 | Flags flags; 80 | 81 | enum Flags : ubyte 82 | { 83 | none = 0x0, 84 | keyHasPostblit = 0x1, 85 | hasPointers = 0x2, 86 | } 87 | 88 | @property size_t length() const pure nothrow @nogc 89 | { 90 | assert(used >= deleted); 91 | return used - deleted; 92 | } 93 | 94 | @property size_t dim() const pure nothrow @nogc @safe 95 | { 96 | return buckets.length; 97 | } 98 | 99 | @property size_t mask() const pure nothrow @nogc 100 | { 101 | return dim - 1; 102 | } 103 | 104 | // find the first slot to insert a value with hash 105 | inout(Bucket)* findSlotInsert(size_t hash) inout pure nothrow @nogc 106 | { 107 | for (size_t i = hash & mask, j = 1;; ++j) 108 | { 109 | if (!buckets[i].filled) 110 | return &buckets[i]; 111 | i = (i + j) & mask; 112 | } 113 | } 114 | 115 | // lookup a key 116 | inout(Bucket)* findSlotLookup(size_t hash, scope const void* pkey, scope const TypeInfo keyti) inout 117 | { 118 | for (size_t i = hash & mask, j = 1;; ++j) 119 | { 120 | if (buckets[i].hash == hash && keyti.equals(pkey, buckets[i].entry)) 121 | return &buckets[i]; 122 | else if (buckets[i].empty) 123 | return null; 124 | i = (i + j) & mask; 125 | } 126 | } 127 | 128 | void grow(scope const TypeInfo keyti) 129 | { 130 | // If there are so many deleted entries, that growing would push us 131 | // below the shrink threshold, we just purge deleted entries instead. 132 | if (length * SHRINK_DEN < GROW_FAC * dim * SHRINK_NUM) 133 | resize(dim); 134 | else 135 | resize(GROW_FAC * dim); 136 | } 137 | 138 | void shrink(scope const TypeInfo keyti) 139 | { 140 | if (dim > INIT_NUM_BUCKETS) 141 | resize(dim / GROW_FAC); 142 | } 143 | 144 | void resize(size_t ndim) 145 | { 146 | auto obuckets = buckets; 147 | buckets = allocBuckets(ndim); 148 | 149 | foreach (ref b; obuckets[firstUsed .. $]) 150 | if (b.filled) 151 | *findSlotInsert(b.hash) = b; 152 | 153 | firstUsed = 0; 154 | used -= deleted; 155 | deleted = 0; 156 | free(cast(ubyte*)(obuckets.ptr)); // safe to free b/c impossible to reference 157 | } 158 | 159 | void clear() pure nothrow 160 | { 161 | import core.stdc.string : memset; 162 | // clear all data, but don't change bucket array length 163 | memset(&buckets[firstUsed], 0, (buckets.length - firstUsed) * Bucket.sizeof); 164 | deleted = used = 0; 165 | firstUsed = cast(uint) dim; 166 | } 167 | } 168 | 169 | //============================================================================== 170 | // Bucket 171 | //------------------------------------------------------------------------------ 172 | 173 | private struct Bucket 174 | { 175 | private pure nothrow @nogc: 176 | size_t hash; 177 | void* entry; 178 | 179 | @property bool empty() const 180 | { 181 | return hash == HASH_EMPTY; 182 | } 183 | 184 | @property bool deleted() const 185 | { 186 | return hash == HASH_DELETED; 187 | } 188 | 189 | @property bool filled() const @safe 190 | { 191 | return cast(ptrdiff_t) hash < 0; 192 | } 193 | } 194 | 195 | Bucket[] allocBuckets(size_t dim) @trusted 196 | { 197 | enum attr = 0b0001_0000; //enum attr = GC.BlkAttr.NO_INTERIOR; 198 | immutable sz = dim * Bucket.sizeof; 199 | return (cast(Bucket*) calloc(sz, attr))[0 .. dim]; 200 | } 201 | 202 | //============================================================================== 203 | // Entry 204 | //------------------------------------------------------------------------------ 205 | 206 | private void* allocEntry(scope const Impl* aa, scope const void* pkey) 207 | { 208 | immutable akeysz = aa.valoff; 209 | void* res = void; 210 | if(aa.entryTI) 211 | res = _d_newitemU(aa.entryTI); 212 | else 213 | res = malloc(akeysz + aa.valsz).ptr; 214 | 215 | memcpy(res, pkey, aa.keysz); // copy key 216 | memset(res + akeysz, 0, aa.valsz); // zero value 217 | 218 | return res; 219 | } 220 | 221 | package void entryDtor(void* p, const TypeInfo_Struct sti) 222 | { 223 | // key and value type info stored after the TypeInfo_Struct by tiEntry() 224 | auto sizeti = __traits(classInstanceSize, TypeInfo_Struct); 225 | auto extra = cast(const(TypeInfo)*)(cast(void*) sti + sizeti); 226 | extra[0].destroy(p); 227 | extra[1].destroy(p + talign(extra[0].size, extra[1].talign)); 228 | } 229 | 230 | private bool hasDtor(const TypeInfo ti) pure nothrow 231 | { 232 | 233 | if (typeid(ti) is typeid(TypeInfo_Struct)) 234 | if ((cast(TypeInfo_Struct) cast(void*) ti).xdtor) 235 | return true; 236 | if (typeid(ti) is typeid(TypeInfo_StaticArray)) 237 | return hasDtor(cast()ti.next); 238 | 239 | return false; 240 | } 241 | 242 | // build type info for Entry with additional key and value fields 243 | TypeInfo_Struct fakeEntryTI(ref Impl aa, const TypeInfo keyti, const TypeInfo valti) 244 | { 245 | import core.arsd.objectutils; 246 | //Same as unqualify 247 | auto kti = unqualify(keyti); 248 | auto vti = unqualify(valti); 249 | 250 | 251 | bool entryHasDtor = hasDtor(kti) || hasDtor(vti); 252 | if (!entryHasDtor) 253 | return null; 254 | 255 | // save kti and vti after type info for struct 256 | enum sizeti = __traits(classInstanceSize, TypeInfo_Struct); 257 | void* p = malloc(sizeti + (2) * (void*).sizeof).ptr; 258 | 259 | memcpy(p, __traits(initSymbol, TypeInfo_Struct).ptr, sizeti); 260 | 261 | auto ti = cast(TypeInfo_Struct) p; 262 | auto extra = cast(TypeInfo*)(p + sizeti); 263 | extra[0] = cast() kti; 264 | extra[1] = cast() vti; 265 | 266 | static immutable tiMangledName = "S2rt3aaA__T5EntryZ"; 267 | ti.name = tiMangledName; 268 | 269 | 270 | // we don't expect the Entry objects to be used outside of this module, so we have control 271 | // over the non-usage of the callback methods and other entries and can keep these null 272 | // xtoHash, xopEquals, xopCmp, xtoString and xpostblit 273 | immutable entrySize = aa.valoff + aa.valsz; 274 | ti.m_init = (cast(ubyte*) null)[0 .. entrySize]; // init length, but not ptr 275 | 276 | if (entryHasDtor) 277 | { 278 | // xdtor needs to be built from the dtors of key and value for the GC 279 | ti.xdtorti = &entryDtor; 280 | ti.m_flags |= TypeInfo_Struct.StructFlags.isDynamicType; 281 | } 282 | 283 | ti.align_ = cast(uint) max(kti.talign, vti.talign); 284 | 285 | return ti; 286 | } 287 | 288 | 289 | //============================================================================== 290 | // Helper functions 291 | //------------------------------------------------------------------------------ 292 | 293 | private size_t talign(size_t tsize, size_t algn) @safe pure nothrow @nogc 294 | { 295 | immutable mask = algn - 1; 296 | assert(!(mask & algn)); 297 | return (tsize + mask) & ~mask; 298 | } 299 | 300 | // mix hash to "fix" bad hash functions 301 | private size_t mix(size_t h) @safe pure nothrow @nogc 302 | { 303 | // final mix function of MurmurHash2 304 | enum m = 0x5bd1e995; 305 | h ^= h >> 13; 306 | h *= m; 307 | h ^= h >> 15; 308 | return h; 309 | } 310 | 311 | private size_t calcHash(scope const void* pkey, scope const TypeInfo keyti) nothrow 312 | { 313 | immutable hash = keyti.getHash(pkey); 314 | // highest bit is set to distinguish empty/deleted from filled buckets 315 | return mix(hash) | HASH_FILLED_MARK; 316 | } 317 | 318 | private size_t nextpow2(const size_t n) pure nothrow @nogc 319 | { 320 | import core.bitop : bsr; 321 | 322 | if (!n) 323 | return 1; 324 | 325 | const isPowerOf2 = !((n - 1) & n); 326 | return 1 << (bsr(n) + !isPowerOf2); 327 | } 328 | 329 | 330 | //============================================================================== 331 | // API Implementation 332 | //------------------------------------------------------------------------------ 333 | 334 | /** Allocate associative array data. 335 | * Called for `new SomeAA` expression. 336 | * Params: 337 | * ti = TypeInfo for the associative array 338 | * Returns: 339 | * A new associative array. 340 | */ 341 | extern (C) Impl* _aaNew(const TypeInfo_AssociativeArray ti) 342 | { 343 | return new Impl(ti); 344 | } 345 | 346 | /// Determine number of entries in associative array. 347 | extern (C) size_t _aaLen(scope const AA aa) pure nothrow @nogc 348 | { 349 | return aa ? aa.length : 0; 350 | } 351 | 352 | /****************************** 353 | * Lookup *pkey in aa. 354 | * Called only from implementation of (aa[key]) expressions when value is mutable. 355 | * Params: 356 | * paa = associative array opaque pointer 357 | * ti = TypeInfo for the associative array 358 | * valsz = ignored 359 | * pkey = pointer to the key value 360 | * Returns: 361 | * if key was in the aa, a mutable pointer to the existing value. 362 | * If key was not in the aa, a mutable pointer to newly inserted value which 363 | * is set to all zeros 364 | */ 365 | extern (C) void* _aaGetY(scope ubyte** paa, const TypeInfo_AssociativeArray ti, 366 | const size_t valsz, scope const void* pkey) 367 | { 368 | bool found; 369 | return _aaGetX(paa, ti, valsz, pkey, found); 370 | } 371 | 372 | /****************************** 373 | * Lookup *pkey in aa. 374 | * Called only from implementation of require 375 | * Params: 376 | * paa = associative array opaque pointer 377 | * ti = TypeInfo for the associative array 378 | * valsz = ignored 379 | * pkey = pointer to the key value 380 | * found = true if the value was found 381 | * Returns: 382 | * if key was in the aa, a mutable pointer to the existing value. 383 | * If key was not in the aa, a mutable pointer to newly inserted value which 384 | * is set to all zeros 385 | */ 386 | extern (C) void* _aaGetX(scope ubyte** paa, const TypeInfo_AssociativeArray ti, 387 | const size_t valsz, scope const void* pkey, out bool found) 388 | { 389 | 390 | // lazily alloc implementation 391 | AA aa = *cast(AA*)paa; 392 | if (aa is null) 393 | { 394 | aa = new Impl(ti); 395 | *cast(AA*)paa = aa; 396 | } 397 | 398 | // get hash and bucket for key 399 | immutable hash = calcHash(pkey, ti.key); 400 | 401 | // found a value => return it 402 | if (auto p = aa.findSlotLookup(hash, pkey, ti.key)) 403 | { 404 | found = true; 405 | return p.entry + aa.valoff; 406 | } 407 | 408 | auto p = aa.findSlotInsert(hash); 409 | if (p.deleted) 410 | --aa.deleted; 411 | // check load factor and possibly grow 412 | else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) 413 | { 414 | aa.grow(ti.key); 415 | p = aa.findSlotInsert(hash); 416 | assert(p.empty); 417 | } 418 | 419 | // update search cache and allocate entry 420 | aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); 421 | p.hash = hash; 422 | p.entry = allocEntry(aa, pkey); 423 | // postblit for key 424 | if (aa.flags & Impl.Flags.keyHasPostblit) 425 | { 426 | import core.arsd.objectutils; 427 | __doPostblit(p.entry, aa.keysz, unqualify(ti.key)); 428 | } 429 | // return pointer to value 430 | return p.entry + aa.valoff; 431 | } 432 | 433 | /****************************** 434 | * Lookup *pkey in aa. 435 | * Called only from implementation of (aa[key]) expressions when value is not mutable. 436 | * Params: 437 | * aa = associative array opaque pointer 438 | * keyti = TypeInfo for the key 439 | * valsz = ignored 440 | * pkey = pointer to the key value 441 | * Returns: 442 | * pointer to value if present, null otherwise 443 | */ 444 | extern (C) inout(void)* _aaGetRvalueX(inout ubyte** aa, scope const TypeInfo keyti, const size_t valsz, 445 | scope const void* pkey) 446 | { 447 | return _aaInX(aa, keyti, pkey); 448 | } 449 | 450 | /****************************** 451 | * Lookup *pkey in aa. 452 | * Called only from implementation of (key in aa) expressions. 453 | * Params: 454 | * aa = associative array opaque pointer 455 | * keyti = TypeInfo for the key 456 | * pkey = pointer to the key value 457 | * Returns: 458 | * pointer to value if present, null otherwise 459 | */ 460 | extern (C) inout(void)* _aaInX(inout ubyte** _aa, scope const TypeInfo keyti, scope const void* pkey) 461 | { 462 | import std.stdio; 463 | AA aa = cast(AA)_aa; 464 | if (aa.empty) 465 | return null; 466 | 467 | immutable hash = calcHash(pkey, keyti); 468 | if (auto p = aa.findSlotLookup(hash, pkey, keyti)) 469 | return cast(inout)(p.entry + aa.valoff); 470 | return null; 471 | } 472 | 473 | /// Delete entry scope const AA, return true if it was present 474 | extern (C) bool _aaDelX(ubyte* _aa, scope const TypeInfo keyti, scope const void* pkey) 475 | { 476 | AA aa = cast(AA)_aa; 477 | if (aa.empty) 478 | return false; 479 | immutable hash = calcHash(pkey, keyti); 480 | if (auto p = aa.findSlotLookup(hash, pkey, keyti)) 481 | { 482 | // clear entry 483 | p.hash = HASH_DELETED; 484 | p.entry = null; 485 | 486 | ++aa.deleted; 487 | // `shrink` reallocates, and allocating from a finalizer leads to 488 | // InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442 489 | if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM) // && !GC.inFinalizer() no GC so never in finalizer 490 | aa.shrink(keyti); 491 | 492 | return true; 493 | } 494 | return false; 495 | } 496 | 497 | /// Remove all elements from AA. 498 | extern (C) void _aaClear(ubyte* _aa) pure nothrow 499 | { 500 | AA aa = cast(AA)_aa; 501 | if (!aa.empty) 502 | { 503 | aa.clear(); 504 | } 505 | } 506 | 507 | /// Rehash AA 508 | extern (C) void* _aaRehash(ubyte** _paa, scope const TypeInfo keyti) 509 | { 510 | AA* paa = cast(AA*)_paa; 511 | AA aa = *paa; 512 | if (!aa.empty) 513 | aa.resize(nextpow2(INIT_DEN * aa.length / INIT_NUM)); 514 | return aa; 515 | } 516 | 517 | /// Return a GC allocated array of all values 518 | extern (C) inout(void[]) _aaValues(inout ubyte* _aa, const size_t keysz, const size_t valsz, 519 | const TypeInfo tiValueArray) 520 | { 521 | AA aa = cast(AA)_aa; 522 | if (aa.empty) 523 | return null; 524 | 525 | auto res = _d_newarrayU(tiValueArray, aa.length).ptr; 526 | auto pval = res; 527 | 528 | immutable off = aa.valoff; 529 | foreach (b; aa.buckets[aa.firstUsed .. $]) 530 | { 531 | if (!b.filled) 532 | continue; 533 | pval[0 .. valsz] = b.entry[off .. valsz + off]; 534 | pval += valsz; 535 | } 536 | // postblit is done in object.values 537 | return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements 538 | } 539 | 540 | /// Return a GC allocated array of all keys 541 | extern (C) inout(void[]) _aaKeys(inout ubyte* _aa, const size_t keysz, const TypeInfo tiKeyArray) 542 | { 543 | AA aa = cast(AA)_aa; 544 | if (aa.empty) 545 | return null; 546 | 547 | auto res = _d_newarrayU(tiKeyArray, aa.length).ptr; 548 | auto pkey = res; 549 | 550 | foreach (b; aa.buckets[aa.firstUsed .. $]) 551 | { 552 | if (!b.filled) 553 | continue; 554 | pkey[0 .. keysz] = b.entry[0 .. keysz]; 555 | pkey += keysz; 556 | } 557 | // postblit is done in object.keys 558 | return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements 559 | } 560 | 561 | // opApply callbacks are extern(D) 562 | extern (D) alias dg_t = int delegate(void*); 563 | extern (D) alias dg2_t = int delegate(void*, void*); 564 | 565 | /// foreach opApply over all values 566 | extern (C) int _aaApply(ubyte* _aa, const size_t keysz, dg_t dg) 567 | { 568 | AA aa = cast(AA)_aa; 569 | if (aa.empty) 570 | return 0; 571 | 572 | immutable off = aa.valoff; 573 | foreach (b; aa.buckets) 574 | { 575 | if (!b.filled) 576 | continue; 577 | if (auto res = dg(b.entry + off)) 578 | return res; 579 | } 580 | return 0; 581 | } 582 | 583 | /// foreach opApply over all key/value pairs 584 | extern (C) int _aaApply2(ubyte* _aa, const size_t keysz, dg2_t dg) 585 | { 586 | AA aa = cast(AA)_aa; 587 | if (aa.empty) 588 | return 0; 589 | 590 | immutable off = aa.valoff; 591 | foreach (b; aa.buckets) 592 | { 593 | if (!b.filled) 594 | continue; 595 | if (auto res = dg(b.entry, b.entry + off)) 596 | return res; 597 | } 598 | return 0; 599 | } 600 | 601 | /** Construct an associative array of type ti from corresponding keys and values. 602 | * Called for an AA literal `[k1:v1, k2:v2]`. 603 | * Params: 604 | * ti = TypeInfo for the associative array 605 | * keys = array of keys 606 | * vals = array of values 607 | * Returns: 608 | * A new associative array opaque pointer, or null if `keys` is empty. 609 | */ 610 | extern (C) ubyte* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, 611 | void[] vals) 612 | { 613 | assert(keys.length == vals.length); 614 | 615 | immutable keysz = ti.key.size; 616 | immutable valsz = ti.value.size; 617 | immutable length = keys.length; 618 | 619 | if (!length) 620 | return null; 621 | 622 | auto aa = new Impl(ti, nextpow2(INIT_DEN * length / INIT_NUM)); 623 | 624 | void* pkey = keys.ptr; 625 | void* pval = vals.ptr; 626 | immutable off = aa.valoff; 627 | uint actualLength = 0; 628 | foreach (_; 0 .. length) 629 | { 630 | immutable hash = calcHash(pkey, ti.key); 631 | auto p = aa.findSlotLookup(hash, pkey, ti.key); 632 | if (p is null) 633 | { 634 | p = aa.findSlotInsert(hash); 635 | p.hash = hash; 636 | p.entry = allocEntry(aa, pkey); // move key, no postblit 637 | aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); 638 | actualLength++; 639 | } 640 | else if (aa.entryTI && hasDtor(ti.value)) 641 | { 642 | // destroy existing value before overwriting it 643 | ti.value.destroy(p.entry + off); 644 | } 645 | // set hash and blit value 646 | auto pdst = p.entry + off; 647 | pdst[0 .. valsz] = pval[0 .. valsz]; // move value, no postblit 648 | 649 | pkey += keysz; 650 | pval += valsz; 651 | } 652 | aa.used = actualLength; 653 | return cast(ubyte*)aa; 654 | } 655 | 656 | /// compares 2 AAs for equality 657 | extern (C) int _aaEqual(scope const TypeInfo tiRaw, scope const ubyte* _aa1, scope const ubyte* _aa2) 658 | { 659 | AA aa1 = cast(AA)_aa1; 660 | AA aa2 = cast(AA)_aa2; 661 | if (aa1 is aa2) 662 | return true; 663 | 664 | immutable len = _aaLen(aa1); 665 | if (len != _aaLen(aa2)) 666 | return false; 667 | 668 | if (!len) // both empty 669 | return true; 670 | 671 | import core.arsd.objectutils; 672 | 673 | auto uti = unqualify(tiRaw); //unqualify 674 | auto ti = *cast(TypeInfo_AssociativeArray*)&uti; 675 | // compare the entries 676 | immutable off = aa1.valoff; 677 | foreach (b1; aa1.buckets) 678 | { 679 | if (!b1.filled) 680 | continue; 681 | auto pb2 = aa2.findSlotLookup(b1.hash, b1.entry, ti.key); 682 | if (pb2 is null || !ti.value.equals(b1.entry + off, pb2.entry + off)) 683 | return false; 684 | } 685 | return true; 686 | } 687 | 688 | /// compute a hash 689 | extern (C) size_t _aaGetHash(scope const ubyte** _paa, scope const TypeInfo tiRaw) nothrow 690 | { 691 | AA* paa = cast(AA*)_paa; 692 | const AA aa = *paa; 693 | 694 | if (aa.empty) 695 | return 0; 696 | 697 | 698 | import core.arsd.objectutils; 699 | auto uti = unqualify(tiRaw); 700 | auto ti = *cast(TypeInfo_AssociativeArray*)&uti; 701 | immutable off = aa.valoff; 702 | auto keyHash = &ti.key.getHash; 703 | auto valHash = &ti.value.getHash; 704 | 705 | size_t h; 706 | foreach (b; aa.buckets) 707 | { 708 | // use addition here, so that hash is independent of element order 709 | if (b.filled) 710 | h += hashOf(valHash(b.entry + off), keyHash(b.entry)); 711 | } 712 | 713 | return h; 714 | } 715 | 716 | /** 717 | * _aaRange implements a ForwardRange 718 | */ 719 | struct Range 720 | { 721 | ubyte* impl; 722 | size_t idx; 723 | alias impl this; 724 | } 725 | 726 | extern (C) pure nothrow @nogc @trusted 727 | { 728 | Range _aaRange(return scope ubyte* _aa) 729 | { 730 | AA aa = cast(AA)_aa; 731 | if (!aa) 732 | return Range(); 733 | 734 | foreach (i; aa.firstUsed .. aa.dim) 735 | { 736 | if (aa.buckets[i].filled) 737 | return Range(cast(ubyte*)aa, i); 738 | } 739 | return Range(cast(ubyte*)aa, aa.dim); 740 | } 741 | 742 | bool _aaRangeEmpty(Range r) 743 | { 744 | return r.impl is null || r.idx >= (cast(Impl*)r.impl).dim; 745 | } 746 | 747 | void* _aaRangeFrontKey(Range r) 748 | { 749 | assert(!_aaRangeEmpty(r)); 750 | if (r.idx >= (cast(Impl*)r.impl).dim) 751 | return null; 752 | return (cast(Impl*)r.impl).buckets[r.idx].entry; 753 | } 754 | 755 | void* _aaRangeFrontValue(Range r) 756 | { 757 | Impl* ri = cast(Impl*)r.impl; 758 | assert(!_aaRangeEmpty(r)); 759 | if (r.idx >= ri.dim) 760 | return null; 761 | 762 | auto entry = ri.buckets[r.idx].entry; 763 | return entry is null ? 764 | null : 765 | (() @trusted { return entry + ri.valoff; } ()); 766 | } 767 | 768 | void _aaRangePopFront(ref Range r) 769 | { 770 | Impl* ri = (cast(Impl*)r.impl); 771 | if (r.idx >= ri.dim) return; 772 | for (++r.idx; r.idx < ri.dim; ++r.idx) 773 | { 774 | if (ri.buckets[r.idx].filled) 775 | break; 776 | } 777 | } 778 | } 779 | -------------------------------------------------------------------------------- /arsd-webassembly/core/arsd/memory_allocation.d: -------------------------------------------------------------------------------- 1 | module core.arsd.memory_allocation; 2 | 3 | 4 | 5 | private __gshared ubyte* nextFree; 6 | private __gshared size_t memorySize; // in units of 64 KB pages 7 | 8 | // ldc defines this, used to find where wasm memory begins 9 | private extern extern(C) ubyte __heap_base; 10 | // ---unused--- -- stack grows down -- -- heap here -- 11 | // this is less than __heap_base. memory map 0 ... __data_end ... __heap_base ... end of memory 12 | private extern extern(C) ubyte __data_end; 13 | 14 | // llvm intrinsics { 15 | /+ 16 | mem must be 0 (it is index of memory thing) 17 | delta is in 64 KB pages 18 | return OLD size in 64 KB pages, or size_t.max if it failed. 19 | +/ 20 | pragma(LDC_intrinsic, "llvm.wasm.memory.grow.i32") 21 | private int llvm_wasm_memory_grow(int mem, int delta); 22 | 23 | 24 | // in 64 KB pages 25 | pragma(LDC_intrinsic, "llvm.wasm.memory.size.i32") 26 | private int llvm_wasm_memory_size(int mem); 27 | // } 28 | // debug 29 | void printBlockDebugInfo(AllocatedBlock* block) { 30 | import std.stdio; 31 | writeln(block.blockSize, " ", block.flags, " ", block.checkChecksum() ? "OK" : "X", " "); 32 | if(block.checkChecksum()) 33 | writeln(cast(size_t)((cast(ubyte*) (block + 2)) + block.blockSize), " ", block.file, " : ", block.line); 34 | } 35 | 36 | 37 | // debug 38 | export extern(C) void printBlockDebugInfo(void* ptr) { 39 | if(ptr is null) { 40 | foreach(block; AllocatedBlock) { 41 | printBlockDebugInfo(block); 42 | } 43 | return; 44 | } 45 | 46 | // otherwise assume it is a pointer returned from malloc 47 | 48 | auto block = (cast(AllocatedBlock*) ptr) - 1; 49 | if(ptr is null) 50 | block = cast(AllocatedBlock*) &__heap_base; 51 | 52 | printBlockDebugInfo(block); 53 | } 54 | 55 | 56 | export extern(C) ubyte* bridge_malloc(size_t sz) { 57 | return malloc(sz).ptr; 58 | } 59 | 60 | 61 | 62 | align(16) 63 | struct AllocatedBlock { 64 | enum Magic = 0x731a_9bec; 65 | enum Flags { 66 | inUse = 1, 67 | unique = 2, 68 | } 69 | 70 | size_t blockSize; 71 | size_t flags; 72 | size_t magic; 73 | size_t checksum; 74 | 75 | size_t used; // the amount actually requested out of the block; used for assumeSafeAppend 76 | 77 | /* debug */ 78 | string file; 79 | size_t line; 80 | 81 | // note this struct MUST align each alloc on an 8 byte boundary or JS is gonna throw bullshit 82 | 83 | void populateChecksum() { 84 | checksum = blockSize ^ magic; 85 | } 86 | 87 | bool checkChecksum() const @nogc { 88 | return magic == Magic && checksum == (blockSize ^ magic); 89 | } 90 | 91 | ubyte[] dataSlice() return { 92 | return ((cast(ubyte*) &this) + typeof(this).sizeof)[0 .. blockSize]; 93 | } 94 | 95 | static int opApply(scope int delegate(AllocatedBlock*) dg) { 96 | if(nextFree is null) 97 | return 0; 98 | ubyte* next = &__heap_base; 99 | AllocatedBlock* block = cast(AllocatedBlock*) next; 100 | while(block.checkChecksum()) { 101 | if(auto result = dg(block)) 102 | return result; 103 | next += AllocatedBlock.sizeof; 104 | next += block.blockSize; 105 | block = cast(AllocatedBlock*) next; 106 | } 107 | 108 | return 0; 109 | } 110 | } 111 | 112 | static assert(AllocatedBlock.sizeof % 16 == 0); 113 | 114 | 115 | 116 | private bool growMemoryIfNeeded(size_t sz) @trusted { 117 | if(cast(size_t) nextFree + AllocatedBlock.sizeof + sz >= memorySize * 64*1024) { 118 | if(llvm_wasm_memory_grow(0, 4) == size_t.max) 119 | assert(0, "Out of memory"); // out of memory 120 | 121 | memorySize = llvm_wasm_memory_size(0); 122 | 123 | return true; 124 | } 125 | 126 | return false; 127 | } 128 | 129 | void free(ubyte* ptr) @nogc @trusted { 130 | auto block = (cast(AllocatedBlock*) ptr) - 1; 131 | if(!block.checkChecksum()) 132 | assert(false, "Could not check block on free"); 133 | 134 | block.used = 0; 135 | block.flags = 0; 136 | 137 | // last one 138 | if(ptr + block.blockSize == nextFree) { 139 | nextFree = cast(ubyte*) block; 140 | assert(cast(size_t)nextFree % 16 == 0); 141 | } 142 | } 143 | 144 | 145 | ubyte[] malloc(size_t sz, string file = __FILE__, size_t line = __LINE__) @trusted { 146 | // lol bumping that pointer 147 | if(nextFree is null) { 148 | nextFree = &__heap_base; // seems to be 75312 149 | assert(cast(size_t)nextFree % 16 == 0); 150 | memorySize = llvm_wasm_memory_size(0); 151 | } 152 | 153 | while(growMemoryIfNeeded(sz)) {} 154 | 155 | auto base = cast(AllocatedBlock*) nextFree; 156 | 157 | auto blockSize = sz; 158 | if(auto val = blockSize % 16) 159 | blockSize += 16 - val; // does NOT include this metadata section! 160 | 161 | // debug list allocations 162 | //import std.stdio; writeln(file, ":", line, " / ", sz, " +", blockSize); 163 | 164 | base.blockSize = blockSize; 165 | base.flags = AllocatedBlock.Flags.inUse; 166 | // these are just to make it more reliable to detect this header by backtracking through the pointer from a random array. 167 | // otherwise it'd prolly follow the linked list from the beginning every time or make a free list or something. idk tbh. 168 | base.magic = AllocatedBlock.Magic; 169 | base.populateChecksum(); 170 | 171 | base.used = sz; 172 | 173 | // debug 174 | base.file = file; 175 | base.line = line; 176 | 177 | nextFree += AllocatedBlock.sizeof; 178 | 179 | auto ret = nextFree; 180 | 181 | nextFree += blockSize; 182 | 183 | //writeln(cast(size_t) nextFree); 184 | //import std.stdio; writeln(cast(size_t) ret, " of ", sz, " rounded to ", blockSize); 185 | //writeln(file, ":", line); 186 | assert(cast(size_t) ret % 8 == 0); 187 | 188 | return ret[0 .. sz]; 189 | } 190 | 191 | 192 | ubyte[] calloc(size_t count, size_t size, string file = __FILE__, size_t line = __LINE__) @trusted 193 | { 194 | auto ret = malloc(count*size,file,line); 195 | ret[0..$] = 0; 196 | return ret; 197 | } 198 | 199 | 200 | ubyte[] realloc(ubyte* ptr, size_t newSize, string file = __FILE__, size_t line = __LINE__) @trusted { 201 | if(ptr is null) 202 | return malloc(newSize, file, line); 203 | 204 | auto block = (cast(AllocatedBlock*) ptr) - 1; 205 | if(!block.checkChecksum()) 206 | assert(false, "Could not check block while realloc"); 207 | 208 | // block.populateChecksum(); 209 | if(newSize <= block.blockSize) { 210 | block.used = newSize; 211 | return ptr[0 .. newSize]; 212 | } else { 213 | // FIXME: see if we can extend teh block into following free space before resorting to malloc 214 | 215 | if(ptr + block.blockSize == nextFree) { 216 | while(growMemoryIfNeeded(newSize)) {} 217 | 218 | size_t blockSize = newSize; 219 | if(const over = blockSize % 16) 220 | blockSize+= 16 - over; 221 | 222 | block.blockSize = blockSize; 223 | block.used = newSize; 224 | block.populateChecksum(); 225 | nextFree = ptr + block.blockSize; 226 | assert(cast(size_t)nextFree % 16 == 0); 227 | return ptr[0 .. newSize]; 228 | } 229 | 230 | auto newThing = malloc(newSize); 231 | newThing[0 .. block.used] = ptr[0 .. block.used]; 232 | 233 | if(block.flags & AllocatedBlock.Flags.unique) { 234 | // if we do malloc, this means we are allowed to free the existing block 235 | free(ptr); 236 | } 237 | 238 | assert(cast(size_t) newThing.ptr % 16 == 0); 239 | 240 | return newThing; 241 | } 242 | } 243 | 244 | /** 245 | * If the ptr isn't owned by the runtime, it will completely malloc the data (instead of realloc) 246 | * and copy its old content. 247 | */ 248 | ubyte[] realloc(ubyte[] ptr, size_t newSize, string file = __FILE__, size_t line = __LINE__) @trusted 249 | { 250 | if(ptr is null) 251 | return malloc(newSize, file, line); 252 | auto block = (cast(AllocatedBlock*) ptr) - 1; 253 | if(!block.checkChecksum()) 254 | { 255 | auto ret = malloc(newSize, file, line); 256 | ret[0..ptr.length] = ptr[]; //Don't clear ptr memory as it can't be clear. 257 | return ret; 258 | } 259 | else return realloc(ptr.ptr, newSize, file, line); 260 | } -------------------------------------------------------------------------------- /arsd-webassembly/core/arsd/objectutils.d: -------------------------------------------------------------------------------- 1 | module core.arsd.objectutils; 2 | 3 | ///Provides only __doPostblit and hasPostblit for making the code simpler. 4 | size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc 5 | { 6 | if (ti && typeid(ti) is typeid(TypeInfo_Struct)) // avoid a complete dynamic type cast 7 | { 8 | auto sti = cast(TypeInfo_Struct)cast(void*)ti; 9 | if (sti.xdtor) 10 | return size_t.sizeof; 11 | } 12 | return 0; 13 | } 14 | // strip const/immutable/shared/inout from type info 15 | inout(TypeInfo) unqualify(return scope inout(TypeInfo) cti) pure nothrow @nogc 16 | { 17 | TypeInfo ti = cast() cti; 18 | while (ti) 19 | { 20 | // avoid dynamic type casts 21 | auto tti = typeid(ti); 22 | if (tti is typeid(TypeInfo_Const)) 23 | ti = (cast(TypeInfo_Const)cast(void*)ti).base; 24 | else if (tti is typeid(TypeInfo_Invariant)) 25 | ti = (cast(TypeInfo_Invariant)cast(void*)ti).base; 26 | else if (tti is typeid(TypeInfo_Shared)) 27 | ti = (cast(TypeInfo_Shared)cast(void*)ti).base; 28 | else if (tti is typeid(TypeInfo_Inout)) 29 | ti = (cast(TypeInfo_Inout)cast(void*)ti).base; 30 | else 31 | break; 32 | } 33 | return ti; 34 | } 35 | 36 | bool hasPostblit(in TypeInfo ti) nothrow pure 37 | { 38 | return (&ti.postblit).funcptr !is &TypeInfo.postblit; 39 | } 40 | 41 | void __doPostblit(void *ptr, size_t len, const TypeInfo ti) 42 | { 43 | if (!hasPostblit(ti)) 44 | return; 45 | 46 | if (auto tis = cast(TypeInfo_Struct)ti) 47 | { 48 | // this is a struct, check the xpostblit member 49 | auto pblit = tis.xpostblit; 50 | if (!pblit) 51 | // postblit not specified, no point in looping. 52 | return; 53 | 54 | // optimized for struct, call xpostblit directly for each element 55 | immutable size = ti.size; 56 | const eptr = ptr + len; 57 | for (;ptr < eptr;ptr += size) 58 | pblit(ptr); 59 | } 60 | else 61 | { 62 | // generic case, call the typeinfo's postblit function 63 | immutable size = ti.size; 64 | const eptr = ptr + len; 65 | for (;ptr < eptr;ptr += size) 66 | ti.postblit(ptr); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /arsd-webassembly/core/arsd/utf_decoding.d: -------------------------------------------------------------------------------- 1 | module core.arsd.utf_decoding; 2 | 3 | 4 | import core.internal.utf : decode, toUTF8; 5 | 6 | /**********************************************/ 7 | /* 1 argument versions */ 8 | 9 | /** 10 | Delegate type corresponding to transformed loop body 11 | 12 | The parameter is a pointer to the current `char`, `wchar` or `dchar` 13 | 14 | Returns: non-zero when a `break` statement is hit 15 | */ 16 | extern (D) alias dg_t = int delegate(void* c); 17 | 18 | // Note: dg is extern(D), but _aApplycd() is extern(C) 19 | 20 | /** 21 | Loop over a string while changing the UTF encoding 22 | 23 | There are 6 combinations of conversions between `char`, `wchar`, and `dchar`, 24 | and 2 of each of those. 25 | 26 | The naming convention is as follows: 27 | 28 | _aApply{c,d,w}{c,d,w}{1,2} 29 | 30 | The first letter corresponds to the input string encoding, and the second letter corresponds to the target character type. 31 | 32 | - c = `char` 33 | - w = `wchar` 34 | - d = `dchar` 35 | 36 | The `1` variant only produces the character, the `2` variant also produces a loop index. 37 | 38 | Examples: 39 | --- 40 | void main() 41 | { 42 | string str; 43 | wtring wstr; 44 | dstring dstr; 45 | 46 | foreach (dchar c; str) {} 47 | // _aApplycd1 48 | 49 | foreach (wchar c; dstr) {} 50 | // _aApplydw1 51 | 52 | foreach (i, wchar c; str) {} 53 | // _aApplycw2 54 | 55 | foreach (wchar w; wstr) {} 56 | // no conversion 57 | } 58 | --- 59 | 60 | Params: 61 | aa = input string 62 | dg = foreach body transformed into a delegate, similar to `opApply` 63 | 64 | Returns: 65 | non-zero when the loop was exited through a `break` 66 | */ 67 | extern (C) int _aApplycd1(in char[] aa, dg_t dg) 68 | { 69 | int result; 70 | size_t len = aa.length; 71 | 72 | debug(apply) printf("_aApplycd1(), len = %d\n", len); 73 | for (size_t i = 0; i < len; ) 74 | { 75 | dchar d = aa[i]; 76 | if (d & 0x80) 77 | d = decode(aa, i); 78 | else 79 | ++i; 80 | result = dg(cast(void *)&d); 81 | if (result) 82 | break; 83 | } 84 | return result; 85 | } 86 | 87 | 88 | 89 | /// ditto 90 | extern (C) int _aApplywd1(in wchar[] aa, dg_t dg) 91 | { 92 | int result; 93 | size_t len = aa.length; 94 | 95 | debug(apply) printf("_aApplywd1(), len = %d\n", len); 96 | for (size_t i = 0; i < len; ) 97 | { 98 | dchar d = aa[i]; 99 | if (d >= 0xD800) 100 | d = decode(aa, i); 101 | else 102 | ++i; 103 | result = dg(cast(void *)&d); 104 | if (result) 105 | break; 106 | } 107 | return result; 108 | } 109 | 110 | 111 | /// ditto 112 | extern (C) int _aApplycw1(in char[] aa, dg_t dg) 113 | { 114 | int result; 115 | size_t len = aa.length; 116 | 117 | debug(apply) printf("_aApplycw1(), len = %d\n", len); 118 | for (size_t i = 0; i < len; ) 119 | { 120 | wchar w = aa[i]; 121 | if (w & 0x80) 122 | { 123 | dchar d = decode(aa, i); 124 | if (d <= 0xFFFF) 125 | w = cast(wchar) d; 126 | else 127 | { 128 | w = cast(wchar)((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); 129 | result = dg(cast(void *)&w); 130 | if (result) 131 | break; 132 | w = cast(wchar)(((d - 0x10000) & 0x3FF) + 0xDC00); 133 | } 134 | } 135 | else 136 | ++i; 137 | result = dg(cast(void *)&w); 138 | if (result) 139 | break; 140 | } 141 | return result; 142 | } 143 | 144 | 145 | /// ditto 146 | extern (C) int _aApplywc1(in wchar[] aa, dg_t dg) 147 | { 148 | int result; 149 | size_t len = aa.length; 150 | 151 | debug(apply) printf("_aApplywc1(), len = %d\n", len); 152 | for (size_t i = 0; i < len; ) 153 | { 154 | wchar w = aa[i]; 155 | if (w & ~0x7F) 156 | { 157 | char[4] buf = void; 158 | 159 | dchar d = decode(aa, i); 160 | auto b = toUTF8(buf, d); 161 | foreach (char c2; b) 162 | { 163 | result = dg(cast(void *)&c2); 164 | if (result) 165 | return result; 166 | } 167 | } 168 | else 169 | { 170 | char c = cast(char)w; 171 | ++i; 172 | result = dg(cast(void *)&c); 173 | if (result) 174 | break; 175 | } 176 | } 177 | return result; 178 | } 179 | 180 | 181 | /// ditto 182 | extern (C) int _aApplydc1(in dchar[] aa, dg_t dg) 183 | { 184 | int result; 185 | 186 | debug(apply) printf("_aApplydc1(), len = %d\n", aa.length); 187 | foreach (dchar d; aa) 188 | { 189 | if (d & ~0x7F) 190 | { 191 | char[4] buf = void; 192 | 193 | auto b = toUTF8(buf, d); 194 | foreach (char c2; b) 195 | { 196 | result = dg(cast(void *)&c2); 197 | if (result) 198 | return result; 199 | } 200 | } 201 | else 202 | { 203 | char c = cast(char)d; 204 | result = dg(cast(void *)&c); 205 | if (result) 206 | break; 207 | } 208 | } 209 | return result; 210 | } 211 | 212 | 213 | /// ditto 214 | extern (C) int _aApplydw1(in dchar[] aa, dg_t dg) 215 | { 216 | int result; 217 | 218 | debug(apply) printf("_aApplydw1(), len = %d\n", aa.length); 219 | foreach (dchar d; aa) 220 | { 221 | wchar w; 222 | 223 | if (d <= 0xFFFF) 224 | w = cast(wchar) d; 225 | else 226 | { 227 | w = cast(wchar)((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); 228 | result = dg(cast(void *)&w); 229 | if (result) 230 | break; 231 | w = cast(wchar)(((d - 0x10000) & 0x3FF) + 0xDC00); 232 | } 233 | result = dg(cast(void *)&w); 234 | if (result) 235 | break; 236 | } 237 | return result; 238 | } 239 | 240 | 241 | /****************************************************************************/ 242 | /* 2 argument versions */ 243 | 244 | /** 245 | Delegate type corresponding to transformed loop body 246 | 247 | Parameters are pointers to a `size_t` loop index, and the current `char`, `wchar` or `dchar`. 248 | 249 | Returns: non-zero when a `break` statement is hit 250 | */ 251 | extern (D) alias dg2_t = int delegate(void* i, void* c); 252 | 253 | // Note: dg is extern(D), but _aApplycd2() is extern(C) 254 | 255 | /** 256 | Variants of _aApplyXXX that include a loop index. 257 | */ 258 | extern (C) int _aApplycd2(in char[] aa, dg2_t dg) 259 | { 260 | int result; 261 | size_t len = aa.length; 262 | 263 | debug(apply) printf("_aApplycd2(), len = %d\n", len); 264 | size_t n; 265 | for (size_t i = 0; i < len; i += n) 266 | { 267 | dchar d = aa[i]; 268 | if (d & 0x80) 269 | { 270 | n = i; 271 | d = decode(aa, n); 272 | n -= i; 273 | } 274 | else 275 | n = 1; 276 | result = dg(&i, cast(void *)&d); 277 | if (result) 278 | break; 279 | } 280 | return result; 281 | } 282 | 283 | /// ditto 284 | extern (C) int _aApplywd2(in wchar[] aa, dg2_t dg) 285 | { 286 | int result; 287 | size_t len = aa.length; 288 | 289 | debug(apply) printf("_aApplywd2(), len = %d\n", len); 290 | size_t n; 291 | for (size_t i = 0; i < len; i += n) 292 | { 293 | dchar d = aa[i]; 294 | if (d & ~0x7F) 295 | { 296 | n = i; 297 | d = decode(aa, n); 298 | n -= i; 299 | } 300 | else 301 | n = 1; 302 | result = dg(&i, cast(void *)&d); 303 | if (result) 304 | break; 305 | } 306 | return result; 307 | } 308 | 309 | /// ditto 310 | extern (C) int _aApplycw2(in char[] aa, dg2_t dg) 311 | { 312 | int result; 313 | size_t len = aa.length; 314 | 315 | debug(apply) printf("_aApplycw2(), len = %d\n", len); 316 | size_t n; 317 | for (size_t i = 0; i < len; i += n) 318 | { 319 | wchar w = aa[i]; 320 | if (w & 0x80) 321 | { 322 | n = i; 323 | dchar d = decode(aa, n); 324 | n -= i; 325 | if (d <= 0xFFFF) 326 | w = cast(wchar) d; 327 | else 328 | { 329 | w = cast(wchar) ((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); 330 | result = dg(&i, cast(void *)&w); 331 | if (result) 332 | break; 333 | w = cast(wchar) (((d - 0x10000) & 0x3FF) + 0xDC00); 334 | } 335 | } 336 | else 337 | n = 1; 338 | result = dg(&i, cast(void *)&w); 339 | if (result) 340 | break; 341 | } 342 | return result; 343 | } 344 | 345 | 346 | /// ditto 347 | extern (C) int _aApplywc2(in wchar[] aa, dg2_t dg) 348 | { 349 | int result; 350 | size_t len = aa.length; 351 | 352 | debug(apply) printf("_aApplywc2(), len = %d\n", len); 353 | size_t n; 354 | for (size_t i = 0; i < len; i += n) 355 | { 356 | wchar w = aa[i]; 357 | if (w & ~0x7F) 358 | { 359 | char[4] buf = void; 360 | 361 | n = i; 362 | dchar d = decode(aa, n); 363 | n -= i; 364 | auto b = toUTF8(buf, d); 365 | foreach (char c2; b) 366 | { 367 | result = dg(&i, cast(void *)&c2); 368 | if (result) 369 | return result; 370 | } 371 | } 372 | else 373 | { 374 | char c = cast(char)w; 375 | n = 1; 376 | result = dg(&i, cast(void *)&c); 377 | if (result) 378 | break; 379 | } 380 | } 381 | return result; 382 | } 383 | 384 | 385 | /// ditto 386 | extern (C) int _aApplydc2(in dchar[] aa, dg2_t dg) 387 | { 388 | int result; 389 | size_t len = aa.length; 390 | 391 | debug(apply) printf("_aApplydc2(), len = %d\n", len); 392 | for (size_t i = 0; i < len; i++) 393 | { 394 | dchar d = aa[i]; 395 | if (d & ~0x7F) 396 | { 397 | char[4] buf = void; 398 | 399 | auto b = toUTF8(buf, d); 400 | foreach (char c2; b) 401 | { 402 | result = dg(&i, cast(void *)&c2); 403 | if (result) 404 | return result; 405 | } 406 | } 407 | else 408 | { 409 | char c = cast(char)d; 410 | result = dg(&i, cast(void *)&c); 411 | if (result) 412 | break; 413 | } 414 | } 415 | return result; 416 | } 417 | 418 | 419 | /// ditto 420 | extern (C) int _aApplydw2(in dchar[] aa, dg2_t dg) 421 | { int result; 422 | 423 | debug(apply) printf("_aApplydw2(), len = %d\n", aa.length); 424 | foreach (size_t i, dchar d; aa) 425 | { 426 | wchar w; 427 | auto j = i; 428 | 429 | if (d <= 0xFFFF) 430 | w = cast(wchar) d; 431 | else 432 | { 433 | w = cast(wchar) ((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); 434 | result = dg(&j, cast(void *)&w); 435 | if (result) 436 | break; 437 | w = cast(wchar) (((d - 0x10000) & 0x3FF) + 0xDC00); 438 | } 439 | result = dg(&j, cast(void *)&w); 440 | if (result) 441 | break; 442 | } 443 | return result; 444 | } 445 | -------------------------------------------------------------------------------- /arsd-webassembly/core/internal/utf.d: -------------------------------------------------------------------------------- 1 | /******************************************** 2 | * Encode and decode UTF-8, UTF-16 and UTF-32 strings. 3 | * 4 | * For Win32 systems, the C wchar_t type is UTF-16 and corresponds to the D 5 | * wchar type. 6 | * For Posix systems, the C wchar_t type is UTF-32 and corresponds to 7 | * the D utf.dchar type. 8 | * 9 | * UTF character support is restricted to (\u0000 <= character <= \U0010FFFF). 10 | * 11 | * See_Also: 12 | * $(LINK2 http://en.wikipedia.org/wiki/Unicode, Wikipedia)
13 | * $(LINK http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8)
14 | * $(LINK http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335) 15 | * 16 | * Copyright: Copyright Digital Mars 2003 - 2016. 17 | * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 18 | * Authors: Walter Bright, Sean Kelly 19 | * Source: $(DRUNTIMESRC core/internal/_utf.d) 20 | */ 21 | 22 | module core.internal.utf; 23 | 24 | extern (C) void onUnicodeError( string msg, size_t idx, string file = __FILE__, size_t line = __LINE__ ) @trusted 25 | { 26 | _d_assert_msg("onUnicodeError: "~msg, file, line); 27 | } 28 | 29 | /******************************* 30 | * Test if c is a valid UTF-32 character. 31 | * 32 | * \uFFFE and \uFFFF are considered valid by this function, 33 | * as they are permitted for internal use by an application, 34 | * but they are not allowed for interchange by the Unicode standard. 35 | * 36 | * Returns: true if it is, false if not. 37 | */ 38 | 39 | @safe @nogc pure nothrow 40 | bool isValidDchar(dchar c) 41 | { 42 | /* Note: FFFE and FFFF are specifically permitted by the 43 | * Unicode standard for application internal use, but are not 44 | * allowed for interchange. 45 | * (thanks to Arcane Jill) 46 | */ 47 | 48 | return c < 0xD800 || 49 | (c > 0xDFFF && c <= 0x10FFFF /*&& c != 0xFFFE && c != 0xFFFF*/); 50 | } 51 | 52 | unittest 53 | { 54 | debug(utf) printf("utf.isValidDchar.unittest\n"); 55 | assert(isValidDchar(cast(dchar)'a') == true); 56 | assert(isValidDchar(cast(dchar)0x1FFFFF) == false); 57 | } 58 | 59 | 60 | 61 | static immutable UTF8stride = 62 | [ 63 | cast(ubyte) 64 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 65 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 66 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 67 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 68 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 69 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 70 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 71 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 72 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 73 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 74 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 75 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 76 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 77 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 78 | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 79 | 4,4,4,4,4,4,4,4,5,5,5,5,6,6,0xFF,0xFF, 80 | ]; 81 | 82 | /** 83 | * stride() returns the length of a UTF-8 sequence starting at index i 84 | * in string s. 85 | * Returns: 86 | * The number of bytes in the UTF-8 sequence or 87 | * 0xFF meaning s[i] is not the start of of UTF-8 sequence. 88 | */ 89 | @safe @nogc pure nothrow 90 | uint stride(const scope char[] s, size_t i) 91 | { 92 | return UTF8stride[s[i]]; 93 | } 94 | 95 | /** 96 | * stride() returns the length of a UTF-16 sequence starting at index i 97 | * in string s. 98 | */ 99 | @safe @nogc pure nothrow 100 | uint stride(const scope wchar[] s, size_t i) 101 | { uint u = s[i]; 102 | return 1 + (u >= 0xD800 && u <= 0xDBFF); 103 | } 104 | 105 | /** 106 | * stride() returns the length of a UTF-32 sequence starting at index i 107 | * in string s. 108 | * Returns: The return value will always be 1. 109 | */ 110 | @safe @nogc pure nothrow 111 | uint stride(const scope dchar[] s, size_t i) 112 | { 113 | return 1; 114 | } 115 | 116 | /******************************************* 117 | * Given an index i into an array of characters s[], 118 | * and assuming that index i is at the start of a UTF character, 119 | * determine the number of UCS characters up to that index i. 120 | */ 121 | @safe 122 | size_t toUCSindex(const scope char[] s, size_t i) 123 | { 124 | size_t n; 125 | size_t j; 126 | 127 | for (j = 0; j < i; ) 128 | { 129 | j += stride(s, j); 130 | n++; 131 | } 132 | if (j > i) 133 | { 134 | onUnicodeError("invalid UTF-8 sequence", j); 135 | } 136 | return n; 137 | } 138 | 139 | /** ditto */ 140 | @safe 141 | size_t toUCSindex(const scope wchar[] s, size_t i) 142 | { 143 | size_t n; 144 | size_t j; 145 | 146 | for (j = 0; j < i; ) 147 | { 148 | j += stride(s, j); 149 | n++; 150 | } 151 | if (j > i) 152 | { 153 | onUnicodeError("invalid UTF-16 sequence", j); 154 | } 155 | return n; 156 | } 157 | 158 | /** ditto */ 159 | @safe @nogc pure nothrow 160 | size_t toUCSindex(const scope dchar[] s, size_t i) 161 | { 162 | return i; 163 | } 164 | 165 | /****************************************** 166 | * Given a UCS index n into an array of characters s[], return the UTF index. 167 | */ 168 | @safe 169 | size_t toUTFindex(const scope char[] s, size_t n) 170 | { 171 | size_t i; 172 | 173 | while (n--) 174 | { 175 | uint j = UTF8stride[s[i]]; 176 | if (j == 0xFF) 177 | onUnicodeError("invalid UTF-8 sequence", i); 178 | i += j; 179 | } 180 | return i; 181 | } 182 | 183 | /** ditto */ 184 | @safe @nogc pure nothrow 185 | size_t toUTFindex(const scope wchar[] s, size_t n) 186 | { 187 | size_t i; 188 | 189 | while (n--) 190 | { wchar u = s[i]; 191 | 192 | i += 1 + (u >= 0xD800 && u <= 0xDBFF); 193 | } 194 | return i; 195 | } 196 | 197 | /** ditto */ 198 | @safe @nogc pure nothrow 199 | size_t toUTFindex(const scope dchar[] s, size_t n) 200 | { 201 | return n; 202 | } 203 | 204 | /* =================== Decode ======================= */ 205 | 206 | /*************** 207 | * Decodes and returns character starting at s[idx]. idx is advanced past the 208 | * decoded character. If the character is not well formed, a UtfException is 209 | * thrown and idx remains unchanged. 210 | */ 211 | @safe 212 | dchar decode(const scope char[] s, ref size_t idx) 213 | in 214 | { 215 | assert(idx >= 0 && idx < s.length); 216 | } 217 | out (result) 218 | { 219 | assert(isValidDchar(result)); 220 | } 221 | do 222 | { 223 | size_t len = s.length; 224 | dchar V; 225 | size_t i = idx; 226 | char u = s[i]; 227 | 228 | if (u & 0x80) 229 | { uint n; 230 | char u2; 231 | 232 | /* The following encodings are valid, except for the 5 and 6 byte 233 | * combinations: 234 | * 0xxxxxxx 235 | * 110xxxxx 10xxxxxx 236 | * 1110xxxx 10xxxxxx 10xxxxxx 237 | * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 238 | * 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 239 | * 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 240 | */ 241 | for (n = 1; ; n++) 242 | { 243 | if (n > 4) 244 | goto Lerr; // only do the first 4 of 6 encodings 245 | if (((u << n) & 0x80) == 0) 246 | { 247 | if (n == 1) 248 | goto Lerr; 249 | break; 250 | } 251 | } 252 | 253 | // Pick off (7 - n) significant bits of B from first byte of octet 254 | V = cast(dchar)(u & ((1 << (7 - n)) - 1)); 255 | 256 | if (i + (n - 1) >= len) 257 | goto Lerr; // off end of string 258 | 259 | /* The following combinations are overlong, and illegal: 260 | * 1100000x (10xxxxxx) 261 | * 11100000 100xxxxx (10xxxxxx) 262 | * 11110000 1000xxxx (10xxxxxx 10xxxxxx) 263 | * 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) 264 | * 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) 265 | */ 266 | u2 = s[i + 1]; 267 | if ((u & 0xFE) == 0xC0 || 268 | (u == 0xE0 && (u2 & 0xE0) == 0x80) || 269 | (u == 0xF0 && (u2 & 0xF0) == 0x80) || 270 | (u == 0xF8 && (u2 & 0xF8) == 0x80) || 271 | (u == 0xFC && (u2 & 0xFC) == 0x80)) 272 | goto Lerr; // overlong combination 273 | 274 | for (uint j = 1; j != n; j++) 275 | { 276 | u = s[i + j]; 277 | if ((u & 0xC0) != 0x80) 278 | goto Lerr; // trailing bytes are 10xxxxxx 279 | V = (V << 6) | (u & 0x3F); 280 | } 281 | if (!isValidDchar(V)) 282 | goto Lerr; 283 | i += n; 284 | } 285 | else 286 | { 287 | V = cast(dchar) u; 288 | i++; 289 | } 290 | 291 | idx = i; 292 | return V; 293 | 294 | Lerr: 295 | onUnicodeError("invalid UTF-8 sequence", i); 296 | return V; // dummy return 297 | } 298 | 299 | 300 | /** ditto */ 301 | @safe 302 | dchar decode(const scope wchar[] s, ref size_t idx) 303 | in 304 | { 305 | assert(idx >= 0 && idx < s.length); 306 | } 307 | out (result) 308 | { 309 | assert(isValidDchar(result)); 310 | } 311 | do 312 | { 313 | string msg; 314 | dchar V; 315 | size_t i = idx; 316 | uint u = s[i]; 317 | 318 | if (u & ~0x7F) 319 | { if (u >= 0xD800 && u <= 0xDBFF) 320 | { uint u2; 321 | 322 | if (i + 1 == s.length) 323 | { msg = "surrogate UTF-16 high value past end of string"; 324 | goto Lerr; 325 | } 326 | u2 = s[i + 1]; 327 | if (u2 < 0xDC00 || u2 > 0xDFFF) 328 | { msg = "surrogate UTF-16 low value out of range"; 329 | goto Lerr; 330 | } 331 | u = ((u - 0xD7C0) << 10) + (u2 - 0xDC00); 332 | i += 2; 333 | } 334 | else if (u >= 0xDC00 && u <= 0xDFFF) 335 | { msg = "unpaired surrogate UTF-16 value"; 336 | goto Lerr; 337 | } 338 | else if (u == 0xFFFE || u == 0xFFFF) 339 | { msg = "illegal UTF-16 value"; 340 | goto Lerr; 341 | } 342 | else 343 | i++; 344 | } 345 | else 346 | { 347 | i++; 348 | } 349 | 350 | idx = i; 351 | return cast(dchar)u; 352 | 353 | Lerr: 354 | onUnicodeError(msg, i); 355 | return cast(dchar)u; // dummy return 356 | } 357 | 358 | /** ditto */ 359 | @safe 360 | dchar decode(const scope dchar[] s, ref size_t idx) 361 | in 362 | { 363 | assert(idx >= 0 && idx < s.length); 364 | } 365 | do 366 | { 367 | size_t i = idx; 368 | dchar c = s[i]; 369 | 370 | if (!isValidDchar(c)) 371 | goto Lerr; 372 | idx = i + 1; 373 | return c; 374 | 375 | Lerr: 376 | onUnicodeError("invalid UTF-32 value", i); 377 | return c; // dummy return 378 | } 379 | 380 | 381 | /* =================== Encode ======================= */ 382 | 383 | /******************************* 384 | * Encodes character c and appends it to array s[]. 385 | */ 386 | @safe pure nothrow 387 | void encode(ref char[] s, dchar c) 388 | in 389 | { 390 | assert(isValidDchar(c)); 391 | } 392 | do 393 | { 394 | char[] r = s; 395 | 396 | if (c <= 0x7F) 397 | { 398 | r ~= cast(char) c; 399 | } 400 | else 401 | { 402 | char[4] buf = void; 403 | uint L; 404 | 405 | if (c <= 0x7FF) 406 | { 407 | buf[0] = cast(char)(0xC0 | (c >> 6)); 408 | buf[1] = cast(char)(0x80 | (c & 0x3F)); 409 | L = 2; 410 | } 411 | else if (c <= 0xFFFF) 412 | { 413 | buf[0] = cast(char)(0xE0 | (c >> 12)); 414 | buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 415 | buf[2] = cast(char)(0x80 | (c & 0x3F)); 416 | L = 3; 417 | } 418 | else if (c <= 0x10FFFF) 419 | { 420 | buf[0] = cast(char)(0xF0 | (c >> 18)); 421 | buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); 422 | buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 423 | buf[3] = cast(char)(0x80 | (c & 0x3F)); 424 | L = 4; 425 | } 426 | else 427 | { 428 | assert(0); 429 | } 430 | r ~= buf[0 .. L]; 431 | } 432 | s = r; 433 | } 434 | 435 | unittest 436 | { 437 | debug(utf) printf("utf.encode.unittest\n"); 438 | 439 | char[] s = "abcd".dup; 440 | encode(s, cast(dchar)'a'); 441 | assert(s.length == 5); 442 | assert(s == "abcda"); 443 | 444 | encode(s, cast(dchar)'\u00A9'); 445 | assert(s.length == 7); 446 | assert(s == "abcda\xC2\xA9"); 447 | //assert(s == "abcda\u00A9"); // BUG: fix compiler 448 | 449 | encode(s, cast(dchar)'\u2260'); 450 | assert(s.length == 10); 451 | assert(s == "abcda\xC2\xA9\xE2\x89\xA0"); 452 | } 453 | 454 | /** ditto */ 455 | @safe pure nothrow 456 | void encode(ref wchar[] s, dchar c) 457 | in 458 | { 459 | assert(isValidDchar(c)); 460 | } 461 | do 462 | { 463 | wchar[] r = s; 464 | 465 | if (c <= 0xFFFF) 466 | { 467 | r ~= cast(wchar) c; 468 | } 469 | else 470 | { 471 | wchar[2] buf = void; 472 | 473 | buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 474 | buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); 475 | r ~= buf; 476 | } 477 | s = r; 478 | } 479 | 480 | /** ditto */ 481 | @safe pure nothrow 482 | void encode(ref dchar[] s, dchar c) 483 | in 484 | { 485 | assert(isValidDchar(c)); 486 | } 487 | do 488 | { 489 | s ~= c; 490 | } 491 | 492 | /** 493 | Returns the code length of $(D c) in the encoding using $(D C) as a 494 | code point. The code is returned in character count, not in bytes. 495 | */ 496 | @safe pure nothrow @nogc 497 | ubyte codeLength(C)(dchar c) 498 | { 499 | static if (C.sizeof == 1) 500 | { 501 | if (c <= 0x7F) return 1; 502 | if (c <= 0x7FF) return 2; 503 | if (c <= 0xFFFF) return 3; 504 | if (c <= 0x10FFFF) return 4; 505 | assert(false); 506 | } 507 | else static if (C.sizeof == 2) 508 | { 509 | return c <= 0xFFFF ? 1 : 2; 510 | } 511 | else 512 | { 513 | static assert(C.sizeof == 4); 514 | return 1; 515 | } 516 | } 517 | 518 | /* =================== Validation ======================= */ 519 | 520 | /*********************************** 521 | Checks to see if string is well formed or not. $(D S) can be an array 522 | of $(D char), $(D wchar), or $(D dchar). Returns $(D false) if it is not. 523 | Use to check all untrusted input for correctness. 524 | */ 525 | @safe 526 | bool isValidString(S)(const scope S s) 527 | { 528 | auto len = s.length; 529 | for (size_t i = 0; i < len; ) 530 | { 531 | decode(s, i); 532 | } 533 | 534 | return true; 535 | } 536 | 537 | /* =================== Conversion to UTF8 ======================= */ 538 | 539 | @safe nothrow @nogc 540 | char[] toUTF8(return scope char[] buf, dchar c) 541 | in 542 | { 543 | assert(isValidDchar(c)); 544 | } 545 | do 546 | { 547 | if (c <= 0x7F) 548 | { 549 | buf[0] = cast(char) c; 550 | return buf[0 .. 1]; 551 | } 552 | else if (c <= 0x7FF) 553 | { 554 | buf[0] = cast(char)(0xC0 | (c >> 6)); 555 | buf[1] = cast(char)(0x80 | (c & 0x3F)); 556 | return buf[0 .. 2]; 557 | } 558 | else if (c <= 0xFFFF) 559 | { 560 | buf[0] = cast(char)(0xE0 | (c >> 12)); 561 | buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 562 | buf[2] = cast(char)(0x80 | (c & 0x3F)); 563 | return buf[0 .. 3]; 564 | } 565 | else if (c <= 0x10FFFF) 566 | { 567 | buf[0] = cast(char)(0xF0 | (c >> 18)); 568 | buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); 569 | buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 570 | buf[3] = cast(char)(0x80 | (c & 0x3F)); 571 | return buf[0 .. 4]; 572 | } 573 | assert(0); 574 | } 575 | 576 | /******************* 577 | * Encodes string s into UTF-8 and returns the encoded string. 578 | */ 579 | @safe 580 | string toUTF8(return scope string s) 581 | in 582 | { 583 | assert(isValidString(s)); 584 | } 585 | do 586 | { 587 | return s; 588 | } 589 | 590 | /** ditto */ 591 | @trusted 592 | string toUTF8(const scope wchar[] s) 593 | { 594 | char[] r; 595 | size_t i; 596 | size_t slen = s.length; 597 | 598 | r.length = slen; 599 | 600 | for (i = 0; i < slen; i++) 601 | { wchar c = s[i]; 602 | 603 | if (c <= 0x7F) 604 | r[i] = cast(char)c; // fast path for ascii 605 | else 606 | { 607 | r.length = i; 608 | foreach (dchar ch; s[i .. slen]) 609 | { 610 | encode(r, ch); 611 | } 612 | break; 613 | } 614 | } 615 | return cast(string)r; 616 | } 617 | 618 | /** ditto */ 619 | @trusted 620 | string toUTF8(const scope dchar[] s) 621 | { 622 | char[] r; 623 | size_t i; 624 | size_t slen = s.length; 625 | 626 | r.length = slen; 627 | 628 | for (i = 0; i < slen; i++) 629 | { dchar c = s[i]; 630 | 631 | if (c <= 0x7F) 632 | r[i] = cast(char)c; // fast path for ascii 633 | else 634 | { 635 | r.length = i; 636 | foreach (dchar d; s[i .. slen]) 637 | { 638 | encode(r, d); 639 | } 640 | break; 641 | } 642 | } 643 | return cast(string)r; 644 | } 645 | 646 | /* =================== Conversion to UTF16 ======================= */ 647 | 648 | @safe pure nothrow @nogc 649 | wchar[] toUTF16(return scope wchar[] buf, dchar c) 650 | in 651 | { 652 | assert(isValidDchar(c)); 653 | } 654 | do 655 | { 656 | if (c <= 0xFFFF) 657 | { 658 | buf[0] = cast(wchar) c; 659 | return buf[0 .. 1]; 660 | } 661 | else 662 | { 663 | buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 664 | buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); 665 | return buf[0 .. 2]; 666 | } 667 | } 668 | 669 | /**************** 670 | * Encodes string s into UTF-16 and returns the encoded string. 671 | * toUTF16z() is suitable for calling the 'W' functions in the Win32 API that take 672 | * an LPWSTR or LPCWSTR argument. 673 | */ 674 | @trusted 675 | wstring toUTF16(const scope char[] s) 676 | { 677 | wchar[] r; 678 | size_t slen = s.length; 679 | 680 | if (!__ctfe) 681 | { 682 | // Reserve still does a lot if slen is zero. 683 | // Return early for that case. 684 | if (0 == slen) 685 | return ""w; 686 | r.reserve(slen); 687 | } 688 | for (size_t i = 0; i < slen; ) 689 | { 690 | dchar c = s[i]; 691 | if (c <= 0x7F) 692 | { 693 | i++; 694 | r ~= cast(wchar)c; 695 | } 696 | else 697 | { 698 | c = decode(s, i); 699 | encode(r, c); 700 | } 701 | } 702 | return cast(wstring)r; 703 | } 704 | 705 | alias const(wchar)* wptr; 706 | /** ditto */ 707 | @trusted 708 | wptr toUTF16z(const scope char[] s) 709 | { 710 | wchar[] r; 711 | size_t slen = s.length; 712 | 713 | if (!__ctfe) 714 | { 715 | // Reserve still does a lot if slen is zero. 716 | // Return early for that case. 717 | if (0 == slen) 718 | return &"\0"w[0]; 719 | r.reserve(slen + 1); 720 | } 721 | for (size_t i = 0; i < slen; ) 722 | { 723 | dchar c = s[i]; 724 | if (c <= 0x7F) 725 | { 726 | i++; 727 | r ~= cast(wchar)c; 728 | } 729 | else 730 | { 731 | c = decode(s, i); 732 | encode(r, c); 733 | } 734 | } 735 | r ~= '\000'; 736 | return &r[0]; 737 | } 738 | 739 | /** ditto */ 740 | @safe 741 | wstring toUTF16(return scope wstring s) 742 | in 743 | { 744 | assert(isValidString(s)); 745 | } 746 | do 747 | { 748 | return s; 749 | } 750 | 751 | /** ditto */ 752 | @trusted 753 | wstring toUTF16(const scope dchar[] s) 754 | { 755 | wchar[] r; 756 | size_t slen = s.length; 757 | 758 | if (!__ctfe) 759 | { 760 | // Reserve still does a lot if slen is zero. 761 | // Return early for that case. 762 | if (0 == slen) 763 | return ""w; 764 | r.reserve(slen); 765 | } 766 | for (size_t i = 0; i < slen; i++) 767 | { 768 | encode(r, s[i]); 769 | } 770 | return cast(wstring)r; 771 | } 772 | 773 | /* =================== Conversion to UTF32 ======================= */ 774 | 775 | /***** 776 | * Encodes string s into UTF-32 and returns the encoded string. 777 | */ 778 | @trusted 779 | dstring toUTF32(const scope char[] s) 780 | { 781 | dchar[] r; 782 | size_t slen = s.length; 783 | size_t j = 0; 784 | 785 | r.length = slen; // r[] will never be longer than s[] 786 | for (size_t i = 0; i < slen; ) 787 | { 788 | dchar c = s[i]; 789 | if (c >= 0x80) 790 | c = decode(s, i); 791 | else 792 | i++; // c is ascii, no need for decode 793 | r[j++] = c; 794 | } 795 | return cast(dstring)r[0 .. j]; 796 | } 797 | 798 | /** ditto */ 799 | @trusted 800 | dstring toUTF32(const scope wchar[] s) 801 | { 802 | dchar[] r; 803 | size_t slen = s.length; 804 | size_t j = 0; 805 | 806 | r.length = slen; // r[] will never be longer than s[] 807 | for (size_t i = 0; i < slen; ) 808 | { 809 | dchar c = s[i]; 810 | if (c >= 0x80) 811 | c = decode(s, i); 812 | else 813 | i++; // c is ascii, no need for decode 814 | r[j++] = c; 815 | } 816 | return cast(dstring)r[0 .. j]; 817 | } 818 | 819 | /** ditto */ 820 | @safe 821 | dstring toUTF32(return scope dstring s) 822 | in 823 | { 824 | assert(isValidString(s)); 825 | } 826 | do 827 | { 828 | return s; 829 | } 830 | 831 | /* ================================ tests ================================== */ 832 | 833 | unittest 834 | { 835 | debug(utf) printf("utf.toUTF.unittest\n"); 836 | 837 | auto c = "hello"c[]; 838 | auto w = toUTF16(c); 839 | assert(w == "hello"); 840 | auto d = toUTF32(c); 841 | assert(d == "hello"); 842 | 843 | c = toUTF8(w); 844 | assert(c == "hello"); 845 | d = toUTF32(w); 846 | assert(d == "hello"); 847 | 848 | c = toUTF8(d); 849 | assert(c == "hello"); 850 | w = toUTF16(d); 851 | assert(w == "hello"); 852 | 853 | 854 | c = "hel\u1234o"; 855 | w = toUTF16(c); 856 | assert(w == "hel\u1234o"); 857 | d = toUTF32(c); 858 | assert(d == "hel\u1234o"); 859 | 860 | c = toUTF8(w); 861 | assert(c == "hel\u1234o"); 862 | d = toUTF32(w); 863 | assert(d == "hel\u1234o"); 864 | 865 | c = toUTF8(d); 866 | assert(c == "hel\u1234o"); 867 | w = toUTF16(d); 868 | assert(w == "hel\u1234o"); 869 | 870 | 871 | c = "he\U000BAAAAllo"; 872 | w = toUTF16(c); 873 | //foreach (wchar c; w) printf("c = x%x\n", c); 874 | //foreach (wchar c; cast(wstring)"he\U000BAAAAllo") printf("c = x%x\n", c); 875 | assert(w == "he\U000BAAAAllo"); 876 | d = toUTF32(c); 877 | assert(d == "he\U000BAAAAllo"); 878 | 879 | c = toUTF8(w); 880 | assert(c == "he\U000BAAAAllo"); 881 | d = toUTF32(w); 882 | assert(d == "he\U000BAAAAllo"); 883 | 884 | c = toUTF8(d); 885 | assert(c == "he\U000BAAAAllo"); 886 | w = toUTF16(d); 887 | assert(w == "he\U000BAAAAllo"); 888 | 889 | wchar[2] buf; 890 | auto ret = toUTF16(buf, '\U000BAAAA'); 891 | assert(ret == "\U000BAAAA"); 892 | } 893 | -------------------------------------------------------------------------------- /arsd-webassembly/object.d: -------------------------------------------------------------------------------- 1 | // Minimal druntime for webassembly. Assumes your program has a main function. 2 | module object; 3 | 4 | static import arsd.webassembly; 5 | 6 | version(CarelessAlocation) 7 | { 8 | version = inline_concat; 9 | } 10 | 11 | import core.arsd.memory_allocation; 12 | 13 | alias noreturn = typeof(*null); 14 | alias string = immutable(char)[]; 15 | alias wstring = immutable(wchar)[]; 16 | alias dstring = immutable(dchar)[]; 17 | alias size_t = uint; 18 | alias ptrdiff_t = int; 19 | 20 | 21 | // then the entry point just for convenience so main works. 22 | extern(C) int _Dmain(string[] args); 23 | export extern(C) void _start() { _Dmain(null); } 24 | 25 | extern(C) bool _xopEquals(in void*, in void*) { return false; } // assert(0); 26 | 27 | // basic array support { 28 | 29 | template _arrayOp(Args...) 30 | { 31 | import core.internal.array.operations; 32 | alias _arrayOp = arrayOp!Args; 33 | } 34 | 35 | extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsz) { 36 | auto d = cast(ubyte*) dst; 37 | auto s = cast(ubyte*) src; 38 | auto len = dstlen * elemsz; 39 | 40 | while(len) { 41 | *d = *s; 42 | d++; 43 | s++; 44 | len--; 45 | } 46 | 47 | } 48 | 49 | void reserve(T)(ref T[] arr, size_t length) @trusted { 50 | arr = (cast(T*) (malloc(length * T.sizeof).ptr))[0 .. 0]; 51 | } 52 | 53 | 54 | extern(C) void _d_arraybounds(string file, size_t line) { 55 | arsd.webassembly.eval( 56 | q{ console.error("Range error: " + $0 + ":" + $1 )}, 57 | file, line); 58 | arsd.webassembly.abort(); 59 | } 60 | 61 | 62 | /// Called when an out of range slice of an array is created 63 | extern(C) void _d_arraybounds_slice(string file, uint line, size_t lwr, size_t upr, size_t length) 64 | { 65 | arsd.webassembly.eval( 66 | q{ console.error("Range error: " + $0 + ":" + $1 + " [" + $2 + ".." + $3 + "] <> " + $4)}, 67 | file, line, lwr, upr, length); 68 | arsd.webassembly.abort(); 69 | } 70 | 71 | /// Called when an out of range array index is accessed 72 | extern(C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length) 73 | { 74 | arsd.webassembly.eval( 75 | q{ console.error("Array index " + $0 + " out of bounds '[0.."+$1+"]' " + $2 + ":" + $3)}, 76 | index, length, file, line); 77 | arsd.webassembly.abort(); 78 | } 79 | 80 | 81 | extern(C) void* memset(void* s, int c, size_t n) @nogc nothrow pure 82 | { 83 | auto d = cast(ubyte*) s; 84 | while(n) { 85 | *d = cast(ubyte) c; 86 | d++; 87 | n--; 88 | } 89 | return s; 90 | } 91 | 92 | pragma(LDC_intrinsic, "llvm.memcpy.p0i8.p0i8.i#") 93 | void llvm_memcpy(T)(void* dst, const(void)* src, T len, bool volatile_ = false); 94 | 95 | extern(C) void *memcpy(void* dest, const(void)* src, size_t n) pure @nogc nothrow 96 | { 97 | ubyte *d = cast(ubyte*) dest; 98 | const (ubyte) *s = cast(const(ubyte)*)src; 99 | for (; n; n--) *d++ = *s++; 100 | return dest; 101 | } 102 | 103 | extern(C) int memcmp(const(void)* s1, const(void*) s2, size_t n) pure @nogc nothrow @trusted 104 | { 105 | auto b = cast(ubyte*) s1; 106 | auto b2 = cast(ubyte*) s2; 107 | foreach(i; 0 .. n) { 108 | if(auto diff = *b - *b2) 109 | return diff; 110 | b++; 111 | b2++; 112 | } 113 | return 0; 114 | } 115 | 116 | public import core.arsd.utf_decoding; 117 | 118 | // } 119 | 120 | extern(C) void _d_assert(string file, uint line) @trusted @nogc pure 121 | { 122 | arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file, line);//, lwr, upr, length); 123 | arsd.webassembly.abort(); 124 | } 125 | void _d_assertp(immutable(char)* file, uint line) 126 | { 127 | // import core.stdc.string : strlen; 128 | size_t sz = 0; 129 | while(file[sz] != '\0') sz++; 130 | arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1 + "(" + $2 + ")"); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file[0 .. sz], line);//, lwr, upr, length); 131 | arsd.webassembly.abort(); 132 | } 133 | 134 | 135 | extern(C) void _d_assert_msg(string msg, string file, uint line) @trusted @nogc pure 136 | { 137 | arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1 + "(" + $2 + ")"); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file, line, msg);//, lwr, upr, length); 138 | arsd.webassembly.abort(); 139 | } 140 | 141 | void __switch_error(string file, size_t line) @trusted @nogc pure 142 | { 143 | _d_assert_msg("final switch error",file, line); 144 | } 145 | 146 | bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) { 147 | if (lhs.length != rhs.length) { 148 | return false; 149 | } 150 | foreach(i; 0..lhs.length) { 151 | if (lhs[i] != rhs[i]) { 152 | return false; 153 | } 154 | } 155 | return true; 156 | } 157 | 158 | // bare basics class support { 159 | 160 | 161 | extern(C) Object _d_allocclass(TypeInfo_Class ti) { 162 | auto ptr = malloc(ti.m_init.length); 163 | ptr[] = ti.m_init[]; 164 | return cast(Object) ptr.ptr; 165 | } 166 | 167 | extern(C) void* _d_dynamic_cast(Object o, TypeInfo_Class c) { 168 | void* res = null; 169 | size_t offset = 0; 170 | if (o && _d_isbaseof2(typeid(o), c, offset)) 171 | { 172 | res = cast(void*) o + offset; 173 | } 174 | return res; 175 | } 176 | 177 | /************************************* 178 | * Attempts to cast Object o to class c. 179 | * Returns o if successful, null if not. 180 | */ 181 | extern(C) void* _d_interface_cast(void* p, TypeInfo_Class c) 182 | { 183 | if (!p) 184 | return null; 185 | 186 | Interface* pi = **cast(Interface***) p; 187 | return _d_dynamic_cast(cast(Object)(p - pi.offset), c); 188 | } 189 | 190 | 191 | extern(C) 192 | int _d_isbaseof2(scope TypeInfo_Class oc, scope const TypeInfo_Class c, scope ref size_t offset) @safe 193 | 194 | { 195 | if (oc is c) 196 | return true; 197 | 198 | do 199 | { 200 | if (oc.base is c) 201 | return true; 202 | 203 | // Bugzilla 2013: Use depth-first search to calculate offset 204 | // from the derived (oc) to the base (c). 205 | foreach (iface; oc.interfaces) 206 | { 207 | if (iface.classinfo is c || _d_isbaseof2(iface.classinfo, c, offset)) 208 | { 209 | offset += iface.offset; 210 | return true; 211 | } 212 | } 213 | 214 | oc = oc.base; 215 | } while (oc); 216 | 217 | return false; 218 | } 219 | 220 | int __cmp(T)(scope const T[] lhs, scope const T[] rhs) @trusted pure @nogc nothrow 221 | if (__traits(isScalar, T)) 222 | { 223 | // Compute U as the implementation type for T 224 | static if (is(T == ubyte) || is(T == void) || is(T == bool)) 225 | alias U = char; 226 | else static if (is(T == wchar)) 227 | alias U = ushort; 228 | else static if (is(T == dchar)) 229 | alias U = uint; 230 | else static if (is(T == ifloat)) 231 | alias U = float; 232 | else static if (is(T == idouble)) 233 | alias U = double; 234 | else static if (is(T == ireal)) 235 | alias U = real; 236 | else 237 | alias U = T; 238 | 239 | static if (is(U == char)) 240 | { 241 | int dstrcmp(scope const char[] s1, scope const char[] s2 ) @trusted pure @nogc nothrow 242 | { 243 | immutable len = s1.length <= s2.length ? s1.length : s2.length; 244 | if (__ctfe) 245 | { 246 | foreach (const u; 0 .. len) 247 | { 248 | if (s1[u] != s2[u]) 249 | return s1[u] > s2[u] ? 1 : -1; 250 | } 251 | } 252 | else 253 | { 254 | const ret = memcmp( s1.ptr, s2.ptr, len ); 255 | if ( ret ) 256 | return ret; 257 | } 258 | return (s1.length > s2.length) - (s1.length < s2.length); 259 | } 260 | return dstrcmp(cast(char[]) lhs, cast(char[]) rhs); 261 | } 262 | else static if (!is(U == T)) 263 | { 264 | // Reuse another implementation 265 | return __cmp(cast(U[]) lhs, cast(U[]) rhs); 266 | } 267 | else 268 | { 269 | version (BigEndian) 270 | static if (__traits(isUnsigned, T) ? !is(T == __vector) : is(T : P*, P)) 271 | { 272 | if (!__ctfe) 273 | { 274 | import core.stdc.string : memcmp; 275 | int c = memcmp(lhs.ptr, rhs.ptr, (lhs.length <= rhs.length ? lhs.length : rhs.length) * T.sizeof); 276 | if (c) 277 | return c; 278 | static if (size_t.sizeof <= uint.sizeof && T.sizeof >= 2) 279 | return cast(int) lhs.length - cast(int) rhs.length; 280 | else 281 | return int(lhs.length > rhs.length) - int(lhs.length < rhs.length); 282 | } 283 | } 284 | 285 | immutable len = lhs.length <= rhs.length ? lhs.length : rhs.length; 286 | foreach (const u; 0 .. len) 287 | { 288 | auto a = lhs.ptr[u], b = rhs.ptr[u]; 289 | static if (is(T : creal)) 290 | { 291 | // Use rt.cmath2._Ccmp instead ? 292 | // Also: if NaN is present, numbers will appear equal. 293 | auto r = (a.re > b.re) - (a.re < b.re); 294 | if (!r) r = (a.im > b.im) - (a.im < b.im); 295 | } 296 | else 297 | { 298 | // This pattern for three-way comparison is better than conditional operators 299 | // See e.g. https://godbolt.org/z/3j4vh1 300 | const r = (a > b) - (a < b); 301 | } 302 | if (r) return r; 303 | } 304 | return (lhs.length > rhs.length) - (lhs.length < rhs.length); 305 | } 306 | } 307 | 308 | // This function is called by the compiler when dealing with array 309 | // comparisons in the semantic analysis phase of CmpExp. The ordering 310 | // comparison is lowered to a call to this template. 311 | int __cmp(T1, T2)(T1[] s1, T2[] s2) 312 | if (!__traits(isScalar, T1) && !__traits(isScalar, T2)) 313 | { 314 | import core.internal.traits : Unqual; 315 | alias U1 = Unqual!T1; 316 | alias U2 = Unqual!T2; 317 | 318 | static if (is(U1 == void) && is(U2 == void)) 319 | static @trusted ref inout(ubyte) at(inout(void)[] r, size_t i) { return (cast(inout(ubyte)*) r.ptr)[i]; } 320 | else 321 | static @trusted ref R at(R)(R[] r, size_t i) { return r.ptr[i]; } 322 | 323 | // All unsigned byte-wide types = > dstrcmp 324 | immutable len = s1.length <= s2.length ? s1.length : s2.length; 325 | 326 | foreach (const u; 0 .. len) 327 | { 328 | static if (__traits(compiles, __cmp(at(s1, u), at(s2, u)))) 329 | { 330 | auto c = __cmp(at(s1, u), at(s2, u)); 331 | if (c != 0) 332 | return c; 333 | } 334 | else static if (__traits(compiles, at(s1, u).opCmp(at(s2, u)))) 335 | { 336 | auto c = at(s1, u).opCmp(at(s2, u)); 337 | if (c != 0) 338 | return c; 339 | } 340 | else static if (__traits(compiles, at(s1, u) < at(s2, u))) 341 | { 342 | if (int result = (at(s1, u) > at(s2, u)) - (at(s1, u) < at(s2, u))) 343 | return result; 344 | } 345 | else 346 | { 347 | // TODO: fix this legacy bad behavior, see 348 | // https://issues.dlang.org/show_bug.cgi?id=17244 349 | static assert(is(U1 == U2), "Internal error."); 350 | import core.stdc.string : memcmp; 351 | auto c = (() @trusted => memcmp(&at(s1, u), &at(s2, u), U1.sizeof))(); 352 | if (c != 0) 353 | return c; 354 | } 355 | } 356 | return (s1.length > s2.length) - (s1.length < s2.length); 357 | } 358 | 359 | 360 | 361 | /** 362 | Support for switch statements switching on strings. 363 | Params: 364 | caseLabels = sorted array of strings generated by compiler. Note the 365 | strings are sorted by length first, and then lexicographically. 366 | condition = string to look up in table 367 | Returns: 368 | index of match in caseLabels, a negative integer if not found 369 | */ 370 | int __switch(T, caseLabels...)(/*in*/ const scope T[] condition) pure nothrow @safe @nogc 371 | { 372 | // This closes recursion for other cases. 373 | static if (caseLabels.length == 0) 374 | { 375 | return int.min; 376 | } 377 | else static if (caseLabels.length == 1) 378 | { 379 | return __cmp(condition, caseLabels[0]) == 0 ? 0 : int.min; 380 | } 381 | // To be adjusted after measurements 382 | // Compile-time inlined binary search. 383 | else static if (caseLabels.length < 7) 384 | { 385 | int r = void; 386 | enum mid = cast(int)caseLabels.length / 2; 387 | if (condition.length == caseLabels[mid].length) 388 | { 389 | r = __cmp(condition, caseLabels[mid]); 390 | if (r == 0) return mid; 391 | } 392 | else 393 | { 394 | // Equivalent to (but faster than) condition.length > caseLabels[$ / 2].length ? 1 : -1 395 | r = ((condition.length > caseLabels[mid].length) << 1) - 1; 396 | } 397 | 398 | if (r < 0) 399 | { 400 | // Search the left side 401 | return __switch!(T, caseLabels[0 .. mid])(condition); 402 | } 403 | else 404 | { 405 | // Search the right side 406 | return __switch!(T, caseLabels[mid + 1 .. $])(condition) + mid + 1; 407 | } 408 | } 409 | else 410 | { 411 | // Need immutable array to be accessible in pure code, but case labels are 412 | // currently coerced to the switch condition type (e.g. const(char)[]). 413 | pure @trusted nothrow @nogc asImmutable(scope const(T[])[] items) 414 | { 415 | assert(__ctfe); // only @safe for CTFE 416 | immutable T[][caseLabels.length] result = cast(immutable)(items[]); 417 | return result; 418 | } 419 | static immutable T[][caseLabels.length] cases = asImmutable([caseLabels]); 420 | 421 | // Run-time binary search in a static array of labels. 422 | return __switchSearch!T(cases[], condition); 423 | } 424 | } 425 | 426 | // binary search in sorted string cases, also see `__switch`. 427 | private int __switchSearch(T)(/*in*/ const scope T[][] cases, /*in*/ const scope T[] condition) pure nothrow @safe @nogc 428 | { 429 | size_t low = 0; 430 | size_t high = cases.length; 431 | 432 | do 433 | { 434 | auto mid = (low + high) / 2; 435 | int r = void; 436 | if (condition.length == cases[mid].length) 437 | { 438 | r = __cmp(condition, cases[mid]); 439 | if (r == 0) return cast(int) mid; 440 | } 441 | else 442 | { 443 | // Generates better code than "expr ? 1 : -1" on dmd and gdc, same with ldc 444 | r = ((condition.length > cases[mid].length) << 1) - 1; 445 | } 446 | 447 | if (r > 0) low = mid + 1; 448 | else high = mid; 449 | } 450 | while (low < high); 451 | 452 | // Not found 453 | return -1; 454 | } 455 | 456 | //TODO: Support someday? 457 | extern(C) void _d_throw_exception(Throwable o) 458 | { 459 | assert(false, "Exception throw"); 460 | } 461 | 462 | 463 | // for closures 464 | extern(C) void* _d_allocmemory(size_t sz) { 465 | return malloc(sz).ptr; 466 | } 467 | 468 | ///For POD structures 469 | extern (C) void* _d_allocmemoryT(TypeInfo ti) 470 | { 471 | return malloc(ti.size).ptr; 472 | } 473 | 474 | 475 | class Object 476 | { 477 | /// Convert Object to human readable string 478 | string toString() { return "Object"; } 479 | /// Compute hash function for Object 480 | size_t toHash() @trusted nothrow 481 | { 482 | auto addr = cast(size_t)cast(void*)this; 483 | return addr ^ (addr >>> 4); 484 | } 485 | 486 | /// Compare against another object. NOT IMPLEMENTED! 487 | int opCmp(Object o) { assert(false, "not implemented"); } 488 | /// Check equivalence againt another object 489 | bool opEquals(Object o) { return this is o; } 490 | } 491 | 492 | /// Compare to objects 493 | bool opEquals(Object lhs, Object rhs) 494 | { 495 | // If aliased to the same object or both null => equal 496 | if (lhs is rhs) return true; 497 | 498 | // If either is null => non-equal 499 | if (lhs is null || rhs is null) return false; 500 | 501 | if (!lhs.opEquals(rhs)) return false; 502 | 503 | // If same exact type => one call to method opEquals 504 | if (typeid(lhs) is typeid(rhs) || 505 | !__ctfe && typeid(lhs).opEquals(typeid(rhs))) 506 | /* CTFE doesn't like typeid much. 'is' works, but opEquals doesn't 507 | (issue 7147). But CTFE also guarantees that equal TypeInfos are 508 | always identical. So, no opEquals needed during CTFE. */ 509 | { 510 | return true; 511 | } 512 | 513 | // General case => symmetric calls to method opEquals 514 | return rhs.opEquals(lhs); 515 | } 516 | /************************ 517 | * Returns true if lhs and rhs are equal. 518 | */ 519 | bool opEquals(const Object lhs, const Object rhs) 520 | { 521 | // A hack for the moment. 522 | return opEquals(cast()lhs, cast()rhs); 523 | } 524 | 525 | class TypeInfo 526 | { 527 | override string toString() const @safe nothrow 528 | { 529 | return typeid(this).name; 530 | } 531 | 532 | const(TypeInfo) next()nothrow pure inout @nogc { return null; } 533 | size_t size() nothrow pure const @safe @nogc { return 0; } 534 | 535 | bool equals(in void* p1, in void* p2) const { return p1 == p2; } 536 | 537 | override size_t toHash() @trusted const nothrow 538 | { 539 | return hashOf(this.toString()); 540 | } 541 | 542 | 543 | size_t getHash(scope const void* p) @trusted nothrow const 544 | { 545 | return hashOf(p); 546 | } 547 | 548 | /** 549 | * Return default initializer. If the type should be initialized to all 550 | * zeros, an array with a null ptr and a length equal to the type size will 551 | * be returned. For static arrays, this returns the default initializer for 552 | * a single element of the array, use `tsize` to get the correct size. 553 | */ 554 | const(void)[] initializer() const @trusted nothrow pure 555 | { 556 | return (cast(const(void)*) null)[0 .. typeof(null).sizeof]; 557 | } 558 | 559 | @property uint flags() nothrow pure const @safe @nogc { return 0; } 560 | /// Run the destructor on the object and all its sub-objects 561 | void destroy(void* p) const {} 562 | /// Run the postblit on the object and all its sub-objects 563 | void postblit(void* p) const {} 564 | 565 | @property size_t talign() nothrow pure const { return size; } 566 | } 567 | 568 | class TypeInfo_Class : TypeInfo 569 | { 570 | ubyte[] m_init; /// class static initializer (length gives class size) 571 | string name; /// name of class 572 | void*[] vtbl; // virtual function pointer table 573 | Interface[] interfaces; 574 | TypeInfo_Class base; 575 | void* destructor; 576 | void function(Object) classInvariant; 577 | uint flags; 578 | void* deallocator; 579 | void*[] offTi; 580 | void function(Object) defaultConstructor; 581 | immutable(void)* rtInfo; 582 | 583 | override @property size_t size() nothrow pure const 584 | { return Object.sizeof; } 585 | 586 | override size_t getHash(scope const void* p) @trusted const 587 | { 588 | auto o = *cast(Object*)p; 589 | return o ? o.toHash() : 0; 590 | } 591 | 592 | override bool equals(in void* p1, in void* p2) const 593 | { 594 | Object o1 = *cast(Object*)p1; 595 | Object o2 = *cast(Object*)p2; 596 | 597 | return (o1 is o2) || (o1 && o1.opEquals(o2)); 598 | } 599 | 600 | override const(void)[] initializer() nothrow pure const @safe 601 | { 602 | return m_init; 603 | } 604 | } 605 | 606 | void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct)) 607 | { 608 | import core.internal.destruction : destructRecurse; 609 | 610 | destructRecurse(obj); 611 | 612 | static if (initialize) 613 | { 614 | import core.internal.lifetime : emplaceInitializer; 615 | emplaceInitializer(obj); // emplace T.init 616 | } 617 | } 618 | 619 | private extern (D) nothrow alias void function (Object) fp_t; 620 | private extern (C) void rt_finalize2(void* p, bool det = true, bool resetMemory = true) nothrow 621 | { 622 | auto ppv = cast(void**) p; 623 | if (!p || !*ppv) 624 | return; 625 | 626 | auto pc = cast(TypeInfo_Class*) *ppv; 627 | if (det) 628 | { 629 | auto c = *pc; 630 | do 631 | { 632 | if (c.destructor) 633 | (cast(fp_t) c.destructor)(cast(Object) p); // call destructor 634 | } 635 | while ((c = c.base) !is null); 636 | } 637 | 638 | if (resetMemory) 639 | { 640 | auto w = (*pc).initializer; 641 | p[0 .. w.length] = w[]; 642 | } 643 | *ppv = null; // zero vptr even if `resetMemory` is false 644 | } 645 | extern(C) void _d_callfinalizer(void* p) 646 | { 647 | rt_finalize2(p); 648 | } 649 | 650 | void destroy(bool initialize = true, T)(T obj) if (is(T == class)) 651 | { 652 | static if (__traits(getLinkage, T) == "C++") 653 | { 654 | static if (__traits(hasMember, T, "__xdtor")) 655 | obj.__xdtor(); 656 | 657 | static if (initialize) 658 | { 659 | const initializer = __traits(initSymbol, T); 660 | (cast(void*)obj)[0 .. initializer.length] = initializer[]; 661 | } 662 | } 663 | else 664 | { 665 | // Bypass overloaded opCast 666 | auto ptr = (() @trusted => *cast(void**) &obj)(); 667 | rt_finalize2(ptr, true, initialize); 668 | } 669 | } 670 | void destroy(bool initialize = true, T)(T obj) if (is(T == interface)) 671 | { 672 | static assert(__traits(getLinkage, T) == "D", "Invalid call to destroy() on extern(" ~ __traits(getLinkage, T) ~ ") interface"); 673 | 674 | destroy!initialize(cast(Object)obj); 675 | } 676 | void destroy(bool initialize = true, T)(ref T obj) 677 | if (!is(T == struct) && !is(T == interface) && !is(T == class) && !__traits(isStaticArray, T)) 678 | { 679 | static if (initialize) 680 | obj = T.init; 681 | } 682 | 683 | 684 | class TypeInfo_Pointer : TypeInfo 685 | { 686 | TypeInfo m_next; 687 | 688 | override bool equals(in void* p1, in void* p2) const { return *cast(void**)p1 == *cast(void**)p2; } 689 | override size_t getHash(scope const void* p) @trusted const 690 | { 691 | size_t addr = cast(size_t) *cast(const void**)p; 692 | return addr ^ (addr >> 4); 693 | } 694 | override @property size_t size() nothrow pure const { return (void*).sizeof; } 695 | 696 | override const(void)[] initializer() const @trusted { return (cast(void *)null)[0 .. (void*).sizeof]; } 697 | 698 | override const (TypeInfo) next() const { return m_next; } 699 | } 700 | 701 | class TypeInfo_Array : TypeInfo { 702 | TypeInfo value; 703 | override size_t size() const { return (void[]).sizeof; } 704 | override const(TypeInfo) next() const { return value; } 705 | 706 | override bool equals(in void* p1, in void* p2) const 707 | { 708 | void[] a1 = *cast(void[]*)p1; 709 | void[] a2 = *cast(void[]*)p2; 710 | if (a1.length != a2.length) 711 | return false; 712 | size_t sz = value.size; 713 | for (size_t i = 0; i < a1.length; i++) 714 | { 715 | if (!value.equals(a1.ptr + i * sz, a2.ptr + i * sz)) 716 | return false; 717 | } 718 | return true; 719 | } 720 | override @property size_t talign() nothrow pure const 721 | { 722 | return (void[]).alignof; 723 | } 724 | override const(void)[] initializer() const @trusted { return (cast(void *)null)[0 .. (void[]).sizeof]; } 725 | } 726 | 727 | class TypeInfo_StaticArray : TypeInfo { 728 | TypeInfo value; 729 | size_t len; 730 | override size_t size() const { return value.size * len; } 731 | override const(TypeInfo) next() const { return value; } 732 | 733 | override bool equals(in void* p1, in void* p2) const { 734 | size_t sz = value.size; 735 | 736 | for (size_t u = 0; u < len; u++) 737 | { 738 | if (!value.equals(p1 + u * sz, p2 + u * sz)) 739 | { 740 | return false; 741 | } 742 | } 743 | return true; 744 | } 745 | override @property size_t talign() nothrow pure const 746 | { 747 | return value.talign; 748 | } 749 | 750 | } 751 | 752 | import core.arsd.aa; 753 | alias AARange = core.arsd.aa.Range; 754 | extern (C) 755 | { 756 | // from druntime/src/rt/aaA.d 757 | /* The real type is (non-importable) `rt.aaA.Impl*`; 758 | * the compiler uses `void*` for its prototypes. 759 | */ 760 | private alias AA = void*; 761 | 762 | // size_t _aaLen(in AA aa) pure nothrow @nogc; 763 | private void* _aaGetY(scope AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey) pure nothrow; 764 | private void* _aaGetX(scope AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey, out bool found) ; 765 | // inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t valsz, in void* pkey); 766 | inout(void[]) _aaValues(inout AA aa, const size_t keysz, const size_t valsz, const TypeInfo tiValueArray) ; 767 | inout(void[]) _aaKeys(inout AA aa, const size_t keysz, const TypeInfo tiKeyArray) ; 768 | void* _aaRehash(AA* paa, const scope TypeInfo keyti) ; 769 | void _aaClear(AA aa) ; 770 | 771 | // alias _dg_t = extern(D) int delegate(void*); 772 | // int _aaApply(AA aa, size_t keysize, _dg_t dg); 773 | 774 | // alias _dg2_t = extern(D) int delegate(void*, void*); 775 | // int _aaApply2(AA aa, size_t keysize, _dg2_t dg); 776 | 777 | AARange _aaRange(AA aa) pure nothrow @nogc @safe; 778 | bool _aaRangeEmpty(AARange r) pure @safe @nogc nothrow; 779 | void* _aaRangeFrontKey(AARange r); 780 | void* _aaRangeFrontValue(AARange r) pure @nogc nothrow; 781 | void _aaRangePopFront(ref AARange r) pure @nogc nothrow @safe; 782 | 783 | int _aaEqual(scope const TypeInfo tiRaw, scope const AA aa1, scope const AA aa2); 784 | size_t _aaGetHash(scope const AA* aa, scope const TypeInfo tiRaw) nothrow; 785 | 786 | /* 787 | _d_assocarrayliteralTX marked as pure, because aaLiteral can be called from pure code. 788 | This is a typesystem hole, however this is existing hole. 789 | Early compiler didn't check purity of toHash or postblit functions, if key is a UDT thus 790 | copiler allowed to create AA literal with keys, which have impure unsafe toHash methods. 791 | */ 792 | void* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, void[] values); 793 | } 794 | 795 | private AARange _aaToRange(T: V[K], K, V)(ref T aa) pure nothrow @nogc @safe 796 | { 797 | // ensure we are dealing with a genuine AA. 798 | static if (is(const(V[K]) == const(T))) 799 | alias realAA = aa; 800 | else 801 | const(V[K]) realAA = aa; 802 | return _aaRange(() @trusted { return *cast(AA*)&realAA; } ()); 803 | } 804 | 805 | auto byKey(T : V[K], K, V)(T aa) pure nothrow @nogc @safe 806 | { 807 | import core.internal.traits : substInout; 808 | 809 | static struct Result 810 | { 811 | AARange r; 812 | 813 | pure nothrow @nogc: 814 | @property bool empty() @safe { return _aaRangeEmpty(r); } 815 | @property ref front() @trusted 816 | { 817 | return *cast(substInout!K*) _aaRangeFrontKey(r); 818 | } 819 | void popFront() @safe { _aaRangePopFront(r); } 820 | @property Result save() { return this; } 821 | } 822 | 823 | return Result(_aaToRange(aa)); 824 | } 825 | 826 | /** ditto */ 827 | auto byKey(T : V[K], K, V)(T* aa) pure nothrow @nogc 828 | { 829 | return (*aa).byKey(); 830 | } 831 | 832 | 833 | 834 | auto byValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe 835 | { 836 | import core.internal.traits : substInout; 837 | 838 | static struct Result 839 | { 840 | AARange r; 841 | 842 | pure nothrow @nogc: 843 | @property bool empty() @safe { return _aaRangeEmpty(r); } 844 | @property ref front() @trusted 845 | { 846 | return *cast(substInout!V*) _aaRangeFrontValue(r); 847 | } 848 | void popFront() @safe { _aaRangePopFront(r); } 849 | @property Result save() { return this; } 850 | } 851 | 852 | return Result(_aaToRange(aa)); 853 | } 854 | 855 | /** ditto */ 856 | auto byValue(T : V[K], K, V)(T* aa) pure nothrow @nogc 857 | { 858 | return (*aa).byValue(); 859 | } 860 | 861 | Key[] keys(T : Value[Key], Value, Key)(T aa) @property 862 | { 863 | // ensure we are dealing with a genuine AA. 864 | static if (is(const(Value[Key]) == const(T))) 865 | alias realAA = aa; 866 | else 867 | const(Value[Key]) realAA = aa; 868 | auto res = () @trusted { 869 | auto a = cast(void[])_aaKeys(*cast(inout(AA)*)&realAA, Key.sizeof, typeid(Key[])); 870 | return *cast(Key[]*)&a; 871 | }(); 872 | static if (__traits(hasPostblit, Key)) 873 | _doPostblit(res); 874 | return res; 875 | } 876 | 877 | /** ditto */ 878 | Key[] keys(T : Value[Key], Value, Key)(T *aa) @property 879 | { 880 | return (*aa).keys; 881 | } 882 | 883 | /*********************************** 884 | * Returns a newly allocated dynamic array containing a copy of the values from 885 | * the associative array. 886 | * Params: 887 | * aa = The associative array. 888 | * Returns: 889 | * A dynamic array containing a copy of the values. 890 | */ 891 | Value[] values(T : Value[Key], Value, Key)(T aa) @property 892 | { 893 | // ensure we are dealing with a genuine AA. 894 | static if (is(const(Value[Key]) == const(T))) 895 | alias realAA = aa; 896 | else 897 | const(Value[Key]) realAA = aa; 898 | auto res = () @trusted { 899 | auto a = cast(void[])_aaValues(*cast(inout(AA)*)&realAA, Key.sizeof, Value.sizeof, typeid(Value[])); 900 | return *cast(Value[]*)&a; 901 | }(); 902 | static if (__traits(hasPostblit, Value)) 903 | _doPostblit(res); 904 | return res; 905 | } 906 | 907 | /** ditto */ 908 | Value[] values(T : Value[Key], Value, Key)(T *aa) @property 909 | { 910 | return (*aa).values; 911 | } 912 | inout(V) get(K, V)(inout(V[K]) aa, K key, lazy inout(V) defaultValue) 913 | { 914 | auto p = key in aa; 915 | return p ? *p : defaultValue; 916 | } 917 | 918 | /** ditto */ 919 | inout(V) get(K, V)(inout(V[K])* aa, K key, lazy inout(V) defaultValue) 920 | { 921 | return (*aa).get(key, defaultValue); 922 | } 923 | // Tests whether T can be @safe-ly copied. Use a union to exclude destructor from the test. 924 | private enum bool isSafeCopyable(T) = is(typeof(() @safe { union U { T x; } T *x; auto u = U(*x); })); 925 | 926 | /*********************************** 927 | * Looks up key; if it exists applies the update callable else evaluates the 928 | * create callable and adds it to the associative array 929 | * Params: 930 | * aa = The associative array. 931 | * key = The key. 932 | * create = The callable to apply on create. 933 | * update = The callable to apply on update. 934 | */ 935 | void update(K, V, C, U)(ref V[K] aa, K key, scope C create, scope U update) 936 | if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof(update(aa[K.init])) == void))) 937 | { 938 | bool found; 939 | // if key is @safe-ly copyable, `update` may infer @safe 940 | static if (isSafeCopyable!K) 941 | { 942 | auto p = () @trusted 943 | { 944 | return cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); 945 | } (); 946 | } 947 | else 948 | { 949 | auto p = cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); 950 | } 951 | if (!found) 952 | *p = create(); 953 | else 954 | { 955 | static if (is(typeof(update(*p)) == void)) 956 | update(*p); 957 | else 958 | *p = update(*p); 959 | } 960 | } 961 | 962 | ref V require(K, V)(ref V[K] aa, K key, lazy V value = V.init) 963 | { 964 | bool found; 965 | // if key is @safe-ly copyable, `require` can infer @safe 966 | static if (isSafeCopyable!K) 967 | { 968 | auto p = () @trusted 969 | { 970 | return cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); 971 | } (); 972 | } 973 | else 974 | { 975 | auto p = cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); 976 | } 977 | if (found) 978 | return *p; 979 | else 980 | { 981 | *p = value; // Not `return (*p = value)` since if `=` is overloaded 982 | return *p; // this might not return a ref to the left-hand side. 983 | } 984 | } 985 | 986 | 987 | 988 | /*********************************** 989 | * Removes all remaining keys and values from an associative array. 990 | * Params: 991 | * aa = The associative array. 992 | */ 993 | void clear(Value, Key)(Value[Key] aa) 994 | { 995 | _aaClear(*cast(AA *) &aa); 996 | } 997 | 998 | /** ditto */ 999 | void clear(Value, Key)(Value[Key]* aa) 1000 | { 1001 | _aaClear(*cast(AA *) aa); 1002 | } 1003 | void* aaLiteral(Key, Value)(Key[] keys, Value[] values) @trusted pure 1004 | { 1005 | return _d_assocarrayliteralTX(typeid(Value[Key]), *cast(void[]*)&keys, *cast(void[]*)&values); 1006 | } 1007 | 1008 | alias AssociativeArray(Key, Value) = Value[Key]; 1009 | 1010 | class TypeInfo_AssociativeArray : TypeInfo 1011 | { 1012 | override string toString() const 1013 | { 1014 | return value.toString() ~ "[" ~ key.toString() ~ "]"; 1015 | } 1016 | 1017 | override bool opEquals(Object o) 1018 | { 1019 | if (this is o) 1020 | return true; 1021 | auto c = cast(const TypeInfo_AssociativeArray)o; 1022 | return c && this.key == c.key && 1023 | this.value == c.value; 1024 | } 1025 | 1026 | override bool equals(in void* p1, in void* p2) @trusted const 1027 | { 1028 | return !!_aaEqual(this, *cast(const AA*) p1, *cast(const AA*) p2); 1029 | } 1030 | 1031 | override size_t getHash(scope const void* p) nothrow @trusted const 1032 | { 1033 | return _aaGetHash(cast(AA*)p, this); 1034 | } 1035 | 1036 | // BUG: need to add the rest of the functions 1037 | 1038 | override @property size_t size() nothrow pure const 1039 | { 1040 | return (char[int]).sizeof; 1041 | } 1042 | 1043 | override const(void)[] initializer() const @trusted 1044 | { 1045 | return (cast(void *)null)[0 .. (char[int]).sizeof]; 1046 | } 1047 | 1048 | override @property inout(TypeInfo) next() nothrow pure inout { return value; } 1049 | override @property uint flags() nothrow pure const { return 1; } 1050 | 1051 | 1052 | TypeInfo value; 1053 | TypeInfo key; 1054 | 1055 | override @property size_t talign() nothrow pure const 1056 | { 1057 | return (char[int]).alignof; 1058 | } 1059 | 1060 | version (WithArgTypes) override int argTypes(out TypeInfo arg1, out TypeInfo arg2) 1061 | { 1062 | arg1 = typeid(void*); 1063 | return 0; 1064 | } 1065 | } 1066 | 1067 | 1068 | 1069 | class TypeInfo_Enum : TypeInfo { 1070 | TypeInfo base; 1071 | string name; 1072 | void[] m_init; 1073 | 1074 | override size_t size() const { return base.size; } 1075 | override const(TypeInfo) next() const { return base.next; } 1076 | override bool equals(in void* p1, in void* p2) const { return base.equals(p1, p2); } 1077 | override @property size_t talign() const { return base.talign; } 1078 | override void destroy(void* p) const { return base.destroy(p); } 1079 | override void postblit(void* p) const { return base.postblit(p); } 1080 | 1081 | override const(void)[] initializer() const 1082 | { 1083 | return m_init.length ? m_init : base.initializer(); 1084 | } 1085 | } 1086 | 1087 | extern (C) void[] _d_newarrayU(const scope TypeInfo ti, size_t length) 1088 | { 1089 | return malloc(length * ti.next.size); 1090 | } 1091 | 1092 | extern(C) void[] _d_newarrayT(const TypeInfo ti, size_t length) 1093 | { 1094 | auto arr = _d_newarrayU(ti, length); 1095 | (cast(byte[])arr)[] = 0; 1096 | return arr; 1097 | } 1098 | 1099 | extern(C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) 1100 | { 1101 | auto result = _d_newarrayU(ti, length); 1102 | auto tinext = ti.next; 1103 | auto size = tinext.size; 1104 | auto init = tinext.initializer(); 1105 | switch (init.length) 1106 | { 1107 | foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) 1108 | { 1109 | case T.sizeof: 1110 | if (tinext.talign % T.alignof == 0) 1111 | { 1112 | (cast(T*)result.ptr)[0 .. size * length / T.sizeof] = *cast(T*)init.ptr; 1113 | return result; 1114 | } 1115 | goto default; 1116 | } 1117 | 1118 | default: 1119 | { 1120 | immutable sz = init.length; 1121 | for (size_t u = 0; u < size * length; u += sz) 1122 | { 1123 | memcpy(result.ptr + u, init.ptr, sz); 1124 | } 1125 | return result; 1126 | } 1127 | } 1128 | } 1129 | 1130 | extern (C) void* _d_newitemU(scope const TypeInfo _ti) 1131 | { 1132 | import core.arsd.objectutils; 1133 | auto ti = cast()_ti; 1134 | immutable tiSize = structTypeInfoSize(ti); 1135 | immutable itemSize = ti.size; 1136 | immutable size = itemSize + tiSize; 1137 | auto p = malloc(size); 1138 | 1139 | return p.ptr; 1140 | } 1141 | 1142 | /// ditto 1143 | extern (C) void* _d_newitemT(in TypeInfo _ti) 1144 | { 1145 | auto p = _d_newitemU(_ti); 1146 | memset(p, 0, _ti.size); 1147 | return p; 1148 | } 1149 | 1150 | /// Same as above, for item with non-zero initializer. 1151 | extern (C) void* _d_newitemiT(in TypeInfo _ti) 1152 | { 1153 | auto p = _d_newitemU(_ti); 1154 | auto init = _ti.initializer(); 1155 | assert(init.length <= _ti.size); 1156 | memcpy(p, init.ptr, init.length); 1157 | return p; 1158 | } 1159 | 1160 | 1161 | 1162 | private void[] _d_newarrayOpT(alias op)(const TypeInfo ti, size_t[] dimensions) 1163 | { 1164 | if (dimensions.length == 0) 1165 | return null; 1166 | 1167 | void[] foo(const TypeInfo ti, size_t[] dimensions) 1168 | { 1169 | size_t count = dimensions[0]; 1170 | 1171 | if (dimensions.length == 1) 1172 | { 1173 | auto r = op(ti, count); 1174 | return (*cast(void[]*)(&r))[0..count]; 1175 | } 1176 | void[] p = malloc((void[]).sizeof * count); 1177 | 1178 | foreach (i; 0..count) 1179 | { 1180 | (cast(void[]*)p.ptr)[i] = foo(ti.next, dimensions[1..$]); 1181 | } 1182 | return p[0..count]; 1183 | } 1184 | 1185 | return foo(ti, dimensions); 1186 | } 1187 | 1188 | 1189 | extern (C) void[] _d_newarraymTX(const TypeInfo ti, size_t[] dims) 1190 | { 1191 | if (dims.length == 0) 1192 | return null; 1193 | else 1194 | return _d_newarrayOpT!(_d_newarrayT)(ti, dims); 1195 | } 1196 | 1197 | /// ditto 1198 | extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims) 1199 | { 1200 | if (dims.length == 0) 1201 | return null; 1202 | else 1203 | return _d_newarrayOpT!(_d_newarrayiT)(ti, dims); 1204 | } 1205 | 1206 | 1207 | 1208 | 1209 | AllocatedBlock* getAllocatedBlock(void* ptr) { 1210 | auto block = (cast(AllocatedBlock*) ptr) - 1; 1211 | if(!block.checkChecksum()) 1212 | return null; 1213 | return block; 1214 | } 1215 | 1216 | /++ 1217 | Marks the memory block as OK to append in-place if possible. 1218 | +/ 1219 | void assumeSafeAppend(T)(T[] arr) { 1220 | auto block = getAllocatedBlock(arr.ptr); 1221 | if(block is null) assert(0); 1222 | 1223 | block.used = arr.length; 1224 | } 1225 | 1226 | /++ 1227 | Marks the memory block associated with this array as unique, meaning 1228 | the runtime is allowed to free the old block immediately instead of 1229 | keeping it around for other lingering slices. 1230 | 1231 | In real D, the GC would take care of this but here I have to hack it. 1232 | 1233 | arsd.webasm extension 1234 | +/ 1235 | void assumeUniqueReference(T)(T[] arr) { 1236 | auto block = getAllocatedBlock(arr.ptr); 1237 | if(block is null) assert(0); 1238 | 1239 | block.flags |= AllocatedBlock.Flags.unique; 1240 | } 1241 | 1242 | template _d_arraysetlengthTImpl(Tarr : T[], T) { 1243 | size_t _d_arraysetlengthT(return scope ref Tarr arr, size_t newlength) @trusted { 1244 | auto orig = arr; 1245 | 1246 | if(newlength <= arr.length) { 1247 | arr = arr[0 ..newlength]; 1248 | } else { 1249 | auto ptr = cast(T*) realloc(cast(ubyte[])arr, newlength * T.sizeof); 1250 | arr = ptr[0 .. newlength]; 1251 | if(orig !is null) { 1252 | arr[0 .. orig.length] = orig[]; 1253 | } 1254 | } 1255 | 1256 | return newlength; 1257 | } 1258 | } 1259 | 1260 | extern (C) byte[] _d_arrayappendcTX(const TypeInfo ti, ref byte[] px, size_t n) @trusted { 1261 | auto elemSize = ti.next.size; 1262 | auto newLength = n + px.length; 1263 | auto newSize = newLength * elemSize; 1264 | //import std.stdio; writeln(newSize, " ", newLength); 1265 | ubyte* ptr; 1266 | if(px.ptr is null) 1267 | ptr = malloc(newSize).ptr; 1268 | else // FIXME: anti-stomping by checking length == used 1269 | ptr = realloc(cast(ubyte[])px, newSize).ptr; 1270 | auto ns = ptr[0 .. newSize]; 1271 | auto op = px.ptr; 1272 | auto ol = px.length * elemSize; 1273 | 1274 | foreach(i, b; op[0 .. ol]) 1275 | ns[i] = b; 1276 | 1277 | (cast(size_t *)(&px))[0] = newLength; 1278 | (cast(void **)(&px))[1] = ns.ptr; 1279 | return px; 1280 | } 1281 | 1282 | 1283 | version(inline_concat) 1284 | extern(C) void[] _d_arraycatnTX(const TypeInfo ti, scope byte[][] arrs) @trusted 1285 | { 1286 | auto elemSize = ti.next.size; 1287 | size_t length; 1288 | foreach (b; arrs) 1289 | length += b.length; 1290 | if(!length) 1291 | return null; 1292 | ubyte* ptr = cast(ubyte*)malloc(length * elemSize); 1293 | 1294 | //Copy data 1295 | { 1296 | ubyte* nPtr = ptr; 1297 | foreach(b; arrs) 1298 | { 1299 | byte* bPtr = b.ptr; 1300 | size_t copySize = b.length*elemSize; 1301 | nPtr[0..copySize] = cast(ubyte[])bPtr[0..copySize]; 1302 | nPtr+= copySize; 1303 | } 1304 | } 1305 | return cast(void[])ptr[0..length]; 1306 | } 1307 | 1308 | version(inline_concat) 1309 | extern (C) byte[] _d_arraycatT(const TypeInfo ti, byte[] x, byte[] y) 1310 | { 1311 | import core.arsd.objectutils; 1312 | auto sizeelem = ti.next.size; // array element size 1313 | size_t xlen = x.length * sizeelem; 1314 | size_t ylen = y.length * sizeelem; 1315 | size_t len = xlen + ylen; 1316 | 1317 | if (!len) 1318 | return null; 1319 | 1320 | byte[] p = cast(byte[])malloc(len); 1321 | memcpy(p.ptr, x.ptr, xlen); 1322 | memcpy(p.ptr + xlen, y.ptr, ylen); 1323 | // do postblit processing 1324 | __doPostblit(p.ptr, xlen + ylen, ti.next); 1325 | return p[0 .. x.length + y.length]; 1326 | } 1327 | 1328 | extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) 1329 | { 1330 | // c could encode into from 1 to 4 characters 1331 | char[4] buf = void; 1332 | byte[] appendthis; // passed to appendT 1333 | if (c <= 0x7F) 1334 | { 1335 | buf.ptr[0] = cast(char)c; 1336 | appendthis = (cast(byte *)buf.ptr)[0..1]; 1337 | } 1338 | else if (c <= 0x7FF) 1339 | { 1340 | buf.ptr[0] = cast(char)(0xC0 | (c >> 6)); 1341 | buf.ptr[1] = cast(char)(0x80 | (c & 0x3F)); 1342 | appendthis = (cast(byte *)buf.ptr)[0..2]; 1343 | } 1344 | else if (c <= 0xFFFF) 1345 | { 1346 | buf.ptr[0] = cast(char)(0xE0 | (c >> 12)); 1347 | buf.ptr[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 1348 | buf.ptr[2] = cast(char)(0x80 | (c & 0x3F)); 1349 | appendthis = (cast(byte *)buf.ptr)[0..3]; 1350 | } 1351 | else if (c <= 0x10FFFF) 1352 | { 1353 | buf.ptr[0] = cast(char)(0xF0 | (c >> 18)); 1354 | buf.ptr[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); 1355 | buf.ptr[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 1356 | buf.ptr[3] = cast(char)(0x80 | (c & 0x3F)); 1357 | appendthis = (cast(byte *)buf.ptr)[0..4]; 1358 | } 1359 | else 1360 | assert(false, "Could not append dchar"); // invalid utf character - should we throw an exception instead? 1361 | 1362 | // 1363 | // TODO: This always assumes the array type is shared, because we do not 1364 | // get a typeinfo from the compiler. Assuming shared is the safest option. 1365 | // Once the compiler is fixed, the proper typeinfo should be forwarded. 1366 | // 1367 | return _d_arrayappendT(typeid(shared char[]), x, appendthis); 1368 | } 1369 | 1370 | 1371 | 1372 | 1373 | alias AliasSeq(T...) = T; 1374 | static foreach(type; AliasSeq!(byte, char, dchar, double, float, int, long, short, ubyte, uint, ulong, ushort, void, wchar)) { 1375 | mixin(q{ 1376 | class TypeInfo_}~type.mangleof~q{ : TypeInfo { 1377 | override string toString() const pure nothrow @safe { return type.stringof; } 1378 | override size_t size() const { return type.sizeof; } 1379 | override @property size_t talign() const pure nothrow 1380 | { 1381 | return type.alignof; 1382 | } 1383 | 1384 | override bool equals(in void* a, in void* b) const { 1385 | static if(is(type == void)) 1386 | return false; 1387 | else 1388 | return (*(cast(type*) a) == (*(cast(type*) b))); 1389 | } 1390 | static if(!is(type == void)) 1391 | override size_t getHash(scope const void* p) @trusted const nothrow 1392 | { 1393 | return hashOf(*cast(const type *)p); 1394 | } 1395 | override const(void)[] initializer() pure nothrow @trusted const 1396 | { 1397 | static if(__traits(isZeroInit, type)) 1398 | return (cast(void*)null)[0 .. type.sizeof]; 1399 | else 1400 | { 1401 | static immutable type[1] c; 1402 | return c; 1403 | } 1404 | } 1405 | } 1406 | class TypeInfo_A}~type.mangleof~q{ : TypeInfo_Array { 1407 | override string toString() const { return (type[]).stringof; } 1408 | override const(TypeInfo) next() const { return cast(inout)typeid(type); } 1409 | override size_t getHash(scope const void* p) @trusted const nothrow 1410 | { 1411 | return hashOf(*cast(const type[]*) p); 1412 | } 1413 | 1414 | override bool equals(in void* av, in void* bv) const { 1415 | type[] a = *(cast(type[]*) av); 1416 | type[] b = *(cast(type[]*) bv); 1417 | 1418 | static if(is(type == void)) 1419 | return false; 1420 | else { 1421 | foreach(idx, item; a) 1422 | if(item != b[idx]) 1423 | return false; 1424 | return true; 1425 | } 1426 | } 1427 | } 1428 | }); 1429 | } 1430 | // typeof(null) 1431 | class TypeInfo_n : TypeInfo 1432 | { 1433 | const: pure: @nogc: nothrow: @safe: 1434 | override string toString() { return "typeof(null)"; } 1435 | override size_t getHash(scope const void*) { return 0; } 1436 | override bool equals(in void*, in void*) { return true; } 1437 | override @property size_t size() { return typeof(null).sizeof; } 1438 | override const(void)[] initializer() @trusted { return (cast(void *)null)[0 .. size_t.sizeof]; } 1439 | } 1440 | 1441 | struct Interface { 1442 | TypeInfo_Class classinfo; 1443 | void*[] vtbl; 1444 | size_t offset; 1445 | } 1446 | 1447 | /** 1448 | * Array of pairs giving the offset and type information for each 1449 | * member in an aggregate. 1450 | */ 1451 | struct OffsetTypeInfo 1452 | { 1453 | size_t offset; /// Offset of member from start of object 1454 | TypeInfo ti; /// TypeInfo for this member 1455 | } 1456 | 1457 | class TypeInfo_Axa : TypeInfo_Aa { 1458 | 1459 | } 1460 | class TypeInfo_Aya : TypeInfo_Aa { 1461 | 1462 | } 1463 | 1464 | class TypeInfo_Function : TypeInfo 1465 | { 1466 | override string toString() const pure @trusted{return deco;} 1467 | override bool opEquals(Object o) 1468 | { 1469 | if (this is o) 1470 | return true; 1471 | auto c = cast(const TypeInfo_Function)o; 1472 | return c && this.deco == c.deco; 1473 | } 1474 | 1475 | // BUG: need to add the rest of the functions 1476 | 1477 | override @property size_t size() nothrow pure const 1478 | { 1479 | return 0; // no size for functions 1480 | } 1481 | override const(void)[] initializer() const @safe{return null;} 1482 | TypeInfo _next; 1483 | override const(TypeInfo) next()nothrow pure inout @nogc { return _next; } 1484 | 1485 | /** 1486 | * Mangled function type string 1487 | */ 1488 | string deco; 1489 | } 1490 | 1491 | 1492 | class TypeInfo_Delegate : TypeInfo { 1493 | TypeInfo next; 1494 | string deco; 1495 | override @property size_t size() nothrow pure const 1496 | { 1497 | alias dg = int delegate(); 1498 | return dg.sizeof; 1499 | } 1500 | override bool equals(in void* p1, in void* p2) const 1501 | { 1502 | auto dg1 = *cast(void delegate()*)p1; 1503 | auto dg2 = *cast(void delegate()*)p2; 1504 | return dg1 == dg2; 1505 | } 1506 | override const(void)[] initializer() const @trusted 1507 | { 1508 | return (cast(void *)null)[0 .. (int delegate()).sizeof]; 1509 | } 1510 | override size_t getHash(scope const void* p) @trusted const 1511 | { 1512 | return hashOf(*cast(const void delegate() *)p); 1513 | } 1514 | 1515 | override @property size_t talign() nothrow pure const 1516 | { 1517 | alias dg = int delegate(); 1518 | return dg.alignof; 1519 | } 1520 | } 1521 | 1522 | 1523 | //Directly copied from LWDR source. 1524 | class TypeInfo_Interface : TypeInfo 1525 | { 1526 | TypeInfo_Class info; 1527 | 1528 | override bool equals(in void* p1, in void* p2) const 1529 | { 1530 | Interface* pi = **cast(Interface ***)*cast(void**)p1; 1531 | Object o1 = cast(Object)(*cast(void**)p1 - pi.offset); 1532 | pi = **cast(Interface ***)*cast(void**)p2; 1533 | Object o2 = cast(Object)(*cast(void**)p2 - pi.offset); 1534 | 1535 | return o1 == o2 || (o1 && o1.opCmp(o2) == 0); 1536 | } 1537 | override size_t getHash(scope const void* p) @trusted const 1538 | { 1539 | if (!*cast(void**)p) 1540 | { 1541 | return 0; 1542 | } 1543 | Interface* pi = **cast(Interface ***)*cast(void**)p; 1544 | Object o = cast(Object)(*cast(void**)p - pi.offset); 1545 | assert(o); 1546 | return o.toHash(); 1547 | } 1548 | 1549 | override const(void)[] initializer() const @trusted 1550 | { 1551 | return (cast(void *)null)[0 .. Object.sizeof]; 1552 | } 1553 | 1554 | override @property size_t size() nothrow pure const 1555 | { 1556 | return Object.sizeof; 1557 | } 1558 | } 1559 | 1560 | class TypeInfo_Const : TypeInfo { 1561 | override size_t getHash(scope const(void*) p) @trusted const nothrow { return base.getHash(p); } 1562 | TypeInfo base; 1563 | override size_t size() const { return base.size; } 1564 | override const(TypeInfo) next() const { return base.next; } 1565 | override const(void)[] initializer() nothrow pure const{return base.initializer();} 1566 | override @property size_t talign() nothrow pure const { return base.talign; } 1567 | override bool equals(in void* p1, in void* p2) const { return base.equals(p1, p2); } 1568 | } 1569 | 1570 | 1571 | ///For some reason, getHash for interfaces wanted that 1572 | pragma(mangle, "_D9invariant12_d_invariantFC6ObjectZv") 1573 | extern(D) void _d_invariant(Object o) 1574 | { 1575 | TypeInfo_Class c; 1576 | 1577 | //printf("__d_invariant(%p)\n", o); 1578 | 1579 | // BUG: needs to be filename/line of caller, not library routine 1580 | assert(o !is null); // just do null check, not invariant check 1581 | 1582 | c = typeid(o); 1583 | do 1584 | { 1585 | if (c.classInvariant) 1586 | { 1587 | (*c.classInvariant)(o); 1588 | } 1589 | c = c.base; 1590 | } while (c); 1591 | } 1592 | 1593 | /+ 1594 | class TypeInfo_Immutable : TypeInfo { 1595 | size_t getHash(in void*) nothrow { return 0; } 1596 | TypeInfo base; 1597 | } 1598 | +/ 1599 | class TypeInfo_Invariant : TypeInfo { 1600 | TypeInfo base; 1601 | override size_t getHash(scope const (void*) p) @trusted const nothrow { return base.getHash(p); } 1602 | override size_t size() const { return base.size; } 1603 | override const(TypeInfo) next() const { return base; } 1604 | } 1605 | class TypeInfo_Shared : TypeInfo { 1606 | override size_t getHash(scope const (void*) p) @trusted const nothrow { return base.getHash(p); } 1607 | TypeInfo base; 1608 | override size_t size() const { return base.size; } 1609 | override const(TypeInfo) next() const { return base; } 1610 | } 1611 | class TypeInfo_Inout : TypeInfo { 1612 | override size_t getHash(scope const (void*) p) @trusted const nothrow { return base.getHash(p); } 1613 | TypeInfo base; 1614 | override size_t size() const { return base.size; } 1615 | override const(TypeInfo) next() const { return base; } 1616 | } 1617 | 1618 | class TypeInfo_Struct : TypeInfo { 1619 | string name; 1620 | void[] m_init; 1621 | @safe pure nothrow 1622 | { 1623 | size_t function(in void*) xtoHash; 1624 | bool function(in void*, in void*) xopEquals; 1625 | int function(in void*, in void*) xopCmp; 1626 | string function(in void*) xtoString; 1627 | } 1628 | uint m_flags; 1629 | union { 1630 | void function(void*) xdtor; 1631 | void function(void*, const TypeInfo_Struct) xdtorti; 1632 | } 1633 | void function(void*) xpostblit; 1634 | uint align_; 1635 | immutable(void)* rtinfo; 1636 | // private struct _memberFunc //? Is it necessary 1637 | // { 1638 | // union 1639 | // { 1640 | // struct // delegate 1641 | // { 1642 | // const void* ptr; 1643 | // const void* funcptr; 1644 | // } 1645 | // @safe pure nothrow 1646 | // { 1647 | // bool delegate(in void*) xopEquals; 1648 | // int delegate(in void*) xopCmp; 1649 | // } 1650 | // } 1651 | // } 1652 | 1653 | enum StructFlags : uint 1654 | { 1655 | hasPointers = 0x1, 1656 | isDynamicType = 0x2, // built at runtime, needs type info in xdtor 1657 | } 1658 | override size_t size() const { return m_init.length; } 1659 | override @property uint flags() nothrow pure const @safe @nogc { return m_flags; } 1660 | 1661 | override size_t toHash() const 1662 | { 1663 | return hashOf(this.name); 1664 | } 1665 | override bool opEquals(Object o) 1666 | { 1667 | if (this is o) 1668 | return true; 1669 | auto s = cast(const TypeInfo_Struct)o; 1670 | return s && this.name == s.name; 1671 | } 1672 | override size_t getHash(scope const void* p) @trusted pure nothrow const 1673 | { 1674 | assert(p); 1675 | if (xtoHash) 1676 | { 1677 | return (*xtoHash)(p); 1678 | } 1679 | else 1680 | { 1681 | return hashOf(p[0 .. initializer().length]); 1682 | } 1683 | } 1684 | 1685 | 1686 | override bool equals(in void* p1, in void* p2) @trusted const 1687 | { 1688 | if (!p1 || !p2) 1689 | return false; 1690 | else if (xopEquals) 1691 | return (*xopEquals)(p1, p2); 1692 | else if (p1 == p2) 1693 | return true; 1694 | else 1695 | // BUG: relies on the GC not moving objects 1696 | return memcmp(p1, p2, m_init.length) == 0; 1697 | } 1698 | override @property size_t talign() nothrow pure const { return align_; } 1699 | final override void destroy(void* p) const 1700 | { 1701 | if (xdtor) 1702 | { 1703 | if (m_flags & StructFlags.isDynamicType) 1704 | (*xdtorti)(p, this); 1705 | else 1706 | (*xdtor)(p); 1707 | } 1708 | } 1709 | 1710 | override void postblit(void* p) const 1711 | { 1712 | if (xpostblit) 1713 | (*xpostblit)(p); 1714 | } 1715 | 1716 | override const(void)[] initializer() nothrow pure const @safe 1717 | { 1718 | return m_init; 1719 | } 1720 | 1721 | } 1722 | 1723 | extern(C) bool _xopCmp(in void*, in void*) { return false; } 1724 | 1725 | // } 1726 | 1727 | void __ArrayDtor(T)(scope T[] a) 1728 | { 1729 | foreach_reverse (ref T e; a) 1730 | e.__xdtor(); 1731 | } 1732 | 1733 | TTo[] __ArrayCast(TFrom, TTo)(return scope TFrom[] from) nothrow 1734 | { 1735 | const fromSize = from.length * TFrom.sizeof; 1736 | const toLength = fromSize / TTo.sizeof; 1737 | 1738 | if ((fromSize % TTo.sizeof) != 0) 1739 | { 1740 | //onArrayCastError(TFrom.stringof, fromSize, TTo.stringof, toLength * TTo.sizeof); 1741 | import arsd.webassembly; 1742 | abort(); 1743 | } 1744 | 1745 | struct Array 1746 | { 1747 | size_t length; 1748 | void* ptr; 1749 | } 1750 | auto a = cast(Array*)&from; 1751 | a.length = toLength; // jam new length 1752 | return *cast(TTo[]*)a; 1753 | } 1754 | 1755 | extern (C) void[] _d_arrayappendT(const TypeInfo ti, ref byte[] x, byte[] y) 1756 | { 1757 | auto length = x.length; 1758 | auto tinext = ti.next; 1759 | auto sizeelem = tinext./*t*/size; // array element size 1760 | _d_arrayappendcTX(ti, x, y.length); 1761 | memcpy(x.ptr + length * sizeelem, y.ptr, y.length * sizeelem); 1762 | 1763 | // do postblit 1764 | //__doPostblit(x.ptr + length * sizeelem, y.length * sizeelem, tinext); 1765 | return x; 1766 | } 1767 | 1768 | extern (C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) 1769 | { 1770 | debug(adi) printf("_adEq2(a1.length = %d, a2.length = %d)\n", a1.length, a2. length); 1771 | if (a1.length != a2.length) 1772 | return 0; // not equal 1773 | if (!ti.equals(&a1, &a2)) 1774 | return 0; 1775 | return 1; 1776 | } 1777 | 1778 | V[K] dup(T : V[K], K, V)(T aa) 1779 | { 1780 | //pragma(msg, "K = ", K, ", V = ", V); 1781 | 1782 | // Bug10720 - check whether V is copyable 1783 | static assert(is(typeof({ V v = aa[K.init]; })), 1784 | "cannot call " ~ T.stringof ~ ".dup because " ~ V.stringof ~ " is not copyable"); 1785 | 1786 | V[K] result; 1787 | 1788 | //foreach (k, ref v; aa) 1789 | // result[k] = v; // Bug13701 - won't work if V is not mutable 1790 | 1791 | ref V duplicateElem(ref K k, ref const V v) @trusted pure nothrow 1792 | { 1793 | void* pv = _aaGetY(cast(AA*)&result, typeid(V[K]), V.sizeof, &k); 1794 | memcpy(pv, &v, V.sizeof); 1795 | return *cast(V*)pv; 1796 | } 1797 | 1798 | foreach (k, ref v; aa) 1799 | { 1800 | static if (!__traits(hasPostblit, V)) 1801 | duplicateElem(k, v); 1802 | else static if (__traits(isStaticArray, V)) 1803 | _doPostblit(duplicateElem(k, v)[]); 1804 | else static if (!is(typeof(v.__xpostblit())) && is(immutable V == immutable UV, UV)) 1805 | (() @trusted => *cast(UV*) &duplicateElem(k, v))().__xpostblit(); 1806 | else 1807 | duplicateElem(k, v).__xpostblit(); 1808 | } 1809 | 1810 | return result; 1811 | } 1812 | 1813 | /** ditto */ 1814 | V[K] dup(T : V[K], K, V)(T* aa) 1815 | { 1816 | return (*aa).dup; 1817 | } 1818 | 1819 | T[] dup(T)(scope T[] array) pure nothrow @trusted if (__traits(isPOD, T) && !is(const(T) : T)) 1820 | { 1821 | T[] result; 1822 | foreach(ref e; array) { 1823 | result ~= e; 1824 | } 1825 | return result; 1826 | } 1827 | 1828 | 1829 | 1830 | T[] dup(T)(scope const(T)[] array) pure nothrow @trusted if (__traits(isPOD, T)) 1831 | { 1832 | T[] result; 1833 | foreach(ref e; array) { 1834 | result ~= e; 1835 | } 1836 | return result; 1837 | } 1838 | 1839 | immutable(T)[] idup(T)(scope const(T)[] array) pure nothrow @trusted 1840 | { 1841 | immutable(T)[] result; 1842 | foreach(ref e; array) { 1843 | result ~= e; 1844 | } 1845 | return result; 1846 | } 1847 | 1848 | class Error { this(string msg) {} } 1849 | class Throwable : Object 1850 | { 1851 | interface TraceInfo 1852 | { 1853 | int opApply(scope int delegate(ref const(char[]))) const; 1854 | int opApply(scope int delegate(ref size_t, ref const(char[]))) const; 1855 | string toString() const; 1856 | } 1857 | 1858 | string msg; /// A message describing the error. 1859 | 1860 | /** 1861 | * The _file name of the D source code corresponding with 1862 | * where the error was thrown from. 1863 | */ 1864 | string file; 1865 | /** 1866 | * The _line number of the D source code corresponding with 1867 | * where the error was thrown from. 1868 | */ 1869 | size_t line; 1870 | 1871 | /** 1872 | * The stack trace of where the error happened. This is an opaque object 1873 | * that can either be converted to $(D string), or iterated over with $(D 1874 | * foreach) to extract the items in the stack trace (as strings). 1875 | */ 1876 | TraceInfo info; 1877 | 1878 | /** 1879 | * A reference to the _next error in the list. This is used when a new 1880 | * $(D Throwable) is thrown from inside a $(D catch) block. The originally 1881 | * caught $(D Exception) will be chained to the new $(D Throwable) via this 1882 | * field. 1883 | */ 1884 | private Throwable nextInChain; 1885 | 1886 | private uint _refcount; // 0 : allocated by GC 1887 | // 1 : allocated by _d_newThrowable() 1888 | // 2.. : reference count + 1 1889 | 1890 | /** 1891 | * Returns: 1892 | * A reference to the _next error in the list. This is used when a new 1893 | * $(D Throwable) is thrown from inside a $(D catch) block. The originally 1894 | * caught $(D Exception) will be chained to the new $(D Throwable) via this 1895 | * field. 1896 | */ 1897 | @property inout(Throwable) next() @safe inout return scope pure nothrow @nogc { return nextInChain; } 1898 | 1899 | /** 1900 | * Replace next in chain with `tail`. 1901 | * Use `chainTogether` instead if at all possible. 1902 | */ 1903 | @property void next(Throwable tail) @safe scope pure nothrow @nogc{} 1904 | 1905 | /** 1906 | * Returns: 1907 | * mutable reference to the reference count, which is 1908 | * 0 - allocated by the GC, 1 - allocated by _d_newThrowable(), 1909 | * and >=2 which is the reference count + 1 1910 | * Note: 1911 | * Marked as `@system` to discourage casual use of it. 1912 | */ 1913 | @system @nogc final pure nothrow ref uint refcount() return { return _refcount; } 1914 | 1915 | /** 1916 | * Loop over the chain of Throwables. 1917 | */ 1918 | int opApply(scope int delegate(Throwable) dg) 1919 | { 1920 | int result = 0; 1921 | for (Throwable t = this; t; t = t.nextInChain) 1922 | { 1923 | result = dg(t); 1924 | if (result) 1925 | break; 1926 | } 1927 | return result; 1928 | } 1929 | 1930 | /** 1931 | * Append `e2` to chain of exceptions that starts with `e1`. 1932 | * Params: 1933 | * e1 = start of chain (can be null) 1934 | * e2 = second part of chain (can be null) 1935 | * Returns: 1936 | * Throwable that is at the start of the chain; null if both `e1` and `e2` are null 1937 | */ 1938 | static @system @nogc pure nothrow Throwable chainTogether(return scope Throwable e1, return scope Throwable e2) 1939 | { 1940 | if (!e1) 1941 | return e2; 1942 | if (!e2) 1943 | return e1; 1944 | if (e2.refcount()) 1945 | ++e2.refcount(); 1946 | 1947 | for (auto e = e1; 1; e = e.nextInChain) 1948 | { 1949 | if (!e.nextInChain) 1950 | { 1951 | e.nextInChain = e2; 1952 | break; 1953 | } 1954 | } 1955 | return e1; 1956 | } 1957 | 1958 | @nogc @safe pure nothrow this(string msg, Throwable nextInChain = null) 1959 | { 1960 | this.msg = msg; 1961 | this.nextInChain = nextInChain; 1962 | if (nextInChain && nextInChain._refcount) 1963 | ++nextInChain._refcount; 1964 | //this.info = _d_traceContext(); 1965 | } 1966 | 1967 | @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable nextInChain = null) 1968 | { 1969 | this(msg, nextInChain); 1970 | this.file = file; 1971 | this.line = line; 1972 | //this.info = _d_traceContext(); 1973 | } 1974 | 1975 | @trusted nothrow ~this(){} 1976 | 1977 | /** 1978 | * Overrides $(D Object.toString) and returns the error message. 1979 | * Internally this forwards to the $(D toString) overload that 1980 | * takes a $(D_PARAM sink) delegate. 1981 | */ 1982 | override string toString() 1983 | { 1984 | string s; 1985 | toString((in buf) { s ~= buf; }); 1986 | return s; 1987 | } 1988 | 1989 | /** 1990 | * The Throwable hierarchy uses a toString overload that takes a 1991 | * $(D_PARAM _sink) delegate to avoid GC allocations, which cannot be 1992 | * performed in certain error situations. Override this $(D 1993 | * toString) method to customize the error message. 1994 | */ 1995 | void toString(scope void delegate(in char[]) sink) const{} 1996 | 1997 | /** 1998 | * Get the message describing the error. 1999 | * Base behavior is to return the `Throwable.msg` field. 2000 | * Override to return some other error message. 2001 | * 2002 | * Returns: 2003 | * Error message 2004 | */ 2005 | const(char)[] message() const 2006 | { 2007 | return this.msg; 2008 | } 2009 | } 2010 | class Exception : Throwable 2011 | { 2012 | 2013 | /** 2014 | * Creates a new instance of Exception. The nextInChain parameter is used 2015 | * internally and should always be $(D null) when passed by user code. 2016 | * This constructor does not automatically throw the newly-created 2017 | * Exception; the $(D throw) statement should be used for that purpose. 2018 | */ 2019 | @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) 2020 | { 2021 | super(msg, file, line, nextInChain); 2022 | } 2023 | 2024 | @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 2025 | { 2026 | super(msg, file, line, nextInChain); 2027 | } 2028 | } 2029 | 2030 | 2031 | import core.internal.hash; 2032 | -------------------------------------------------------------------------------- /arsd-webassembly/std/random.d: -------------------------------------------------------------------------------- 1 | module std.random; 2 | 3 | import arsd.webassembly; 4 | 5 | int uniform(int low, int high) { 6 | int max = high - low; 7 | return low + eval!int(q{ return Math.floor(Math.random() * $0); }, max); 8 | } 9 | -------------------------------------------------------------------------------- /arsd-webassembly/std/stdio.d: -------------------------------------------------------------------------------- 1 | module std.stdio; 2 | 3 | import arsd.webassembly; 4 | 5 | void writeln(T...)(T t) { 6 | eval(q{ 7 | var str = ""; 8 | for(var i = 0; i < arguments.length; i++) 9 | str += arguments[i]; 10 | 11 | str += "\n"; 12 | 13 | var txt = document.createTextNode(str); 14 | var fd = document.getElementById("stdout"); 15 | fd.appendChild(txt); 16 | }, t); 17 | } -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | cmd /c "ldc2 -i=. --d-version=CarelessAlocation -i=std -Iarsd-webassembly/ -L-allow-undefined -ofserver/omg.wasm -mtriple=wasm32-unknown-unknown-wasm arsd-webassembly/core/arsd/aa arsd-webassembly/core/arsd/objectutils arsd-webassembly/core/internal/utf arsd-webassembly/core/arsd/utf_decoding hello arsd-webassembly/object.d" -------------------------------------------------------------------------------- /fail.d: -------------------------------------------------------------------------------- 1 | just_seeing_compile_error_in_browser 2 | -------------------------------------------------------------------------------- /hello.d: -------------------------------------------------------------------------------- 1 | // ldc2 -i=. -i=std -Iarsd-webassembly/ -L-allow-undefined -ofserver/omg.wasm -mtriple=wasm32-unknown-unknown-wasm omg arsd-webassembly/object.d 2 | 3 | import arsd.webassembly; 4 | 5 | class A { 6 | int a() { return 123; } 7 | } 8 | 9 | class B : A { 10 | int val; 11 | override int a() { return 455 + val; } 12 | } 13 | 14 | import std.stdio; 15 | 16 | void main() { 17 | B b = new B; 18 | b.val = 5; 19 | A a = b; 20 | 21 | int num = eval!int(q{ console.log("hi " + $1 + ", " + $0); this.omg = "yay"; return 52; }, a.a(), "hello world"); 22 | eval(q{ console.log("asdasd " + this.omg + " " + $0); }, num); 23 | 24 | 25 | NativeHandle body = eval!NativeHandle("return document.body"); 26 | body.methods.insertAdjacentHTML!void("beforeend", "hello world"); 27 | eval(`console.log($0)`, body.properties.innerHTML!string); 28 | 29 | writeln("writeln!!!"); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /server/Makefile: -------------------------------------------------------------------------------- 1 | # This assumes you have my dmdi script but basically you just bring down the arsd lib into here then dmd -i and you get the same result. 2 | all: 3 | dmdi serve.d -version=embedded_httpd 4 | -------------------------------------------------------------------------------- /server/serve.d: -------------------------------------------------------------------------------- 1 | import arsd.cgi; 2 | 3 | import std.file; 4 | import std.string; 5 | import std.process; 6 | 7 | // -Wl,--export=__heap_base 8 | 9 | // https://github.com/skoppe/wasm-sourcemaps 10 | 11 | void handler(Cgi cgi) { 12 | 13 | if(cgi.pathInfo == "/webassembly-core.js") { 14 | cgi.setResponseContentType("text/javascript"); 15 | cgi.write(readText("webassembly-core.js"), true); 16 | return; 17 | } 18 | 19 | 20 | // lol trivial validation 21 | size_t dot; 22 | { 23 | if(cgi.pathInfo.length > 32) return; 24 | foreach(idx, ch; cgi.pathInfo[1 .. $]) { 25 | if(!( 26 | (ch >= '0' && ch <= '9') || 27 | (ch >= 'A' && ch <= 'Z') || 28 | (ch >= 'a' && ch <= 'z') || 29 | ch == '_' || 30 | ch == '.' 31 | )) return; 32 | if(ch == '.') { 33 | if(idx == 0) return; 34 | if(dot) return; 35 | dot = idx; 36 | } 37 | } 38 | } 39 | 40 | auto path = cgi.pathInfo.length == 0 ? "" : cgi.pathInfo[1 .. $]; 41 | 42 | if(dot && path[dot .. $] == ".wasm") { 43 | cgi.setResponseContentType("application/wasm"); 44 | cgi.write(read("../" ~ path), true); 45 | return; 46 | 47 | } 48 | 49 | if(path.length == 0) { 50 | // index 51 | string html = "Webassembly in DTry it yourself

Rules:

  1. Small files only
  2. Copy/paste your code locally because I don't save it
  3. Most things won't work
  4. If two users try this simultaneously the race condition might bite you.
  5. Tip: use the export keyword on functions you want visible by javascript!
58 |
59 | Paste your D source code here:
60 | 61 |
62 |
63 | `; 64 | cgi.write(html, true); 65 | return; 66 | } 67 | 68 | if(dot && path[dot .. $] == ".d") { 69 | cgi.setResponseContentType("text/plain"); 70 | cgi.write(readText("../" ~ path), true); 71 | return; 72 | 73 | } 74 | 75 | if(dot) 76 | return; 77 | 78 | if(path == "usertemp") { 79 | if("source" in cgi.post) 80 | std.file.write("../usertemp.d", cgi.post["source"]); 81 | } 82 | 83 | // otherwise, compile and serve! 84 | if(!exists("../" ~ path ~ ".d")) { 85 | cgi.write("404"); 86 | return; 87 | } 88 | 89 | if(!exists("../" ~ path ~ ".wasm") || cgi.requestMethod == Cgi.RequestMethod.POST) { 90 | auto res = executeShell("timeout -k 1s 1s ldc2 --revert=dtorfields --fvisibility=hidden -i=. -i=core -i=std -Iarsd-webassembly/ -L-allow-undefined -of"~path~".wasm -mtriple=wasm32-unknown-unknown-wasm "~path~".d arsd-webassembly/object.d", 91 | null, 92 | Config.none, 93 | 64_000, 94 | ".." 95 | ); 96 | if(res.status == 0) { 97 | // yay 98 | } else { 99 | cgi.setResponseContentType("text/plain"); 100 | cgi.write(res.output); 101 | return; 102 | } 103 | } 104 | 105 | cgi.write(readText("webassembly-skeleton.html").replace("omg", path), true); 106 | 107 | } 108 | 109 | mixin GenericMain!handler; 110 | -------------------------------------------------------------------------------- /server/webassembly-core.js: -------------------------------------------------------------------------------- 1 | 2 | // can webassembly's main be async? 3 | // I might be able to make a semaphore out of 4 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait 5 | 6 | // stores { object: o, refcount: n } 7 | var bridgeObjects = [{}]; // the first one is a null object; ignored 8 | // placeholder to be filled in by the loader 9 | var memory; 10 | 11 | var printBlockDebugInfo; 12 | function memdump(address, length) { 13 | var a = new Uint32Array(memory.buffer, address, length); 14 | console.log(a); 15 | } 16 | 17 | function meminfo() { 18 | document.getElementById("stdout").innerHTML = ""; 19 | printBlockDebugInfo(0); 20 | } 21 | 22 | var bridge_malloc; 23 | 24 | var dModules = {}; 25 | 26 | var exports; 27 | 28 | 29 | var savedFunctions = {}; 30 | 31 | var importObject = { 32 | env: { 33 | acquire: function(returnType, modlen, modptr, javascriptCodeStringLength, javascriptCodeStringPointer, argsLength, argsPtr) { 34 | var td = new TextDecoder(); 35 | var md = td.decode(new Uint8Array(memory.buffer, modptr, modlen)); 36 | var s = td.decode(new Uint8Array(memory.buffer, javascriptCodeStringPointer, javascriptCodeStringLength)); 37 | 38 | var jsArgs = []; 39 | var argIdx = 0; 40 | 41 | var jsArgsNames = ""; 42 | 43 | var a = new Uint32Array(memory.buffer, argsPtr, argsLength * 3); 44 | var aidx = 0; 45 | 46 | for(var argIdx = 0; argIdx < argsLength; argIdx++) { 47 | var type = a[aidx]; 48 | aidx++; 49 | var ptr = a[aidx]; 50 | aidx++; 51 | var length = a[aidx]; 52 | aidx++; 53 | 54 | if(jsArgsNames.length) 55 | jsArgsNames += ", "; 56 | jsArgsNames += "$" + argIdx; 57 | 58 | var value; 59 | 60 | switch(type) { 61 | case 0: 62 | // an integer was casted to the pointer 63 | if(ptr & 0x80000000) 64 | value = - (~ptr + 1); // signed 2's complement 65 | else 66 | value = ptr; 67 | break; 68 | case 1: 69 | // pointer+length is a string 70 | value = td.decode(new Uint8Array(memory.buffer, ptr, length)); 71 | break; 72 | case 2: 73 | // a handle 74 | value = bridgeObjects[ptr].object; 75 | break; 76 | case 3: 77 | // float passed by ref cuz idk how else to reinterpret cast in js 78 | value = (new Float32Array(memory.buffer, ptr, 1))[0]; 79 | break; 80 | case 4: 81 | // float passed by ref cuz idk how else to reinterpret cast in js 82 | value = (new Uint8Array(memory.buffer, ptr, length)); 83 | break; 84 | /* 85 | case 5: 86 | // a pointer to a delegate 87 | let p1 = a[ptr]; 88 | let p2 = a[ptr + 1]; 89 | value = function() 90 | break; 91 | */ 92 | } 93 | 94 | jsArgs.push(value); 95 | } 96 | 97 | ///* 98 | var func = savedFunctions[s]; 99 | if(!func) { 100 | func = new Function(jsArgsNames, s); 101 | savedFunctions[s] = func; 102 | } 103 | //*/ 104 | //var func = new Function(jsArgsNames, s); 105 | var ret = func.apply(dModules[md] ? dModules[md] : (dModules[md] = {}), jsArgs); 106 | 107 | switch(returnType) { 108 | case 0: // void 109 | return 0; 110 | case 1: 111 | // int 112 | return ret; 113 | case 2: 114 | // float 115 | var view = new Float32Array(memory.buffer, 0, 1); 116 | view[0] = ret; 117 | return 0; 118 | case 3: 119 | // boxed object 120 | var handle = bridgeObjects.length; 121 | bridgeObjects.push({ refcount: 1, object: ret }); 122 | return handle; 123 | case 4: 124 | // ubyte[] into given buffer 125 | case 5: 126 | // malloc'd ubyte[] 127 | case 6: 128 | // string into given buffer 129 | case 7: 130 | // malloc'd string. it puts the length as an int before the string, then returns the pointer. 131 | var te = new TextEncoder(); 132 | var s = te.encode(ret); 133 | var ptr = bridge_malloc(s.byteLength + 4); 134 | var view = new Uint32Array(memory.buffer, ptr, 1); 135 | view[0] = s.byteLength; 136 | var view2 = new Uint8Array(memory.buffer, ptr + 4, s.length); 137 | view2.set(s); 138 | return ptr; 139 | case 8: 140 | // return the function itself, so box it up but do not actually call it 141 | } 142 | return -1; 143 | }, 144 | 145 | retain: function(handle) { 146 | bridgeObjects[handle].refcount++; 147 | }, 148 | release: function(handle) { 149 | bridgeObjects[handle].refcount--; 150 | if(bridgeObjects[handle].refcount <= 0) { 151 | //console.log("freeing " + handle); 152 | bridgeObjects[handle] = null; 153 | if(handle + 1 == bridgeObjects.length) 154 | bridgeObjects.pop(); 155 | } 156 | }, 157 | abort: function() { 158 | var i = document.getElementById("stdout"); 159 | i.innerHTML += "
Aborted
"; 160 | throw "aborted"; 161 | }, 162 | _Unwind_Resume: function() {}, 163 | 164 | monotimeNow: function() { 165 | return performance.now()|0; 166 | }, 167 | 168 | executeCanvasCommands: function(handle, start, len) { 169 | var context = bridgeObjects[handle].object; 170 | 171 | var commands = new Float64Array(memory.buffer, start, len); 172 | 173 | context.save(); 174 | 175 | len = 0; 176 | 177 | while(len < commands.length) { 178 | switch(commands[len++]) { 179 | case 0: break; // intentionally blank 180 | case 1: // clear 181 | context.clearRect(0, 0, context.canvas.width, context.canvas.height); 182 | break; 183 | case 2: // strokeStyle 184 | var s = ""; 185 | var slen = commands[len++]; 186 | for(var i = 0; i < slen; i++) 187 | s += String.fromCharCode(commands[len++]); 188 | context.strokeStyle = s; 189 | break; 190 | case 3: // fillStyle 191 | var s = ""; 192 | var slen = commands[len++]; 193 | for(var i = 0; i < slen; i++) 194 | s += String.fromCharCode(commands[len++]); 195 | context.fillStyle = s; 196 | break; 197 | case 4: // drawRectangle 198 | context.beginPath(); 199 | context.rect(commands[len++] + 0.5, commands[len++] + 0.5, commands[len++] - 1, commands[len++] - 1); 200 | context.closePath(); 201 | 202 | context.stroke(); 203 | context.fill(); 204 | break; 205 | case 5: // drawText 206 | // FIXME 207 | var x = commands[len++]; 208 | var y = commands[len++]; 209 | var s = ""; 210 | var slen = commands[len++]; 211 | for(var i = 0; i < slen; i++) 212 | s += String.fromCharCode(commands[len++]); 213 | 214 | context.font = "18px sans-serif"; 215 | context.strokeText(s, x, y); 216 | break; 217 | case 6: // drawCircle 218 | context.beginPath(); 219 | context.arc(commands[len++], commands[len++], commands[len++], 0, 2 * Math.PI, false); 220 | context.closePath(); 221 | context.fill(); 222 | context.stroke(); 223 | break; 224 | case 7: // drawLine 225 | context.beginPath(); 226 | context.moveTo(commands[len++] + 0.5, commands[len++] + 0.5); 227 | context.lineTo(commands[len++] + 0.5, commands[len++] + 0.5); 228 | context.closePath(); 229 | 230 | context.stroke(); 231 | break; 232 | case 8: // drawPolygon 233 | context.beginPath(); 234 | var count = commands[len++]; 235 | 236 | context.moveTo(commands[len++] + 0.5, 237 | commands[len++] + 0.5); 238 | for(var i = 1; i < count; i++) 239 | context.lineTo(commands[len++] + 0.5, 240 | commands[len++] + 0.5); 241 | 242 | context.closePath(); 243 | context.fill(); 244 | context.stroke(); 245 | break; 246 | default: throw new Error("unknown command from sdpy bridge"); 247 | } 248 | } 249 | 250 | context.restore(); 251 | 252 | } 253 | } 254 | }; 255 | -------------------------------------------------------------------------------- /server/webassembly-skeleton.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebAssembly in D 5 | 6 | 11 | 12 | 13 | arsd.webassembly 14 | Learn more about this program here 15 |

Please note: this code doesn't work on Safari.

16 |

Also try the arrow keys and spacebar to interact with many of these programs. Tip: shift+right click can get the normal menu on the canvas.

17 |
18 | View Source 19 | 20 |
21 | 22 |
23 | 24 | 25 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test_runtime.d: -------------------------------------------------------------------------------- 1 | // ldc2 -i=. --d-version=CarelessAlocation -i=std -Iarsd-webassembly/ -L-allow-undefined -ofserver/omg.wasm -mtriple=wasm32-unknown-unknown-wasm arsd-webassembly/core/arsd/aa arsd-webassembly/core/arsd/objectutils arsd-webassembly/core/internal/utf arsd-webassembly/core/arsd/utf_decoding hello arsd-webassembly/object.d 2 | 3 | import arsd.webassembly; 4 | import std.stdio; 5 | 6 | alias thisModule = __traits(parent, {}); 7 | 8 | class A { 9 | int _b = 200; 10 | int a() { return 123; } 11 | } 12 | 13 | interface C { 14 | void test(); 15 | } 16 | interface D { 17 | void check(); 18 | } 19 | 20 | class B : A, C 21 | { 22 | int val; 23 | override int a() { return 455 + val; } 24 | 25 | void test() 26 | { 27 | rawlog(a()); 28 | int[] a; 29 | a~= 1; 30 | } 31 | } 32 | 33 | void rawlog(Args...)(Args a, string file = __FILE__, size_t line = __LINE__) 34 | { 35 | writeln(a, " at "~ file~ ":", line); 36 | } 37 | 38 | 39 | struct Tester 40 | { 41 | int b = 50; 42 | string a = "hello"; 43 | } 44 | void main() 45 | { 46 | float[] f = new float[4]; 47 | assert(f[0] is float.init); 48 | f~= 5.5; //Append 49 | f~= [3, 4]; 50 | int[] inlineConcatTest = [1, 2] ~ [3, 4]; 51 | 52 | auto dg = delegate() 53 | { 54 | writeln(inlineConcatTest[0], f[1]); 55 | }; 56 | dg(); 57 | B b = new B; 58 | b.val = 5; 59 | A a = b; 60 | a.a(); 61 | C c = b; 62 | c.test(); 63 | assert(cast(D)c is null); 64 | Tester[] t = new Tester[10]; 65 | assert(t[0] == Tester.init); 66 | assert(t.length == 10); 67 | 68 | switch("hello") 69 | { 70 | case "test": 71 | writeln("broken"); 72 | break; 73 | case "hello": 74 | writeln("Working switch string"); 75 | break; 76 | default: writeln("What happenned here?"); 77 | } 78 | string strTest = "test"[0..$]; 79 | assert(strTest == "test"); 80 | 81 | 82 | Tester* structObj = new Tester(50_000, "Inline Allocation"); 83 | writeln(structObj is null, structObj.a, structObj.b); 84 | 85 | int[string] hello = ["hello": 500]; 86 | assert(("hello" in hello) !is null, "No key hello yet..."); 87 | assert(hello["hello"] == 500, "Not 500"); 88 | hello["hello"] = 1200; 89 | assert(hello["hello"] == 1200, "Reassign didn't work"); 90 | hello["h2o"] = 250; 91 | assert(hello["h2o"] == 250, "New member"); 92 | 93 | 94 | int[] appendTest; 95 | appendTest~= 50; 96 | appendTest~= 500; 97 | appendTest~= 5000; 98 | foreach(v; appendTest) 99 | writeln(v); 100 | string strConcatTest; 101 | strConcatTest~= "Hello"; 102 | strConcatTest~= "World"; 103 | writeln(strConcatTest); 104 | int[] intConcatTest = cast(int[2])[1, 2]; 105 | intConcatTest~= 50; 106 | string decInput = "a"; 107 | decInput~= "こんいちは"; 108 | foreach(dchar ch; "こんいちは") 109 | { 110 | decInput~= ch; 111 | writeln(ch); 112 | } 113 | writeln(decInput); 114 | int[] arrCastTest = [int.max]; 115 | 116 | foreach(v; cast(ubyte[])arrCastTest) 117 | writeln(v); 118 | 119 | 120 | 121 | enum Type 122 | { 123 | int_, 124 | string_, 125 | } 126 | struct TestWithPtr 127 | { 128 | int* a; 129 | Type t = Type.string_; 130 | } 131 | 132 | TestWithPtr[] _; 133 | _~= TestWithPtr(new int(50), Type.int_); 134 | _ = _[0..$-1]; 135 | _~= TestWithPtr(new int(100), Type.string_); 136 | _~= TestWithPtr(new int(150), Type.string_); 137 | _~= TestWithPtr(new int(200), Type.int_); 138 | 139 | foreach(v; _) 140 | writeln(*v.a); 141 | 142 | 143 | char[] sup; 144 | string rev; 145 | 146 | // string test = null; 147 | for(int i = 'a'; i <= 'z'; i++) 148 | { 149 | sup~= cast(char)i; 150 | rev~= ('z' - cast(char)i) + 'a'; 151 | } 152 | writeln((typeid(sup)).toString); 153 | 154 | static foreach(mem; __traits(allMembers, std.stdio)) 155 | writeln(mem); 156 | 157 | float[][] matrixTest = new float[][](8, 8); 158 | 159 | foreach(array; matrixTest) 160 | foreach(value; array) 161 | writeln(value); 162 | 163 | // foreach(array; matrixTest) 164 | // foreach(value; array) 165 | // writeln(value); 166 | 167 | 168 | assert(false, sup~sup~sup); 169 | } -------------------------------------------------------------------------------- /tetris.d: -------------------------------------------------------------------------------- 1 | import arsd.simpledisplay; 2 | import arsd.simpleaudio; 3 | 4 | enum PieceSize = 16; 5 | 6 | enum SettleStatus { 7 | none, 8 | settled, 9 | cleared, 10 | tetris, 11 | gameOver, 12 | } 13 | 14 | class Board { 15 | int width; 16 | int height; 17 | int[] state; 18 | int score; 19 | this(int width, int height) { 20 | state = new int[](width * height); 21 | this.width = width; 22 | this.height = height; 23 | } 24 | 25 | SettleStatus settlePiece(Piece piece) { 26 | if(piece.y <= 0) 27 | return SettleStatus.gameOver; 28 | 29 | SettleStatus status = SettleStatus.settled; 30 | foreach(yo, line; pieces[piece.type][piece.rotation]) { 31 | int mline = line; 32 | foreach(xo; 0 .. 4) { 33 | if(mline & 0b1000) 34 | state[(piece.y+yo) * width + xo + piece.x] = piece.type + 1; 35 | mline <<= 1; 36 | } 37 | } 38 | 39 | int[4] del; 40 | int delPos = 0; 41 | 42 | foreach(y; piece.y .. piece.y + 4) { 43 | int presentCount; 44 | if(y >= height) 45 | break; 46 | foreach(x; 0 .. width) 47 | if(state[y * width + x]) 48 | presentCount++; 49 | 50 | if(presentCount == width) { 51 | del[delPos++] = y; 52 | status = SettleStatus.cleared; 53 | } 54 | } 55 | 56 | if(delPos == 4) { 57 | score += 4; // tetris bonus! 58 | status = SettleStatus.tetris; 59 | } 60 | 61 | foreach(p; 0 .. delPos) { 62 | foreach_reverse(y; 0 .. del[p]) 63 | state[(y + 1) * width .. (y + 2) * width] = state[(y + 0) * width .. (y + 1) * width]; 64 | state[0 .. width] = 0; 65 | 66 | score++; 67 | } 68 | 69 | return status; 70 | 71 | /+ 72 | import std.stdio; 73 | writeln; 74 | writeln; 75 | foreach(y; 0 .. height) { 76 | foreach(x; 0 .. width) { 77 | write(state[y * width + x]); 78 | } 79 | writeln(""); 80 | } 81 | +/ 82 | } 83 | 84 | SettleStatus trySettle(Piece piece) { 85 | auto pieceMap = pieces[piece.type][piece.rotation]; 86 | int ph = 4; 87 | foreach_reverse(line; pieceMap) { 88 | if(line) 89 | break; 90 | ph--; 91 | } 92 | if(ph + piece.y >= this.height) { 93 | if(!piece.settleNextFrame) { 94 | piece.settleNextFrame = true; 95 | return SettleStatus.none; 96 | } else { 97 | return settlePiece(piece); 98 | } 99 | } 100 | piece.y++; 101 | if(collisionDetect(piece)) { 102 | piece.y--; 103 | 104 | if(!piece.settleNextFrame) { 105 | piece.settleNextFrame = true; 106 | return SettleStatus.none; 107 | } else { 108 | return settlePiece(piece); 109 | } 110 | } else { 111 | piece.settleNextFrame = false; 112 | } 113 | piece.y--; 114 | return SettleStatus.none; 115 | } 116 | 117 | bool collisionDetect(Piece piece) { 118 | auto pieceMap = pieces[piece.type][piece.rotation]; 119 | foreach_reverse(yo,line; pieceMap) { 120 | int mline = line; 121 | foreach(xo; 0 .. 4) { 122 | if(mline & 0b1000) { 123 | // FIXME: potential range violation 124 | if(state[(piece.y+yo) * this.width + xo + piece.x]) 125 | return true; 126 | } 127 | mline <<= 1; 128 | } 129 | } 130 | return false; 131 | } 132 | 133 | void redraw(SimpleWindow window) { 134 | auto painter = window.draw(); 135 | int x, y; 136 | foreach(s; state) { 137 | painter.fillColor = s ? palette[s - 1] : Color.black; 138 | painter.outlineColor = s ? Color.white : Color.black; 139 | painter.drawRectangle(Point(x, y) * PieceSize, PieceSize, PieceSize); 140 | x++; 141 | if(x == width) { 142 | x = 0; 143 | y++; 144 | } 145 | } 146 | } 147 | } 148 | 149 | static immutable ubyte[][][] pieces = [ 150 | // long straight 151 | [[0b1000, 152 | 0b1000, 153 | 0b1000, 154 | 0b1000], 155 | [0b1111, 156 | 0b0000, 157 | 0b0000, 158 | 0b0000]], 159 | // l 160 | [[0b1000, 161 | 0b1000, 162 | 0b1100, 163 | 0b0000], 164 | [0b0010, 165 | 0b1110, 166 | 0b0000, 167 | 0b0000], 168 | [0b1100, 169 | 0b0100, 170 | 0b0100, 171 | 0b0000], 172 | [0b1110, 173 | 0b1000, 174 | 0b0000, 175 | 0b0000]], 176 | // j 177 | [[0b0100, 178 | 0b0100, 179 | 0b1100, 180 | 0b0000], 181 | [0b1000, 182 | 0b1110, 183 | 0b0000, 184 | 0b0000], 185 | [0b1100, 186 | 0b1000, 187 | 0b1000, 188 | 0b0000], 189 | [0b1110, 190 | 0b0010, 191 | 0b0000, 192 | 0b0000]], 193 | // n 194 | [[0b1100, 195 | 0b0110, 196 | 0b0000, 197 | 0b0000], 198 | [0b0100, 199 | 0b1100, 200 | 0b1000, 201 | 0b0000]], 202 | // other n 203 | [[0b0110, 204 | 0b1100, 205 | 0b0000, 206 | 0b0000], 207 | [0b1000, 208 | 0b1100, 209 | 0b0100, 210 | 0b0000]], 211 | // t 212 | [[0b0100, 213 | 0b1110, 214 | 0b0000, 215 | 0b0000], 216 | [0b1000, 217 | 0b1100, 218 | 0b1000, 219 | 0b0000], 220 | [0b1110, 221 | 0b0100, 222 | 0b0000, 223 | 0b0000], 224 | [0b0100, 225 | 0b1100, 226 | 0b0100, 227 | 0b0000]], 228 | // square 229 | [[0b1100, 230 | 0b1100, 231 | 0b0000, 232 | 0b0000]], 233 | ]; 234 | 235 | immutable Color[] palette = [ 236 | Color.red, 237 | Color.blue, 238 | Color.green, 239 | Color.yellow, 240 | Color.teal, 241 | Color.purple, 242 | Color.gray 243 | ]; 244 | 245 | static assert(palette.length == pieces.length); 246 | 247 | class Piece { 248 | SimpleWindow window; 249 | Board board; 250 | this(SimpleWindow window, Board board) { 251 | this.window = window; 252 | this.board = board; 253 | } 254 | 255 | static int randomType() { 256 | import std.random; 257 | return uniform(0, cast(int) pieces.length); 258 | } 259 | 260 | int width() { 261 | int fw = 0; 262 | foreach(int s; pieces[type][rotation]) { 263 | int w = 4; 264 | while(w && ((s & 1) == 0)) { 265 | w--; 266 | s >>= 1; 267 | } 268 | if(w > fw) 269 | fw = w; 270 | } 271 | return fw; 272 | } 273 | 274 | void reset(int type) { 275 | this.type = type; 276 | rotation = 0; 277 | x = board.width / 2 - 1; 278 | y = 0; 279 | settleNextFrame = false; 280 | } 281 | 282 | int type; 283 | int rotation; 284 | 285 | int x; 286 | int y; 287 | 288 | bool settleNextFrame; 289 | 290 | void erase() { 291 | draw(true); 292 | } 293 | 294 | void draw(bool erase = false) { 295 | auto painter = window.draw(); 296 | painter.fillColor = erase ? Color.black : palette[type]; 297 | painter.outlineColor = erase ? Color.black : Color.white; 298 | foreach(yo, line; pieces[type][rotation]) { 299 | int mline = line; 300 | foreach(xo; 0 .. 4) { 301 | if(mline & 0b1000) 302 | painter.drawRectangle(Point(cast(int) (x + xo), cast(int) (y + yo)) * PieceSize, PieceSize, PieceSize); 303 | mline <<= 1; 304 | } 305 | } 306 | } 307 | 308 | void moveDown() { 309 | if(!settleNextFrame) { 310 | y++; 311 | if(board.collisionDetect(this)) 312 | y--; 313 | } 314 | } 315 | 316 | void moveLeft() { 317 | if(x) { 318 | x--; 319 | if(board.collisionDetect(this)) 320 | x++; 321 | } 322 | } 323 | 324 | void moveRight() { 325 | if(x + width < board.width) { 326 | x++; 327 | if(board.collisionDetect(this)) 328 | x--; 329 | } 330 | } 331 | 332 | void rotate() { 333 | rotation++; 334 | if(rotation >= pieces[type].length) 335 | rotation = 0; 336 | if(x + width > board.width) 337 | x = board.width - width; 338 | } 339 | } 340 | 341 | void main() { 342 | auto audio = AudioOutputThread(0); 343 | //audio.start(); 344 | 345 | auto board = new Board(10, 20); 346 | 347 | auto window = new SimpleWindow(board.width * PieceSize, board.height * PieceSize, "Detris"); 348 | 349 | // clear screen to black 350 | { 351 | auto painter = window.draw(); 352 | painter.outlineColor = Color.black; 353 | painter.fillColor = Color.black; 354 | painter.drawRectangle(Point(0, 0), Size(window.width, window.height)); 355 | } 356 | 357 | Piece currentPiece = new Piece(window, board); 358 | 359 | int frameCounter; 360 | bool downPressed; 361 | 362 | int gameOverY = 0; 363 | 364 | int difficulty = 1; 365 | 366 | window.eventLoop(100 / 5, () { 367 | 368 | if(gameOverY > board.height + 1) { 369 | window.close(); 370 | return; 371 | } 372 | 373 | if(frameCounter <= 0) { 374 | if(gameOverY == 0) { 375 | currentPiece.erase(); 376 | currentPiece.moveDown(); 377 | currentPiece.draw(); 378 | } 379 | auto sb = board.score; 380 | bool donew = false; 381 | final switch (board.trySettle(currentPiece)) { 382 | case SettleStatus.none: 383 | break; 384 | case SettleStatus.settled: 385 | audio.beep(400); 386 | donew = true; 387 | break; 388 | case SettleStatus.cleared: 389 | audio.beep(1100); 390 | audio.beep(400); 391 | donew = true; 392 | break; 393 | case SettleStatus.tetris: 394 | audio.beep(1200); 395 | audio.beep(400); 396 | audio.beep(700); 397 | donew = true; 398 | break; 399 | case SettleStatus.gameOver: 400 | audio.boop(); 401 | 402 | auto painter = window.draw(); 403 | painter.outlineColor = Color.white; 404 | painter.fillColor = Color(127, 127, 127); 405 | painter.drawRectangle(Point(0, 0), Size(window.width, (gameOverY + 1) * PieceSize)); 406 | gameOverY++; 407 | break; 408 | } 409 | 410 | if(donew) { 411 | currentPiece.reset(Piece.randomType()); 412 | if(board.score != sb) 413 | board.redraw(window); 414 | } 415 | 416 | frameCounter = 2 * 5 * 3; 417 | } 418 | 419 | if(downPressed) 420 | frameCounter -= 12; 421 | frameCounter -= difficulty; 422 | }, (KeyEvent kev) { 423 | if(kev.key == Key.Down) 424 | downPressed = kev.pressed; 425 | if(!kev.pressed) return; 426 | switch(kev.key) { 427 | case Key.Up: 428 | case Key.Space: 429 | currentPiece.erase(); 430 | currentPiece.rotate(); 431 | currentPiece.draw(); 432 | audio.beep(1100); 433 | audio.beep(1000); 434 | break; 435 | case Key.Left: 436 | currentPiece.erase(); 437 | currentPiece.moveLeft(); 438 | currentPiece.draw(); 439 | break; 440 | case Key.Right: 441 | currentPiece.erase(); 442 | currentPiece.moveRight(); 443 | currentPiece.draw(); 444 | break; 445 | case Key.LeftBracket: 446 | if(difficulty) 447 | difficulty--; 448 | break; 449 | case Key.RightBracket: 450 | if(difficulty < 10) 451 | difficulty++; 452 | break; 453 | default: 454 | } 455 | }); 456 | 457 | import std.stdio; 458 | writeln(board.score); 459 | } 460 | --------------------------------------------------------------------------------