├── .gitmodules ├── dub.json ├── readme.md ├── src └── dabble │ ├── defs.d │ ├── grammars.d │ ├── main.d │ ├── meta.d │ ├── parser.d │ ├── repl.d │ ├── sharedlib.d │ ├── testing.d │ └── util.d └── ui ├── css ├── codemirror.css ├── nv.d3.css └── style.css ├── fonts ├── DroidSansMono-webfont.woff └── OpenSans-Regular-webfont.woff ├── html ├── child.html └── repl.html ├── images ├── c.png ├── dlogo.png ├── dlogosmall.png ├── f.png ├── k.png ├── loader.gif ├── s.png └── v.png └── js ├── codemirror.js ├── d3.v3.min.js ├── handlers.js ├── jquery-1.10.2.js ├── main.js ├── mode └── d.js ├── nv.d3.min.js ├── shortcut.js └── show-hint.js /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/DCD"] 2 | path = src/DCD 3 | url = https://github.com/Hackerpilot/DCD 4 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dabble", 3 | "description": "A simple REPL for DMD on Win32", 4 | "authors": ["cal"], 5 | "homepage": "", 6 | "license": "Boost v1.0", 7 | 8 | "targetType": "executable", 9 | "targetPath":"./bin", 10 | "platforms": ["windows", "posix"], 11 | 12 | "dflags-posix": [ 13 | "-defaultlib=libphobos2.so", 14 | "-release" 15 | ], 16 | 17 | "libs-posix": [ 18 | "dl" 19 | ], 20 | 21 | "buildTypes": { 22 | "debug": { "buildOptions": ["debugInfo"] }, 23 | "release": { "buildOptions": ["releaseMode"] } 24 | }, 25 | 26 | "dependencies": { 27 | "pegged":"~master" 28 | }, 29 | 30 | "mainSourceFile": "src/dabble/main.d", 31 | 32 | "excludedSourceFiles": [ 33 | "src/dabble/sourcebrowser.d", 34 | "src/DCD/client.d", 35 | "src/DCD/autocomplete.d", 36 | "src/DCD/server.d", 37 | "src/DCD/dscanner/main.d", 38 | "src/DCD/dscanner/analysis/*", 39 | "src/DCD/msgpack-d/example/*" 40 | ] 41 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Dabble 2 | 3 | A repl for the D programming language (Windows and Linux). Tested on Win 7, Arch Linux 32 and 64 bit. 4 | 5 | ### Note 6 | 7 | This repl is not sandboxed in any way - use it at your own risk. 8 | 9 | ### Getting started: 10 | 11 | You will need to have the Digital Mars D compiler installed and on your executable path. 12 | 13 | 1. Get DUB (currently requires git-head): http://registry.vibed.org/download 14 | 15 | 2. Get, build and run dabble: 16 | 17 | ``` 18 | git clone https://github.com/callumenator/dabble 19 | cd dabble 20 | git submodule update --init --recursive 21 | dub run 22 | ``` 23 | 24 | ### How it works 25 | 26 | (The idea for this comes from http://neugierig.org/software/c-repl/). 27 | 28 | User input is minimally parsed to search for declarations (new variables, classes, structs, enums, aliases). 29 | 30 | Variable declarations are replaced by code which places the variables on the heap, and references to existing variables are redirected to point at these heap copies. User type declarations and functions are extracted verbatim to be placed at module scope. All other statements are placed inside a dummy main function. 31 | 32 | The parsed/modified code is then written to a temporary .d file, and compiled into a shared library. If compilation succeeds, the library is loaded into memory, and the dummy main function is called. Some attempt is made to catch exceptions thrown inside the user's code, for example to trap range violations. 33 | 34 | If the user's code resulted in a value, that value is printed to the screen. Else, 'OK' is printed to indicate success. The loop then continues. 35 | 36 | Read-eval-print latency is usually acceptable, since DMD compiles simple code blazingly fast. 37 | 38 | ### Meta Commands 39 | 40 | Meta commands are intercepted by the REPL environment and interpreted directly. They do not trigger compilation. The following list of commands are recognized. 41 | (Arguments enclosed in square brakets ```[]``` are optional, those in angle brackets ```<>``` indicate a choice.) 42 | 43 | ##### print 44 | 45 | ```print [expression]``` 46 | 47 | If expression is omitted, prints a list of all currently defined variables, along with their types and values. If expression is provided, it should be a comma separated list of sub-expressions, for example: 48 | 49 | ```print a``` - prints the value of variable 'a' 50 | 51 | ```print a.b``` - if 'a' is an aggregate, prints the value of member 'b' 52 | 53 | ```print a[4]``` - if 'a' is an array, and index 4 is within it's array bounds, prints value at index 4 54 | 55 | ```print *a``` - if 'a' is a pointer, and is not null, prints the value pointed to by 'a' 56 | 57 | ```print cast(MyType)a``` - if 'MyType' is a known type, prints 'a' interpreted as MyType 58 | 59 | More complex combinations of the above are also accepted, for example: 60 | 61 | ```print ((cast(MyType)*a).b[3]).d``` - cast the value pointed to by 'a' to MyType, access index 3 of member 'b', print the value of member 'd' 62 | 63 | ##### type 64 | 65 | ```type expression``` 66 | 67 | This command is identical to the ```print``` command, however instead of outputting a value, information on the type is output. E.g.: 68 | 69 | ```type *a.b[2]``` - print the type of the pointer target of index 2 of member 'b' of 'a' 70 | 71 | ##### delete 72 | 73 | ```delete var1 [, var2, var3, ...]``` 74 | 75 | Delete each of the variables named in the comma separated list. This allows you to re-use those identifiers for new variables. 76 | 77 | ##### use (currently windows only) 78 | 79 | ```use module_filename``` 80 | 81 | This command is used to include user modules in the compilation step. It allows you to reference type and function definitions, and call functions, in the user module. Note that this only set's the module up to be included in compilation - you will still need to ```import``` the module in your REPL session. If the used module contains other dependencies, these will also need to be ```use```'d. 82 | 83 | ##### reset session 84 | 85 | ```reset session``` 86 | 87 | Clear all variables and definitions, remove all imports. Bascially reset the REPL to a clean state. 88 | 89 | ##### clear 90 | 91 | ```clear [buffer]``` 92 | 93 | Clear without arguments will clear the command console screen. If the ```buffer``` argument is supplied, the code input buffer will be cleared. This is useful if you make a mistake during multi-line input, and need to clear the current input. 94 | 95 | ##### debug 96 | 97 | ```debug ``` 98 | 99 | Turn on or off debug switches. 100 | 101 | * parseOnly - only do the parse step, do not compile or call. 102 | 103 | * stages - write a message indicating each stage (parse, build, call). 104 | 105 | * times - output the times (in msecs) taken to complete each stage (parse, build, call). 106 | 107 | 108 | ### Issues: 109 | - Static variables (TLS or standard globals) - in particular, static data used by imported modules will not work as expected, since these data are not preserved between shared library loads. 110 | - Storing the address of code or static data - this should still work (assuming the pointer is detected), but will prevent the shared library from being unloaded, 111 | -------------------------------------------------------------------------------- /src/dabble/defs.d: -------------------------------------------------------------------------------- 1 | /** 2 | Written in the D programming language. 3 | 4 | Various code that is shared between core and DLL. 5 | 6 | Copyright: Copyright Callum Anderson 2013 7 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Callum Anderson 9 | **/ 10 | 11 | module dabble.defs; 12 | 13 | //version = log; 14 | package bool trace = false; 15 | private enum replSharePrefix = "_repl_"; 16 | 17 | struct Code 18 | { 19 | import std.array; 20 | Appender!string header, prefix, suffix; 21 | } 22 | 23 | 24 | /** 25 | * Info shared between DLL and REPL executable. 26 | */ 27 | struct ReplShare 28 | { 29 | Var[] vars; 30 | Decl[] decls; 31 | Vtbl[] vtbls; 32 | Type*[string] map; /// map used by typeBuilder and friends 33 | 34 | void* gc; /// host gc instance 35 | bool keepAlive; 36 | string resultFile; /// result of the eval 37 | void*[2] imageBounds; 38 | 39 | void init() 40 | { 41 | buildBasicTypes(map); 42 | } 43 | 44 | void reset() 45 | { 46 | map.clear(); 47 | vars.clear(); 48 | decls.clear(); 49 | vtbls.clear(); 50 | init(); 51 | } 52 | 53 | Code generate() 54 | { 55 | Code c; 56 | foreach(i, ref v; vars) 57 | v.generate(c, i); 58 | foreach(i, ref d; decls) 59 | d.generate(c, i); 60 | return c; 61 | } 62 | 63 | void prune() 64 | { 65 | import std.algorithm : filter; 66 | import std.array : array; 67 | 68 | Var[] vtemp; 69 | Decl[] dtemp; 70 | foreach(v; vars.filter!( x => x.valid )) 71 | vtemp ~= v; 72 | foreach(d; decls.filter!( x => x.valid )) 73 | dtemp ~= d; 74 | 75 | vars = vtemp; 76 | decls = dtemp; 77 | 78 | //vars = vars.filter!( x => x.valid )().array(); 79 | //decls = decls.filter!( x => x.valid )().array(); 80 | } 81 | 82 | void deleteVar(string name) 83 | { 84 | import std.algorithm : filter; 85 | import std.array : array; 86 | 87 | Var[] vtemp; 88 | foreach(v; vars.filter!( x => x.valid )) 89 | vtemp ~= v; 90 | 91 | vars = vtemp; 92 | 93 | //vars = vars.filter!( x => x.name == name )().array(); 94 | } 95 | } 96 | 97 | 98 | struct Vtbl 99 | { 100 | string name; 101 | void*[] vtbl; 102 | } 103 | 104 | 105 | struct Var 106 | { 107 | import std.array : Appender; 108 | 109 | string name, type, init, current, displayType; 110 | bool first = true, valid = false, func = false; 111 | QualifiedType ty; 112 | void* addr; 113 | 114 | void put(T...)(ref Appender!string app, T items) 115 | { 116 | foreach(i; items) 117 | app.put(i); 118 | } 119 | 120 | void generate(ref Code c, size_t index) 121 | { 122 | import std.string : strip; 123 | import std.regex : match; 124 | import std.conv : text, to; 125 | 126 | if (first) 127 | { 128 | auto accessor = replSharePrefix ~ ".vars[" ~ index.to!string() ~ "]"; 129 | put(c.suffix, accessor, ".valid = true;\n"); 130 | first = false; 131 | 132 | auto typeLambda = text("auto typeLambda = ()=>(", init, ");"); 133 | 134 | init = strip(init); 135 | 136 | if (strip(type).length == 0 || !type.match(`\bauto\b`).empty) 137 | type = text("typeof( (() => (", init,"))() )"); 138 | 139 | 140 | string _funcLit() 141 | { 142 | string code = 143 | " static if ((__traits(compiles, isFunctionPointer!(" ~ type ~ ")) && isFunctionPointer!(" ~ type ~ ")) || is(" ~ type ~ " == delegate))\n {\n" 144 | " " ~ accessor ~ ".func = true;\n" 145 | " " ~ type ~ "* " ~ name ~ ";\n" 146 | " " ~ accessor ~ ".type = q{" ~ type ~ "}.idup;\n"; 147 | code ~= init .length ? " {\n auto _temp = " ~ init ~ ";\n " ~ name ~ " = cast(" ~ type ~ "*)&_temp;\n }\n }\n" : " }\n"; 148 | return code; 149 | } 150 | 151 | string _maker() 152 | { 153 | string assign; 154 | string s = "{\n"; 155 | string utype = text("Unqual!(",type,")"); 156 | 157 | s ~= text(" static if (is(",type," == class)) {\n"); 158 | s ~= text(" ",accessor,".addr = cast(void*)emplace!(",utype,")(new void[](__traits(classInstanceSize,",utype,")));\n"); 159 | s ~= text(" } else {\n"); 160 | s ~= text(" ",accessor,".addr = cast(void*)emplace!(",utype,")(new void[]((",utype,").sizeof));\n"); 161 | s ~= text(" }\n"); 162 | 163 | if (strip(init).length == 0) 164 | return s ~ "}\n"; 165 | 166 | assign = text(typeLambda, " *cast(Unqual!(typeof(typeLambda()))*)", accessor, ".addr = cast(Unqual!(typeof(typeLambda())))(",init,");"); 167 | s ~= text(" static if (__traits(compiles,",utype,"(",init,")))\n {{\n"); 168 | s ~= text(" ",assign,"\n }}\n"); 169 | 170 | assign = text(typeLambda, " *cast(Unqual!(typeof(typeLambda()))*)", accessor, ".addr = cast(Unqual!(typeof(typeLambda())))",init,";"); 171 | s ~= text(" else static if (__traits(compiles, { ",assign," }))\n {{\n"); 172 | s ~= text(" ",assign,"\n }}\n"); 173 | 174 | assign = text(typeLambda, " *cast(Unqual!(typeof(typeLambda()))*)", accessor, ".addr = cast(Unqual!(typeof(typeLambda())))(",init,");"); 175 | s ~= text(" else\n {{\n"); 176 | s ~= text(" ",assign,"\n }}\n}\n"); 177 | 178 | return s; 179 | } 180 | 181 | put(c.prefix, 182 | _funcLit(), " else\n {\n", 183 | " ", _maker(), "\n", 184 | " auto ", name, " = cast(", type, "*)", accessor, ".addr;\n }\n\n"); 185 | 186 | put(c.prefix, 187 | " ", accessor, ".displayType = typeof(*", name, ").stringof.idup;\n", 188 | " if (!", accessor, ".func) _REPL.exprResult(*", name, ", __expressionResult);\n\n"); 189 | 190 | put(c.suffix, 191 | " if (", accessor, ".func)\n {\n" 192 | " ", accessor, ".current = q{", init, "}.idup;\n" 193 | " }\n else\n {\n" 194 | " ", accessor, ".ty = _REPL.buildType!(typeof(*",name, "))(_repl_.map);\n" 195 | " }\n\n"); 196 | } 197 | else // var has already been created, just grab it 198 | { 199 | if (func) 200 | { 201 | put(c.prefix, type, "* ", name, ";\n{\n"); 202 | if (init == "") 203 | put(c.prefix, " auto _temp = cast(", type, ") null;\n"); 204 | else 205 | put(c.prefix, " auto _temp = ", init, ";\n"); 206 | 207 | put(c.prefix, " ", name, " = cast(", type, "*)&_temp;\n}\n\n"); 208 | } 209 | else 210 | { 211 | put(c.prefix, "auto ", name, " = _REPL.get!(", type, ")(_repl_.vars,", index.to!string(), ");\n"); 212 | } 213 | } 214 | } 215 | 216 | void toString(scope void delegate(const(char)[]) sink) 217 | { 218 | sink(name); 219 | sink(" ("); 220 | sink(displayType); 221 | sink(") = "); 222 | sink(current); 223 | } 224 | } 225 | 226 | 227 | struct Decl 228 | { 229 | string decl; 230 | bool global = false, first = true, valid = false; 231 | 232 | void generate(ref Code c, size_t index) 233 | { 234 | import std.conv : to; 235 | 236 | if (global) 237 | c.header.put(decl ~ "\n"); 238 | else 239 | c.prefix.put(decl ~ "\n"); 240 | 241 | if (first) 242 | { 243 | c.suffix.put(replSharePrefix ~ ".decls[" ~ index.to!string() ~ "].valid = true;\n"); 244 | first = false; 245 | } 246 | } 247 | 248 | void toString(scope void delegate(const(char)[]) sink) 249 | { 250 | sink(decl); 251 | } 252 | } 253 | 254 | 255 | T* get(T)(const(Var[]) symbols, size_t index) 256 | { 257 | return cast(T*)symbols[index].addr; 258 | } 259 | 260 | 261 | auto exprResult(E)(lazy E expr, ref string result) 262 | { 263 | import std.exception : collectException; 264 | import std.conv : to; 265 | 266 | static if (__traits(compiles, is(typeof(expr()))) && !is(typeof(expr()) == void)) 267 | { 268 | auto temp = expr(); 269 | collectException!Throwable(result = temp.to!string()); 270 | return temp; 271 | } 272 | else 273 | { 274 | result = ""; 275 | expr(); 276 | } 277 | } 278 | 279 | 280 | template needsDup(T) 281 | { 282 | import std.regex : RegexMatch; 283 | import std.traits : isArray, isPointer, isAggregateType; 284 | 285 | static if (is(T _ == RegexMatch!(U), U...)) 286 | enum needsDup = false; 287 | else static if (isArray!T) 288 | enum needsDup = true; 289 | else static if (isPointer!T) 290 | enum needsDup = true; 291 | else static if (isAggregateType!T) 292 | enum needsDup = true; 293 | else 294 | enum needsDup = false; 295 | } 296 | 297 | 298 | /** 299 | * Look for pointers to DLL data, and copy onto the heap. keepAlive == true 300 | * indicates that a function pointer is pointing at DLL code. 301 | */ 302 | void dupSearch(T)(ref T t, void* start, void* stop, ref bool keepAlive) 303 | { 304 | import std.traits : isFunctionPointer, isSomeString, Unqual, isPointer, PointerTarget, isAggregateType, isArray; 305 | import std.c.string : memcpy; 306 | import std.stdio : writeln; 307 | 308 | static if (!needsDup!T) 309 | return; 310 | 311 | static if (isFunctionPointer!T) 312 | { 313 | if (t >= start && t <= stop) 314 | { 315 | version(log) { writeln("dupeSearch: keep alive - ", T.stringof); } 316 | keepAlive = true; 317 | } 318 | } 319 | else static if (isSomeString!T) 320 | { 321 | if (t.ptr >= start && t.ptr <= stop) 322 | { 323 | version(log) { writeln("dupSearch: dup string"); } 324 | cast(Unqual!T)t = t.idup; 325 | } 326 | } 327 | else static if (isArray!T) 328 | { 329 | if (t.length > 0) 330 | { 331 | version(log) { writeln("dupSearch: duping array ", T.stringof); } 332 | auto newMem = new void[]((ArrayElement!T).sizeof * t.length); 333 | memcpy(newMem.ptr, t.ptr, (ArrayElement!T).sizeof * t.length); 334 | void* p0 = &t; 335 | void** ptrAddr = cast(void**)(cast(size_t)p0 + 4); 336 | *ptrAddr = newMem.ptr; 337 | } 338 | 339 | static if (needsDup!(ArrayElement!T)) 340 | { 341 | version(log) { writeln("dupSearch: check array elements ", T.stringof); } 342 | 343 | foreach(ref e; t) 344 | dupSearch(e, start, stop, keepAlive); 345 | } 346 | } 347 | else static if (isPointer!T) 348 | { 349 | if (cast(void*)t >= start && cast(void*)t <= stop) 350 | { 351 | version(log) { writeln("dupSearch: dup pointer ", T.stringof); } 352 | auto newMem = new void[]((PointerTarget!T).sizeof); 353 | memcpy(newMem.ptr, t, (PointerTarget!T).sizeof); 354 | t = cast(T)newMem.ptr; 355 | } 356 | 357 | // Only check for a pointer to string 358 | static if (isSomeString!(PointerTarget!T)) 359 | dupSearch(*t, start, stop, keepAlive); 360 | } 361 | else static if (isAggregateType!T) 362 | { 363 | version(log) { writeln("dupSearch: aggregate ", T.stringof); } 364 | 365 | size_t offset; 366 | void* baseAddr = cast(void*)&t; 367 | 368 | static if (is(T == class)) 369 | { 370 | offset += 2*(void*).sizeof; 371 | baseAddr = cast(void*)t; 372 | } 373 | 374 | foreach(ref f; t.tupleof) 375 | { 376 | static if ( (is(typeof(f) == class) || isPointer!(typeof(f))) ) 377 | { 378 | if (f !is null) 379 | dupSearch((*(cast(typeof(f)*)(baseAddr + offset))), start, stop, keepAlive); 380 | } 381 | else 382 | { 383 | dupSearch((*(cast(typeof(f)*)(baseAddr + offset))), start, stop, keepAlive); 384 | } 385 | offset += f.sizeof; 386 | } 387 | } 388 | } 389 | 390 | 391 | struct QualifiedType 392 | { 393 | import std.typecons : Tuple; 394 | 395 | enum Qualifier { None, Const, Immutable } 396 | 397 | Type* type; 398 | Qualifier qualifier = Qualifier.None; 399 | alias type this; 400 | 401 | auto typeOf(Operation[] stack, ref Type*[string] map) 402 | { 403 | import std.algorithm : find; 404 | import std.array : front, empty; 405 | 406 | QualifiedType currType = this; 407 | foreach(i; stack) 408 | { 409 | final switch(i.op) with(Operation.Op) 410 | { 411 | case Deref: 412 | if (!currType.type.isPointer) 413 | return tuple(QualifiedType(), "Error: "~currType.toString()~" is not a pointer"); 414 | currType = currType.type._ref; 415 | break; 416 | case Index: 417 | if (!currType.type.isArray) 418 | return tuple(QualifiedType(), "Error: "~currType.toString()~" is not an array"); 419 | currType = currType.type._ref; 420 | break; 421 | case Slice: 422 | return tuple(QualifiedType(), "Error: slicing not supported"); 423 | case Member: 424 | auto m = currType.type._object.find!((a,b) => a.name==b)(i.val); 425 | if (!currType.type.isAggregate || m.empty) 426 | return tuple(QualifiedType(), "Error: no member "~i.val~" for type "~currType.toString()); 427 | currType = m.front.type; 428 | break; 429 | case Cast: 430 | currType = buildType(i.val, map); 431 | if (currType.type is null) 432 | return tuple(QualifiedType(), "Error: unknown type "~i.val); 433 | break; 434 | } 435 | } 436 | // Returning tuple directly results in buggy behaviour 437 | Tuple!(QualifiedType, string) ret; 438 | ret[0] = currType; 439 | return ret; 440 | } 441 | 442 | string valueOf(Operation[] stack, void* ptr, ref Type*[string] map) 443 | { 444 | import std.conv : to; 445 | import std.stdio : writeln; 446 | 447 | if (trace) writeln("valueOf: stack: ", stack); 448 | 449 | void* addr = ptr; 450 | QualifiedType currType = this; 451 | foreach(i; stack) 452 | { 453 | final switch(i.op) with(Operation.Op) 454 | { 455 | case Deref: 456 | if (!currType.type.isPointer) 457 | return "Error: "~currType.toString()~" is not a pointer"; 458 | currType = currType.deref(addr); 459 | break; 460 | case Index: 461 | if (!currType.type.isArray) 462 | return "Error: "~currType.toString()~" is not an array"; 463 | currType = currType.index(addr, i.val.to!size_t()); 464 | break; 465 | case Slice: 466 | return "Error: slicing not supported"; 467 | case Member: 468 | if (!currType.type.isAggregate) 469 | return "Error: " ~ currType.toString() ~ " is not an aggregate"; 470 | currType = currType.member(addr, i.val); 471 | if (currType.type is null) 472 | return "Error: no member "~i.val~" for "~currType.toString(); 473 | break; 474 | case Cast: 475 | currType = buildType(i.val, map); 476 | if (currType.type is null) 477 | return "Error: unknown type " ~ i.val; 478 | break; 479 | } 480 | } 481 | 482 | if (currType.type is null) 483 | return "Error: null type*"; 484 | 485 | if (addr is null) 486 | return "Error: null address"; 487 | 488 | if (trace) writeln("valueOf: type is: ", currType.typeName); 489 | return currType.getMe(addr); 490 | } 491 | 492 | string toString(bool expand = true) 493 | { 494 | if (type is null) return "Error: null type*"; 495 | 496 | final switch(qualifier) with(Qualifier) 497 | { 498 | case None: return type.toString(expand); 499 | case Const: return "const(" ~ type.toString(expand) ~ ")"; 500 | case Immutable: return "immutable(" ~ type.toString(expand) ~ ")"; 501 | } 502 | assert(false); 503 | } 504 | } 505 | 506 | struct Type 507 | { 508 | import std.typecons; 509 | 510 | enum { Basic, Pointer, Class, Struct, DynamicArr, StaticArr, AssocArr } 511 | 512 | union 513 | { 514 | QualifiedType _ref; /// for pointer and array types 515 | Tuple!(string,"name",QualifiedType,"type",size_t,"offset")[] _object; /// for aggregates 516 | } 517 | 518 | uint flag; 519 | size_t length; // for static arrays 520 | size_t typeSize; 521 | string typeName; 522 | 523 | @property bool isBasic() { return flag == Basic; } 524 | @property bool isPointer() { return flag == Pointer; } 525 | @property bool isClass() { return flag == Class; } 526 | @property bool isStruct() { return flag == Struct; } 527 | @property bool isAggregate() { return flag == Struct || flag == Class; } 528 | @property bool isDynamicArr() { return flag == DynamicArr; } 529 | @property bool isStaticArr() { return flag == StaticArr; } 530 | @property bool isArray() { return flag == StaticArr || flag == DynamicArr; } 531 | @property bool isReference() { return flag == Class || flag == DynamicArr; } 532 | 533 | @property void* newBaseAddr(void* absAddr) 534 | { 535 | if (isClass || isPointer) 536 | return *cast(void**)absAddr; 537 | else if (isDynamicArr) 538 | return (*cast(void[]*)(absAddr)).ptr; 539 | else if (isStaticArr) 540 | return absAddr; 541 | else 542 | return absAddr; 543 | } 544 | 545 | size_t len(void* absAddr) 546 | { 547 | if (isDynamicArr) 548 | return *cast(size_t*)absAddr; 549 | else if (isStaticArr) 550 | return length; 551 | else 552 | return 0; 553 | } 554 | 555 | QualifiedType deref(ref void* absAddr) 556 | { 557 | import std.conv : to; 558 | assert(isPointer, flag.to!string()); 559 | absAddr = *cast(void**)absAddr; 560 | return _ref; 561 | } 562 | 563 | QualifiedType index(ref void* absAddr, size_t i) 564 | { 565 | import std.stdio : writeln; 566 | 567 | assert(isArray || isPointer); 568 | 569 | if (isArray && i >= len(absAddr)) 570 | { 571 | writeln("Out of bounds index: ", i); 572 | return QualifiedType(); 573 | } 574 | 575 | //absAddr = cast(void*)( (cast(size_t)newBaseAddr(absAddr)) + i * _ref.typeSize); 576 | absAddr = newBaseAddr(absAddr).padd(i * _ref.typeSize); 577 | return _ref; 578 | } 579 | 580 | QualifiedType slice(ref void* absAddr, size_t a, size_t b) 581 | { 582 | assert(false, "No support for slices."); 583 | } 584 | 585 | QualifiedType member(ref void* absAddr, string name) 586 | { 587 | import std.algorithm : find; 588 | import std.array : empty, front; 589 | 590 | assert(isAggregate); 591 | 592 | auto m = _object.find!((a,b) => a.name==b)(name); 593 | 594 | if (!m.empty) 595 | { 596 | //absAddr = cast(void*)((cast(size_t)newBaseAddr(absAddr)) + m.front.offset); 597 | absAddr = newBaseAddr(absAddr).padd(m.front.offset); 598 | return m.front.type; 599 | } 600 | return QualifiedType(); 601 | } 602 | 603 | string getMe(void* absAddr) 604 | { 605 | import std.stdio : write, writeln; 606 | import std.conv : to; 607 | 608 | if (trace) write("GetMe: ", this.typeName); 609 | 610 | enum types = ["byte", "ubyte", "char", "dchar", "wchar", 611 | "short", "ushort", "int", "uint", "long", "ulong", 612 | "float", "double", "real"]; 613 | 614 | static string generateCases(alias S, string Append = "")() 615 | { 616 | string res; 617 | foreach(s; S) 618 | res ~= "case `" ~ s ~ "`: return stringItAs!("~s~Append~")();\n"; 619 | return res ~ "default: return ``;\n"; 620 | } 621 | 622 | string stringItAs(T)() { return (*cast(T*)absAddr).to!string(); } 623 | 624 | if (isBasic) 625 | { 626 | if (trace) writeln(" (isBasic)"); 627 | switch(typeName) { mixin(generateCases!(types)()); } 628 | } 629 | else if (isAggregate) 630 | { 631 | if (trace) writeln(" (isAggregate)"); 632 | 633 | if (isClass) 634 | { 635 | if (trace) writeln(" isClass, getting address..."); 636 | absAddr = newBaseAddr(absAddr); 637 | } 638 | 639 | string s = typeName ~ "("; 640 | 641 | foreach(count, m; _object) 642 | { 643 | if (trace) writeln(" member ", m.name, " (offset: ", m.offset, ")"); 644 | //s ~= m.type.getMe(cast(void*)(cast(size_t)absAddr + m.offset)); 645 | s ~= m.type.getMe(absAddr.padd(m.offset)); 646 | if (count < _object.length - 1) s ~= ", "; 647 | } 648 | 649 | return s ~= ")"; 650 | } 651 | else if (isDynamicArr) 652 | { 653 | if (trace) writeln(" (isDynamicArr: length:", *cast(size_t*)absAddr, ")"); 654 | return _ref.getMeArray(newBaseAddr(absAddr), 0, *cast(size_t*)absAddr); 655 | } 656 | else if (isStaticArr) 657 | { 658 | if (trace) writeln(" (isStaticArr: length:", this.toString(), ")"); 659 | return _ref.getMeArray(newBaseAddr(absAddr), 0, length, true); 660 | } 661 | else if (isPointer) 662 | { 663 | if (trace) writeln(" (isPointer: ", absAddr, ")"); 664 | return (*cast(void**)absAddr).to!string(); 665 | } 666 | else 667 | { 668 | if (trace) writeln(" (no handled type!)"); 669 | return ""; 670 | } 671 | 672 | assert(false); 673 | } 674 | 675 | string getMeArray(void* baseAddr, size_t start, size_t stop, bool staticArr = false) 676 | { 677 | import std.range : iota; 678 | 679 | void* arrBase = baseAddr; 680 | string s = "["; 681 | foreach(i; iota(start, stop)) 682 | { 683 | //s ~= getMe(cast(void*)(cast(size_t)arrBase + i*typeSize)); 684 | s ~= getMe(arrBase.padd(i*typeSize)); 685 | if (i < stop - 1) s ~= ","; 686 | } 687 | return s ~ "]"; 688 | } 689 | 690 | string toString(bool expand = true) 691 | { 692 | import std.conv : to; 693 | 694 | string s; 695 | if (isPointer) 696 | { 697 | s ~= _ref.toString(false) ~ "*"; 698 | } 699 | else if (isDynamicArr) 700 | { 701 | s ~= _ref.toString(false) ~ "[]"; 702 | } 703 | else if (isStaticArr) 704 | { 705 | s ~= _ref.toString(false) ~ "[" ~ length.to!string() ~ "]"; 706 | } 707 | else if (isAggregate) 708 | { 709 | s ~= typeName; 710 | 711 | if (expand) 712 | { 713 | s ~= "("; 714 | 715 | foreach(i, m; _object) 716 | { 717 | s ~= m.name ~ ": " ~ m.type.toString(false); 718 | if (i < _object.length - 1) s ~= ", "; 719 | } 720 | 721 | s ~= ")"; 722 | } 723 | } 724 | else if (isBasic) 725 | { 726 | s = typeName; 727 | } 728 | return s; 729 | } 730 | } 731 | 732 | private 733 | { 734 | void* padd(void* p, size_t i) 735 | { 736 | return cast(void*)(cast(size_t)p + i); 737 | } 738 | } 739 | 740 | 741 | /** 742 | * Operations to carry out in a print expression. 743 | */ 744 | struct Operation 745 | { 746 | enum Op { Deref, Index, Slice, Cast, Member } 747 | Op op; 748 | string val, val2; 749 | } 750 | 751 | 752 | /** 753 | * Run buildType for the built-in types. 754 | */ 755 | void buildBasicTypes(ref Type*[string] map) 756 | { 757 | import std.typetuple : TypeTuple; 758 | 759 | foreach(T; TypeTuple!(byte, ubyte, char, dchar, wchar, int, uint, 760 | short, ushort, long, ulong, float, double, real, 761 | void*, void[])) 762 | buildType!T(map); 763 | } 764 | 765 | 766 | /** 767 | * Dynamic (but simple) type building. 768 | */ 769 | QualifiedType buildType()(string typeString, ref Type*[string] map) 770 | { 771 | import std.array: front, empty, popFront; 772 | import std.conv : to; 773 | import std.stdio : writeln; 774 | 775 | bool isIdentChar(dchar c) 776 | { 777 | return (c >= 'A' && c <= 'Z') || 778 | (c >= 'a' && c <= 'z') || 779 | (c >= '0' && c <= '9') || 780 | (c == '_'); 781 | } 782 | 783 | if (typeString in map) 784 | return QualifiedType(map[typeString]); 785 | 786 | string ident; 787 | while(!typeString.empty && isIdentChar(typeString.front)) 788 | { 789 | ident ~= typeString.front; 790 | typeString.popFront(); 791 | } 792 | 793 | void skipPast(ref string s, dchar term) 794 | { 795 | while(!s.empty && s.front != term) 796 | s.popFront(); 797 | if (!s.empty) 798 | s.popFront(); 799 | } 800 | 801 | if (ident in map) 802 | { 803 | auto baseType = QualifiedType(map[ident]); // current type 804 | 805 | // Get *, [], [number] 806 | while(!typeString.empty) 807 | { 808 | switch(typeString.front) 809 | { 810 | case '*': 811 | typeString.popFront(); 812 | QualifiedType qt; 813 | qt.type = new Type; 814 | qt.type.flag = Type.Pointer; 815 | qt.type._ref = baseType; 816 | qt.type.typeName = ident ~ "*"; 817 | qt.type.typeSize = (void*).sizeof; 818 | map[qt.type.typeName.idup] = qt; 819 | baseType = qt; 820 | break; 821 | 822 | case '[': 823 | string len; 824 | typeString.popFront(); 825 | while(!typeString.empty && typeString.front != ']') 826 | { 827 | if (typeString.front >= '0' && typeString.front <= '9') 828 | { 829 | len ~= typeString.front; 830 | typeString.popFront(); 831 | } 832 | else 833 | { 834 | assert(false); 835 | } 836 | } 837 | 838 | skipPast(typeString, ']'); 839 | 840 | if (len.length > 0) // static array 841 | { 842 | QualifiedType qt; 843 | qt.type = new Type; 844 | qt.type.flag = Type.StaticArr; 845 | qt.type._ref = baseType; 846 | qt.type.length = len.to!size_t(); 847 | qt.type.typeName = ident ~ "[" ~ len ~ "]"; 848 | qt.type.typeSize = (baseType.typeSize)*qt.type.length; 849 | map[qt.type.typeName.idup] = qt; 850 | baseType = qt; 851 | } 852 | else // dynamic array 853 | { 854 | QualifiedType qt; 855 | qt.type = new Type; 856 | qt.type.flag = Type.DynamicArr; 857 | qt.type._ref = baseType; 858 | qt.type.typeName = ident ~ "[]"; 859 | qt.type.typeSize = (void[]).sizeof; 860 | map[qt.type.typeName.idup] = qt; 861 | baseType = qt; 862 | } 863 | break; 864 | 865 | default: 866 | typeString.popFront(); 867 | break; 868 | } 869 | } 870 | 871 | if (trace) writeln("Dynamic build type: ", baseType.toString()); 872 | return baseType; 873 | } 874 | 875 | return QualifiedType(); 876 | } 877 | 878 | 879 | template typeQualifier(T) 880 | { 881 | static if (is(T _ == const U, U)) 882 | enum typeQualifier = QualifiedType.Qualifier.Const; 883 | else static if (is(T _ == immutable U, U)) 884 | enum typeQualifier = QualifiedType.Qualifier.Immutable; 885 | else 886 | enum typeQualifier = QualifiedType.Qualifier.None; 887 | } 888 | 889 | 890 | /** 891 | * Static type building. 892 | */ 893 | QualifiedType buildType(T)(ref Type*[string] map, QualifiedType* ptr = null) 894 | { 895 | import std.stdio: writeln; 896 | import std.algorithm : splitter; 897 | import std.typecons : Tuple; 898 | import std.array : array; 899 | import std.traits : 900 | Unqual, 901 | PointerTarget, 902 | isBasicType, 903 | isPointer, 904 | isFunctionPointer, 905 | isAggregateType, 906 | isArray, 907 | isStaticArray, 908 | isDynamicArray, 909 | isAssociativeArray; 910 | 911 | 912 | static if (!__traits(compiles, T.sizeof)) 913 | return QualifiedType(); 914 | else { 915 | 916 | alias typeName!T name; /// unqualified type name 917 | 918 | if (name in map) 919 | { 920 | if (trace) writeln("buildType: retrieving ", name); 921 | if (ptr !is null) *ptr.type = *map[name]; 922 | return QualifiedType(map[name], typeQualifier!T); 923 | } 924 | 925 | QualifiedType qt = QualifiedType(null, typeQualifier!T); 926 | 927 | // This is to avoid problems with circular deps 928 | qt.type = ptr ? ptr.type : new Type; 929 | qt.type.typeName = name.idup; 930 | qt.type.typeSize = T.sizeof; 931 | 932 | map[name.idup] = qt; // store it here to avoid infinite recursion 933 | 934 | if (trace) writeln("buildType: building ", name); 935 | 936 | static if (isAggregateType!T) 937 | { 938 | if (trace) writeln("buildType: type is aggregate"); 939 | 940 | static if (is(T == class)) 941 | qt.type.flag = Type.Class; 942 | else 943 | qt.type.flag = Type.Struct; 944 | 945 | foreach(i; Iota!(0, T.tupleof.length)) 946 | { 947 | alias typeof(T.tupleof[i]) _Type; 948 | if (trace) writeln("buildType: aggregate member ", _Type.stringof); 949 | static if (!isFunctionPointer!_Type) 950 | { 951 | enum _name = ((splitter(T.tupleof[i].stringof, ".")).array())[$-1]; 952 | enum _offset = T.tupleof[i].offsetof; 953 | mixin("qt.type._object ~= Tuple!(string,`name`,QualifiedType,`type`,size_t,`offset`)(`"~_name~"`.idup,buildType!(_Type)(map), _offset);"); 954 | } 955 | } 956 | 957 | // Here we get inner defs, that may not be directly used by the type 958 | foreach(m; __traits(allMembers, T)) 959 | static if (__traits(compiles, mixin("{TypeBuilder.buildType!(T."~m~")(map);}"))) 960 | mixin("TypeBuilder.buildType!(T."~m~")(map);"); 961 | } 962 | else static if (isPointer!T) 963 | { 964 | if (trace) writeln("buildType: type is pointer"); 965 | 966 | qt.type.flag = Type.Pointer; 967 | qt.type._ref.type = new Type; 968 | buildType!(PointerTarget!T)(map, &qt.type._ref); 969 | } 970 | else static if (isArray!T) 971 | { 972 | if (trace) writeln("buildType: type is array"); 973 | 974 | static if (!isStaticArray!T) 975 | qt.type.flag = Type.DynamicArr; 976 | else 977 | { 978 | qt.type.flag = Type.StaticArr; 979 | qt.type.length = T.length; 980 | } 981 | qt.type._ref.type = new Type; 982 | buildType!(ArrayElement!T)(map, &qt.type._ref); 983 | } 984 | else static if (isAssociativeArray!T) 985 | { 986 | import std.traits: KeyType, ValueType; 987 | 988 | qt.type = buildType!(object.AssociativeArray!(KeyType!T, ValueType!T))(map); 989 | } 990 | else 991 | { 992 | static assert(isBasicType!T); 993 | if (trace) writeln("buildType: type is basic"); 994 | qt.type.flag = Type.Basic; 995 | } 996 | 997 | if (trace && qt.type) writeln("buildType: type is ", name, ", ", qt.type.toString()); 998 | return qt; 999 | } 1000 | } 1001 | 1002 | 1003 | /** 1004 | * String name for a type. 1005 | */ 1006 | template typeName(T) 1007 | { 1008 | import std.traits : 1009 | Unqual, 1010 | isStaticArray, 1011 | PointerTarget, 1012 | isDynamicArray, 1013 | isPointer, 1014 | isAggregateType; 1015 | 1016 | alias Unqual!(T) Type; 1017 | static if (isAggregateType!T) 1018 | enum typeName = Type.stringof; 1019 | else static if (isStaticArray!T) 1020 | enum typeName = typeName!(ArrayElement!T) ~ "[" ~ T.length.to!string() ~ "]"; 1021 | else static if (isDynamicArray!T) 1022 | enum typeName = typeName!(ArrayElement!T) ~ "[]"; 1023 | else static if (isPointer!T) 1024 | enum typeName = typeName!(PointerTarget!T) ~ "*"; 1025 | else 1026 | enum typeName = Type.stringof; 1027 | } 1028 | 1029 | 1030 | /** 1031 | * Alias to the element type of an array. 1032 | */ 1033 | template ArrayElement(T) 1034 | { 1035 | static if (is(T _ : U[], U)) 1036 | alias U ArrayElement; 1037 | else static if (is(T _ : U[V], U, V)) 1038 | static assert(false, "Assoc Array"); 1039 | else 1040 | static assert(false); 1041 | } 1042 | 1043 | 1044 | /** 1045 | * For static foreach. 1046 | */ 1047 | template Iota(size_t i, size_t n) 1048 | { 1049 | import std.typetuple : TypeTuple; 1050 | 1051 | static if (n == 0) 1052 | alias TypeTuple!() Iota; 1053 | else 1054 | alias TypeTuple!(i, Iota!(i + 1, n - 1)) Iota; 1055 | } -------------------------------------------------------------------------------- /src/dabble/grammars.d: -------------------------------------------------------------------------------- 1 | 2 | module dabble.grammars; 3 | 4 | import 5 | std.stdio, 6 | std.typecons; 7 | 8 | import 9 | pegged.peg, 10 | pegged.grammar; 11 | 12 | import dabble.defs : Operation; 13 | 14 | 15 | /** 16 | * Used for detecting multi-line input 17 | */ 18 | struct Balanced 19 | { 20 | static: 21 | 22 | int braceCount; 23 | 24 | bool test(string code) 25 | { 26 | braceCount = 0; 27 | return BB.BalancedBraces(code).successful; 28 | } 29 | 30 | T incBraceCount(T)(T t) 31 | { 32 | if (t.successful) 33 | braceCount++; 34 | return t; 35 | } 36 | } 37 | 38 | mixin(grammar(` 39 | 40 | BB: 41 | 42 | BalancedBraces <~ (~Until('{', .) (eoi / (~BwBraces){Balanced.incBraceCount} ))+ 43 | 44 | Until(T, U) <- (!(T/eoi) (Comment/String/CharLiteral/U))* 45 | BwBraces(T=.) <- Nested('{', Comment / String / CharLiteral / T, '}') 46 | Nested(L,Items,R) <- ^L (!R (Nested(L,Items,R) / blank / Items))* ^R 47 | String <- (WYSString / DBQString / TKNString / DLMString) 48 | WYSString <~ 'r' doublequote (!doublequote .)* doublequote / backquote (!backquote .)* backquote 49 | DBQString <~ doublequote (!doublequote Char)* doublequote 50 | TKNString <~ (&'q{' ('q' NestedList('{',String,'}'))) 51 | DLMString <~ ('q' doublequote) ( (&'{' NestedList('{',String,'}')) 52 | / (&'[' NestedList('[',String,']')) 53 | / (&'(' NestedList('(',String,')')) 54 | / (&'<' NestedList('<',String,'>')) 55 | ) doublequote 56 | 57 | Char <~ backslash ( quote / doublequote / backquote / backslash 58 | / '-' / '[' / ']' 59 | / [nrt] 60 | / [0-2][0-7][0-7] / [0-7][0-7]? 61 | / 'x' hexDigit hexDigit 62 | / 'u' hexDigit hexDigit hexDigit hexDigit 63 | / 'U' hexDigit hexDigit hexDigit hexDigit hexDigit hexDigit hexDigit hexDigit 64 | ) / . # or anything else 65 | 66 | CharLiteral <~ quote Char quote 67 | Comment <~ (LineComment / BlockComment / NestingBlockComment) 68 | LineComment <- "//" (!(endOfLine/eoi) .)* (endOfLine/eoi) 69 | BlockComment <- "/*" (!"*/" .)* "*/" 70 | NestingBlockComment <- NestedList("/+","+/") 71 | NestedList(L,Items,R) <- ^L ( !(L/R/Items) . )* ( Items / NestedList(L,Items,R) / ( !(L/R/Items) . )* )* ( !(L/R/Items) . )* ^R 72 | NestedList(L,R) <- ^L ( !(L/R) . )* (NestedList(L,R) / ( !(L/R) . )* )* ( !(L/R) . )* ^R 73 | `)); 74 | 75 | 76 | /** 77 | * Handle meta commands 78 | */ 79 | mixin(grammar(` 80 | 81 | MetaParser: 82 | 83 | MetaCommand <- MetaPrint MetaArgs? 84 | / MetaType MetaArgs? 85 | / MetaDelete MetaArgs 86 | / MetaReset MetaArgs 87 | / MetaDebugOn MetaArgs 88 | / MetaDebugOff MetaArgs 89 | / MetaUse MetaArgs 90 | / MetaClear MetaArgs? 91 | / MetaVersion 92 | / MetaHistory 93 | 94 | MetaPrint <- 'print' 95 | MetaType <- 'type' 96 | MetaDelete <- 'delete' 97 | MetaReset <- 'reset' 98 | MetaUse <- 'use' 99 | MetaDebugOn <~ ('debug' wx 'on') 100 | MetaDebugOff <~ ('debug' wx 'off') 101 | MetaClear <- 'clear' 102 | MetaVersion <- 'version' 103 | MetaHistory <- 'history' 104 | 105 | MetaArgs <- (wxd Seq(MetaArg, ',')) 106 | MetaArg <- ~((!(endOfLine / ',') .)*) 107 | 108 | w <- ' ' / '\t' / endOfLine 109 | wx <- ;(w?) :(w*) 110 | wxd <- :(w*) 111 | Seq(T, Sep) <- wxd T wxd (Sep wxd T wxd)* 112 | 113 | `)); 114 | 115 | 116 | /** 117 | * Print-expression parser 118 | */ 119 | mixin(grammar(` 120 | 121 | ExprParser: 122 | 123 | Expr < ( IndexExpr / SubExpr / CastExpr / DerefExpr / MemberExpr / Ident )* 124 | 125 | SubExpr < '(' Expr ')' 126 | 127 | Ident < identifier 128 | Number <~ [0-9]+ 129 | Index < '[' Number ']' 130 | CastTo <~ (!')' .)* 131 | 132 | IndexExpr < (SubExpr / MemberExpr / Ident) Index+ 133 | CastExpr < 'cast' '(' CastTo ')' Expr 134 | DerefExpr < '*' Expr 135 | MemberExpr < '.' Ident 136 | 137 | `)); 138 | 139 | 140 | Tuple!(string, Operation[]) parseExpr(string s) 141 | { 142 | Operation[] list; 143 | auto p = ExprParser(s); 144 | expressionList(p, list); 145 | return tuple(list[0].val, list[1..$]); 146 | } 147 | 148 | 149 | void expressionList(ParseTree p, ref Operation[] list) 150 | { 151 | enum Prefix = "ExprParser"; 152 | switch(p.name) with(Operation) 153 | { 154 | case Prefix: 155 | expressionList(p.children[0], list); 156 | break; 157 | case Prefix ~ ".Expr": 158 | foreach(c; p.children) 159 | expressionList(c, list); 160 | break; 161 | case Prefix ~ ".SubExpr": 162 | expressionList(p.children[0], list); 163 | break; 164 | case Prefix ~ ".IndexExpr": 165 | expressionList(p.children[0], list); 166 | foreach(c; p.children[1..$]) 167 | list ~= Operation(Op.Index, c.children[0].matches[0]); 168 | break; 169 | case Prefix ~ ".CastExpr": 170 | expressionList(p.children[1], list); 171 | list ~= Operation(Op.Cast, p.children[0].matches[0]); 172 | break; 173 | case Prefix ~ ".DerefExpr": 174 | expressionList(p.children[0], list); 175 | list ~= Operation(Op.Deref); 176 | break; 177 | case Prefix ~ ".MemberExpr": 178 | list ~= Operation(Op.Member, p.children[0].matches[0]); 179 | break; 180 | case Prefix ~ ".Ident": 181 | list ~= Operation(Op.Member, p.matches[0]); 182 | break; 183 | default: writeln("No clause for ", p.name); 184 | } 185 | } 186 | 187 | 188 | -------------------------------------------------------------------------------- /src/dabble/main.d: -------------------------------------------------------------------------------- 1 | /** 2 | Written in the D programming language. 3 | 4 | Main entry point for command-line version of Dabble. 5 | 6 | Copyright: Copyright Callum Anderson 2013 7 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Callum Anderson 9 | **/ 10 | 11 | module dabble.main; 12 | 13 | import dabble.repl; 14 | 15 | void main(char[][] args) 16 | { 17 | scope(exit) { onExit(); } 18 | parseArgs(args[1..$]); 19 | 20 | import dabble.testing; 21 | //testAll(); 22 | 23 | loop(); 24 | } 25 | 26 | void parseArgs(char[][] args) 27 | { 28 | import std.string : toLower; 29 | import std.stdio: writeln; 30 | 31 | foreach(arg; args) 32 | { 33 | switch(arg.toLower()) 34 | { 35 | case "--noconsole": dabble.repl.consoleSession = false; break; 36 | case "--showtimes": addDebugLevel(Debug.times); break; 37 | case "--parseonly": addDebugLevel(Debug.parseOnly); break; 38 | default: writeln("Unrecognized argument: ", arg); break; 39 | } 40 | } 41 | } 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/dabble/meta.d: -------------------------------------------------------------------------------- 1 | 2 | module dabble.meta; 3 | 4 | import 5 | std.stdio, 6 | std.conv; 7 | 8 | import 9 | dabble.repl, 10 | dabble.grammars; 11 | 12 | 13 | /** 14 | * See if the given buffer contains meta commands, which are interpreted directly 15 | * and do not trigger recompilation. 16 | */ 17 | bool handleMetaCommand(ref string inBuffer, ref string codeBuffer) 18 | { 19 | import std.conv : text; 20 | import std.process : system; 21 | import std.string : join; 22 | import std.algorithm : canFind, map, find; 23 | import std.range : array, front, empty; 24 | 25 | auto origCommand = inBuffer.to!string(); 26 | auto parse = MetaParser.decimateTree(MetaParser(origCommand)); 27 | 28 | if (!parse.successful) 29 | return false; 30 | 31 | parse = parse.children[0]; 32 | auto cmd = parse.children[0].matches[0]; 33 | 34 | string[] args; 35 | if (parse.children.length == 2) 36 | { 37 | if (parse.children[1].name == "MetaParser.MetaArgs") 38 | { 39 | auto seq = parse.children[1].children[0].children; 40 | args.length = seq.length; 41 | foreach(i, p; seq) 42 | args[i] = p.matches[0]; 43 | } 44 | } 45 | 46 | alias T = Tuple!(string, Operation[]); 47 | 48 | /** Value print helper **/ 49 | string printValue(T p) 50 | { 51 | auto v = context.share.vars.find!((a,b) => a.name == b)(p[0]); 52 | if (v.empty) return ""; 53 | return v.front.ty !is null ? 54 | v.front.ty.valueOf(p[1], v.front.addr, context.share.map) : 55 | v.front.func ? v.front.init : ""; 56 | } 57 | 58 | /** Type print helper **/ 59 | string printType(T p) 60 | { 61 | auto v = context.share.vars.find!((a,b) => a.name == b)(p[0]); 62 | if (v.empty) return ""; 63 | if (v.front.ty is null) return v.front.displayType; 64 | auto t = v.front.ty.typeOf(p[1], context.share.map); 65 | return t[1].length ? t[1] : t[0].toString(); 66 | } 67 | 68 | string summary, jsonstr; 69 | 70 | switch(cmd) 71 | { 72 | case "version": 73 | { 74 | summary = title(); 75 | jsonstr = json("id","meta","cmd","version","summary",title()); 76 | break; 77 | } 78 | case "history": // return raw code, history of d code in this session 79 | { 80 | summary = context.rawCode.toString(); 81 | jsonstr = json("id", "meta", "cmd", "history", "summary", summary.escapeJSON()); 82 | break; 83 | } 84 | case "print": with(context.share) 85 | { 86 | if (args.length == 0 || canFind(args, "all")) // print all vars 87 | { 88 | summary = vars.map!(v => text(v.name, " (", v.displayType, ") = ", printValue(T(v.name,[])))).join("\n"); 89 | jsonstr = json("id", "meta", "cmd", "print", "summary", summary.escapeJSON(), "data", 90 | vars.map!(v => tuple("name", v.name, "type", v.displayType.escapeJSON(), "value", printValue(T(v.name,[])).escapeJSON())).array); 91 | } 92 | else if (args.length == 1 && args[0] == "__keepAlive") 93 | { 94 | summary = "SharedLibs still alive:" ~ .keepAlive.map!(a => a.to!string()).join("\n"); 95 | jsonstr = json("id", "meta", "cmd", "__keepAlive", "summary", summary.escapeJSON(), "data", .keepAlive.map!(a => a.to!string()).array); 96 | } 97 | else // print selected symbols 98 | { 99 | summary = args.map!(a => printValue(parseExpr(a))).join("\n"); 100 | jsonstr = json("id", "meta", "cmd", "print", "summary", summary.escapeJSON(), "data", args.map!(a => printValue(parseExpr(a)).escapeJSON()).array); 101 | } 102 | break; 103 | } 104 | case "type": with(context.share) 105 | { 106 | if (args.length == 0 || canFind(args, "all")) // print types of all vars 107 | { 108 | summary = vars.map!(v => text(v.name, " ") ~ printType(T(v.name,[]))).join("\n"); 109 | jsonstr = json("id", "meta", "cmd", "type", "summary", summary.escapeJSON(), "data", 110 | vars.map!(v => tuple("name", v.name, "type", printType(T(v.name,[])).escapeJSON())).array); 111 | } 112 | else 113 | { 114 | summary = args.map!(a => printType(parseExpr(a))).join("\n"); 115 | jsonstr = json("id", "meta", "cmd", "type", "summary", summary.escapeJSON(), "data", args.map!(a => printType(parseExpr(a)).escapeJSON()).array); 116 | } 117 | break; 118 | } 119 | case "reset": 120 | { 121 | if (canFind(args, "session")) 122 | { 123 | context.reset(); 124 | keepAlive.clear(); 125 | summary = "Session reset"; 126 | jsonstr = json("id", "meta", "cmd", "reset session", "summary", "Session reset"); 127 | } 128 | break; 129 | } 130 | case "delete": 131 | { 132 | foreach(a; args) 133 | context.share.deleteVar(a); 134 | break; 135 | } 136 | case "use": 137 | { 138 | import std.file : exists; 139 | import std.range : ElementType; 140 | import std.path : dirName, baseName, dirSeparator; 141 | import std.datetime : SysTime; 142 | alias ElementType!(typeof(context.userModules)) TupType; 143 | 144 | string[] msg; 145 | foreach(a; args) 146 | { 147 | if (exists(a)) 148 | { 149 | context.userModules ~= TupType(dirName(a), baseName(a), SysTime(0).stdTime()); 150 | continue; 151 | } 152 | 153 | // see if the file exists in dabble src directory (like delve.d does) 154 | auto altPath = [replPath(), "src", "dabble", a].join(dirSeparator); 155 | if (exists(altPath)) 156 | { 157 | context.userModules ~= TupType(dirName(altPath), baseName(altPath), SysTime(0).stdTime()); 158 | continue; 159 | } 160 | 161 | // else fail 162 | msg ~= text("Error: module ", a, " could not be found"); 163 | } 164 | 165 | summary = msg.join("\n"); 166 | jsonstr = json("id", "meta", "cmd", "use", "summary", summary.escapeJSON(), "data", msg); 167 | break; 168 | } 169 | case "clear": 170 | { 171 | if (args.length == 0) 172 | clearScreen(); 173 | else if (args.length == 1 && args[0] == "buffer") 174 | codeBuffer.clear(); 175 | break; 176 | } 177 | case "debug on": 178 | foreach(arg; args) 179 | setDebugLevelFromString!"on"(arg); 180 | break; 181 | case "debug off": 182 | foreach(arg; args) 183 | setDebugLevelFromString!"off"(arg); 184 | break; 185 | default: 186 | return false; 187 | } 188 | 189 | // If we got to here, we successfully parsed a meta command, so clear the code buffer 190 | consoleSession ? summary.send : jsonstr.send; 191 | codeBuffer.clear(); 192 | return true; 193 | } 194 | -------------------------------------------------------------------------------- /src/dabble/parser.d: -------------------------------------------------------------------------------- 1 | /** 2 | Written in the D programming language. 3 | 4 | Parser 5 | 6 | Copyright: Copyright Callum Anderson 2013 7 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Callum Anderson 9 | **/ 10 | 11 | module dabble.parser; 12 | 13 | import 14 | std.stdio, 15 | std.array, 16 | std.conv, 17 | std.typecons, 18 | std.range, 19 | std.algorithm; 20 | 21 | import 22 | stdx.d.lexer, 23 | stdx.d.parser, 24 | stdx.d.ast; 25 | 26 | 27 | void hideMessages(string, size_t, size_t, string, bool) { } 28 | 29 | class DabbleParser : Parser 30 | { 31 | alias Insert = Tuple!(size_t, size_t); // index, length 32 | Insert[] inserts; // sorted list of inserts 33 | 34 | LexerConfig config; 35 | string source, original, lastInit; 36 | 37 | Tuple!(size_t, size_t, string)[] errors; // line, col, msg 38 | string[] types; 39 | string[][] params; 40 | bool declCanBeGlobal; 41 | size_t blockDepth = 0, declStart = 0, funcLiteralDepth = 0; 42 | 43 | bool delegate(string) redirectVar; 44 | void delegate(bool,string,string) newDecl; 45 | void delegate(string,string,string,string) newVar; 46 | 47 | string parse(string _source, 48 | bool delegate(string) _redirectVar, 49 | void delegate(bool,string,string) _newDecl, 50 | void delegate(string,string,string,string) _newVar) 51 | { 52 | messageFunction = &hideMessages; 53 | 54 | redirectVar = _redirectVar; 55 | newDecl = _newDecl; 56 | newVar = _newVar; 57 | 58 | source = _source; 59 | original = _source; 60 | 61 | lastInit = ""; 62 | blockDepth = 0; 63 | errors.clear; 64 | inserts.clear; 65 | 66 | /// Reset parent state 67 | StringCache* cache = new StringCache(StringCache.defaultBucketCount); 68 | tokens = byToken(cast(ubyte[]) source, config, cache).array(); 69 | suppressMessages = 0; 70 | index = 0; 71 | 72 | parseDeclarationsAndStatements(); 73 | return source; 74 | } 75 | 76 | 77 | override void error(lazy string message, bool shouldAdvance = true) 78 | { 79 | if (!suppressMessages) 80 | { 81 | size_t column = index < tokens.length ? tokens[index].column : tokens[$ - 1].column; 82 | size_t line = index < tokens.length ? tokens[index].line : tokens[$ - 1].line; 83 | errors ~= tuple(line, column, message); 84 | } 85 | super.error(message, shouldAdvance); 86 | } 87 | 88 | 89 | /** 90 | * Map original text indices to modified text indices 91 | */ 92 | Tuple!(size_t, size_t) mapIndices(size_t from, size_t to) in {assert(to >= from);} body 93 | { 94 | auto modFrom = from; 95 | auto len = to - from; 96 | 97 | foreach(i; inserts) 98 | { 99 | if (i[0] < from) 100 | modFrom += i[1]; 101 | if (from <= i[0] && i[0] <= to) 102 | len += i[1]; 103 | if (i[0] > to) 104 | break; 105 | } 106 | return tuple(modFrom, modFrom + len); 107 | } 108 | 109 | 110 | /** 111 | * Insert text into modified text starting at mapped index 112 | */ 113 | void insert(size_t index, string text, bool after = false) 114 | { 115 | size_t add = 0, pos = 0; 116 | for(; pos < inserts.length; pos++) { 117 | if (!after && inserts[pos][0] >= index) break; 118 | if (after && inserts[pos][0] > index) break; 119 | add += inserts[pos][1]; 120 | } 121 | 122 | source.insertInPlace(index + add, text); 123 | 124 | if (pos >= inserts.length) 125 | inserts ~= Insert(index, text.length); 126 | else 127 | inserts[pos][1] += text.length; 128 | } 129 | 130 | 131 | /** 132 | * Blank text between given indices in both original and modified text. 133 | */ 134 | void blank(size_t from, size_t to) 135 | { 136 | auto t = mapIndices(from, to); 137 | source.replaceInPlace(t[0], t[1], iota(t[1]-t[0]).map!(x=>" ")().joiner()); 138 | original.replaceInPlace(from, to, iota(to-from).map!(x=>" ")().joiner()); 139 | } 140 | 141 | 142 | /** 143 | * Get the modified code, using indices from the unmodified array. 144 | */ 145 | string grab(size_t from, size_t to) 146 | { 147 | auto t = mapIndices(from, to); 148 | return source[t[0]..t[1]]; 149 | } 150 | 151 | size_t charIndex() 152 | { 153 | return index < tokens.length ? tokens[index].index : original.length; 154 | } 155 | 156 | auto wrap(E)(lazy E func) 157 | { 158 | auto s = charIndex(); 159 | auto r = func; 160 | auto e = charIndex(); 161 | return tuple(r,s,e); 162 | } 163 | 164 | override DeclarationOrStatement parseDeclarationOrStatement() 165 | { 166 | static depth = 0; 167 | 168 | if (!suppressMessages && depth == 0) 169 | { 170 | declStart = charIndex(); 171 | declCanBeGlobal = true; 172 | types.clear(); 173 | params.clear(); 174 | } 175 | 176 | if (!suppressMessages) depth++; 177 | 178 | auto t = wrap(super.parseDeclarationOrStatement()); 179 | 180 | if (!suppressMessages) depth--; 181 | return t[0]; 182 | } 183 | 184 | override Expression parseExpression() 185 | { 186 | auto t = wrap(super.parseExpression()); 187 | expr(t[1], t[2]); 188 | return t[0]; 189 | } 190 | 191 | 192 | override FunctionLiteralExpression parseFunctionLiteralExpression() 193 | { 194 | funcLiteralDepth++; 195 | auto t = wrap(super.parseFunctionLiteralExpression()); 196 | funcLiteralDepth--; 197 | return t[0]; 198 | } 199 | 200 | /** 201 | * Need to override whole function as node.identifier is a parameter but is found too late 202 | */ 203 | override LambdaExpression parseLambdaExpression() 204 | { 205 | funcLiteralDepth++; 206 | 207 | mixin(traceEnterAndExit!(__FUNCTION__)); 208 | auto node = new LambdaExpression; 209 | if (currentIsOneOf(tok!"function", tok!"delegate")) 210 | { 211 | node.functionType = advance().type; 212 | goto lParen; 213 | } 214 | else if (currentIs(tok!"identifier")) 215 | { 216 | node.identifier = advance(); 217 | params = [node.identifier.text] ~ params; 218 | } 219 | else if (currentIs(tok!"(")) 220 | { 221 | lParen: 222 | node.parameters = parseParameters(); 223 | do 224 | { 225 | auto attribute = parseFunctionAttribute(false); 226 | if (attribute is null) 227 | break; 228 | node.functionAttributes ~= attribute; 229 | } 230 | while (moreTokens()); 231 | } 232 | else 233 | { 234 | error(`Identifier or argument list expected`); 235 | return null; 236 | } 237 | 238 | if (expect(tok!"=>") is null) return null; 239 | 240 | if ((node.assignExpression = parseAssignExpression()) is null) 241 | return null; 242 | 243 | funcLiteralDepth--; 244 | return node; 245 | } 246 | 247 | override Parameters parseParameters() 248 | { 249 | auto t = wrap(super.parseParameters()); 250 | if (t[0] is null) 251 | return null; 252 | if (t[0].parameters.length) 253 | params = [t[0].parameters.map!( x => x.name.text )().array()] ~ params; 254 | return t[0]; 255 | } 256 | 257 | override VariableDeclaration parseVariableDeclaration(Type type = null, bool isAuto = false) 258 | { 259 | auto t = wrap(super.parseVariableDeclaration(type, isAuto)); 260 | varDecl(t[1], t[2], t[0], isAuto); 261 | return t[0]; 262 | } 263 | 264 | override FunctionDeclaration parseFunctionDeclaration(Type type = null, bool isAuto = false) 265 | { 266 | auto t = wrap(super.parseFunctionDeclaration(type, isAuto)); 267 | userTypeDecl(declStart, t[2], "function"); 268 | return t[0]; 269 | } 270 | 271 | override StructDeclaration parseStructDeclaration() 272 | { 273 | auto t = wrap(super.parseStructDeclaration()); 274 | userTypeDecl(t[1], t[2], "struct"); 275 | return t[0]; 276 | } 277 | 278 | override ClassDeclaration parseClassDeclaration() 279 | { 280 | auto t = wrap(super.parseClassDeclaration()); 281 | userTypeDecl(t[1], t[2], "class"); 282 | return t[0]; 283 | } 284 | 285 | override ImportDeclaration parseImportDeclaration() 286 | { 287 | auto t = wrap(super.parseImportDeclaration()); 288 | userTypeDecl(t[1], t[2], "import"); 289 | return t[0]; 290 | } 291 | 292 | override EnumDeclaration parseEnumDeclaration() 293 | { 294 | auto t = wrap(super.parseEnumDeclaration()); 295 | userTypeDecl(t[1], t[2], "enum"); 296 | return t[0]; 297 | } 298 | 299 | override AliasDeclaration parseAliasDeclaration() 300 | { 301 | auto t = wrap(super.parseAliasDeclaration()); 302 | userTypeDecl(t[1], t[2], "alias"); 303 | return t[0]; 304 | } 305 | 306 | override Type parseType() 307 | { 308 | static size_t depth = 0, start = 0; 309 | 310 | if (!suppressMessages && !blockDepth && depth == 0) 311 | start = charIndex(); 312 | 313 | if (!suppressMessages) depth++; 314 | 315 | auto t = wrap(super.parseType()); 316 | 317 | if (!suppressMessages) depth--; 318 | 319 | if (!suppressMessages && !blockDepth && depth == 0) 320 | types = original[start..charIndex()] ~ types; 321 | 322 | return t[0]; 323 | } 324 | 325 | override Initializer parseInitializer() 326 | { 327 | static size_t depth = 0, start = 0; 328 | 329 | if (!suppressMessages) 330 | { 331 | if (depth == 0) 332 | start = charIndex(); 333 | depth++; 334 | } 335 | 336 | auto t = wrap(super.parseInitializer()); 337 | 338 | if (!suppressMessages) 339 | { 340 | depth--; 341 | if (depth == 0) 342 | lastInit = grab(t[1],t[2]); 343 | } 344 | return t[0]; 345 | } 346 | 347 | override IdentifierOrTemplateInstance parseIdentifierOrTemplateInstance() 348 | { 349 | import std.string : strip; 350 | 351 | auto i = index; 352 | auto t = wrap(super.parseIdentifierOrTemplateInstance()); 353 | 354 | if (suppressMessages) 355 | return t[0]; 356 | 357 | if (i == 0 || (i > 0 && tokens[i - 1].type != tok!".")) 358 | { 359 | auto ident = strip(original[t[1]..t[2]]); 360 | bool redirect = redirectVar(ident); 361 | 362 | if (funcLiteralDepth && params.length && params[0].canFind(ident)) 363 | redirect = false; 364 | 365 | if (redirect) 366 | { 367 | declCanBeGlobal = false; 368 | insert(t[1], "(*"); 369 | insert(t[2], ")"); 370 | } 371 | } 372 | 373 | return t[0]; 374 | } 375 | 376 | override PrimaryExpression parsePrimaryExpression() 377 | { 378 | auto t = wrap(super.parsePrimaryExpression()); 379 | 380 | if (t[0] is null) 381 | return null; 382 | 383 | if (t[0].primary.type == tok!"stringLiteral" || 384 | t[0].primary.type == tok!"dstringLiteral" || 385 | t[0].primary.type == tok!"wstringLiteral" ) 386 | { 387 | insert(t[2], ".idup"); 388 | } 389 | return t[0]; 390 | } 391 | 392 | static string makeBlocks() 393 | { 394 | enum blocks = [ 395 | ["BlockStatement", "parseBlockStatement()"], 396 | ["StructBody", "parseStructBody()"], 397 | ["ForeachStatement", "parseForeachStatement()"], 398 | ["ForStatement", "parseForStatement()"], 399 | ["WhileStatement", "parseWhileStatement()"] 400 | ]; 401 | 402 | string s; 403 | foreach(b; blocks) 404 | s ~= "override " ~ b[0] ~ " " ~ b[1] ~ "{ blockDepth++; auto r = super." ~ b[1] ~ "; blockDepth--; return r; }\n"; 405 | return s; 406 | } 407 | 408 | mixin(DabbleParser.makeBlocks()); 409 | 410 | void varDecl(size_t start, size_t end, VariableDeclaration v, bool isAuto) 411 | { 412 | import std.string : strip; 413 | 414 | if (suppressMessages || blockDepth) 415 | return; 416 | 417 | auto name = v.autoDeclaration ? 418 | v.autoDeclaration.identifiers.map!(x => x.text)().joiner(".").to!string() : 419 | v.declarators.map!(x => x.name.text)().joiner(".").to!string(); 420 | 421 | name = strip(name); 422 | auto type = isAuto ? "auto" : types.length ? types[0] : null; 423 | assert(type !is null); 424 | newVar(name, type, lastInit, original[declStart..end]); 425 | blank(declStart, end); 426 | clear(); 427 | } 428 | 429 | void userTypeDecl(size_t start, size_t end, string type) 430 | { 431 | if (suppressMessages || blockDepth) 432 | return; 433 | 434 | auto global = (type == "alias" || type == "enum") ? declCanBeGlobal : true; 435 | newDecl(global, type, original[start..end]); 436 | blank(start, end); 437 | clear(); 438 | } 439 | 440 | void expr(size_t start, size_t end) 441 | { 442 | if (suppressMessages) 443 | return; 444 | 445 | insert(start, "_REPL.exprResult("); 446 | insert(end, ", __expressionResult)", true); 447 | } 448 | 449 | void clear() 450 | { 451 | lastInit = ""; 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/dabble/repl.d: -------------------------------------------------------------------------------- 1 | /** 2 | Written in the D programming language. 3 | 4 | Main REPL functionality. 5 | 6 | Copyright: Copyright Callum Anderson 2013 7 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Callum Anderson 9 | **/ 10 | 11 | module dabble.repl; 12 | 13 | import 14 | std.range, 15 | std.conv, 16 | std.variant, 17 | std.algorithm, 18 | std.stdio; 19 | 20 | import std.typecons : Tuple, tuple; 21 | 22 | import 23 | dabble.meta, 24 | dabble.parser, 25 | dabble.util, 26 | dabble.defs, 27 | dabble.grammars, 28 | dabble.sharedlib; 29 | 30 | protected: 31 | SharedLib[] keepAlive; 32 | ReplContext context; 33 | DabbleParser parser; 34 | enum sterm = "\u0006"; 35 | 36 | public: 37 | bool consoleSession = true; 38 | Tuple!(string,"stage",long,"msecs")[] timings; 39 | 40 | 41 | shared static this() 42 | { 43 | context.init(); 44 | parser = new DabbleParser; 45 | } 46 | 47 | 48 | /** 49 | * Stages of evaluation. 50 | */ 51 | enum Stage 52 | { 53 | none, 54 | meta, 55 | parse, 56 | build, 57 | call 58 | } 59 | 60 | 61 | /** 62 | * Available levels of debug info. 63 | */ 64 | enum Debug 65 | { 66 | none = 0x00, /// no debug output 67 | times = 0x01, /// display time to parse, build and call 68 | parseOnly = 0x04, /// show parse tree and return 69 | } 70 | 71 | 72 | /** 73 | * Add a debug level to the session. 74 | */ 75 | void addDebugLevel(Debug level) in { assert(context.initalized, "Context not initalized"); } body 76 | { 77 | context.debugLevel |= level; 78 | } 79 | 80 | 81 | /** 82 | * Set the debug level to the session. 83 | */ 84 | void setDebugLevel(uint level) in { assert(context.initalized, "Context not initalized"); } body 85 | { 86 | context.debugLevel = level; 87 | } 88 | 89 | 90 | /** 91 | * Reset the repl session 92 | */ 93 | void resetSession() 94 | { 95 | context.reset(); 96 | } 97 | 98 | 99 | /** 100 | * Main repl entry point. Keep reading lines from stdin, handle any 101 | * repl commands, pass the rest onto eval. 102 | */ 103 | void loop() 104 | { 105 | import std.stdio : writeln, write, stdout, stdin; 106 | import std.string : chomp, strip; 107 | 108 | assert(context.initalized, "Context not initalized"); 109 | 110 | string input, codeBuffer; 111 | if (consoleSession) 112 | { 113 | clearScreen(); 114 | writeln(title()); 115 | write(prompt()); 116 | } 117 | 118 | do 119 | { 120 | input = stdin.readln(); 121 | auto r = eval(input, codeBuffer); 122 | 123 | if (codeBuffer.length) // multiLine 124 | { 125 | consoleSession ? prompt(true).send(false) : json("id", "parse-multiline").send; 126 | } 127 | else 128 | { 129 | if (r[1] == Stage.call) 130 | consoleSession ? r[0].send : splitMessages(r[0]); 131 | 132 | if (consoleSession) 133 | prompt().send(false); 134 | } 135 | 136 | version(none) 137 | { 138 | if (context.debugLevel & Debug.times) 139 | result[1] ~= "Timings:\n" ~ timings.map!( x => text(" ", 140 | x.stage," - ",x.msecs) )().join("\n") ~ "\n"; 141 | } 142 | 143 | } while (strip(input) != "exit"); 144 | } 145 | 146 | 147 | /** 148 | * Split up repl result into standard result stuff and json messages. 149 | */ 150 | void splitMessages(string s) 151 | { 152 | string outer; 153 | string[] inner; 154 | auto r = s.findSplit(sterm); 155 | while(!r[2].empty) 156 | { 157 | outer ~= r[0]; 158 | r = r[2].findSplit(sterm); 159 | inner ~= r[0]; 160 | r = r[2].findSplit(sterm); 161 | } 162 | outer ~= r[0]; 163 | json("id", "repl-result", "summary", outer.escapeJSON()).send; 164 | foreach(i; inner) 165 | json("id", "repl-message", "message", i.escapeJSON()).send; 166 | } 167 | 168 | 169 | /** 170 | * Free any shared libs that were kept alive. 171 | */ 172 | void onExit() 173 | { 174 | import std.array : empty, front, popFront; 175 | 176 | while(!keepAlive.empty) 177 | { 178 | //debug { writeln("Free'ing lib: ", keepAlive.front.filename); } 179 | keepAlive.front.free(); 180 | keepAlive.popFront(); 181 | } 182 | } 183 | 184 | 185 | /** 186 | * Evaluate code in the context of the supplied ReplContext. This version assumes 187 | * inBuffer does not contain multiline input. 188 | */ 189 | Tuple!(string, Stage) eval(string inBuffer) 190 | { 191 | string dummyBuffer; 192 | return eval(inBuffer, dummyBuffer); 193 | } 194 | 195 | 196 | /** 197 | * Evaluate code in the context of the supplied ReplContext. 198 | */ 199 | Tuple!(string, Stage) eval(string inBuffer, ref string codeBuffer) 200 | { 201 | import std.string : strip, stripRight, toLower; 202 | 203 | assert(context.initalized, "Context not initalized"); 204 | 205 | timings.clear(); 206 | Tuple!(string, Stage) result; 207 | bool multiLine = codeBuffer.length > 0; 208 | auto newInput = strip(inBuffer); 209 | 210 | if (newInput.toLower() == "exit") 211 | return result; 212 | 213 | if (handleMetaCommand(newInput, codeBuffer)) 214 | return tuple("", Stage.meta); 215 | 216 | if (newInput.length > 0) 217 | { 218 | codeBuffer ~= inBuffer; 219 | if (canParse(codeBuffer.to!string)) 220 | { 221 | result = evaluate(codeBuffer.to!string()); 222 | codeBuffer.clear; 223 | } 224 | } 225 | else 226 | { 227 | // If line is empty, but codebuffer is not, evaluate whatever is available in the buffer 228 | if (codeBuffer.length) 229 | { 230 | result = evaluate(codeBuffer.to!string()); 231 | codeBuffer.clear; 232 | } 233 | } 234 | return result; 235 | } 236 | 237 | 238 | /** 239 | * Raw code, for better error messages. 240 | */ 241 | struct RawCode 242 | { 243 | import std.algorithm, std.range; 244 | 245 | struct Entry 246 | { 247 | bool valid; 248 | string code; 249 | } 250 | 251 | Entry[] _header, _body; 252 | 253 | void append(string s, bool global) 254 | { 255 | if (global) 256 | _header ~= Entry(false, s); 257 | else 258 | _body ~= Entry(false, s); 259 | } 260 | 261 | void fail() 262 | { 263 | _header = _header.filter!(x => x.valid)().array(); 264 | _body = _body.filter!(x => x.valid)().array(); 265 | } 266 | 267 | void pass() 268 | { 269 | foreach(ref i; _header) 270 | i.valid = true; 271 | foreach(ref i; _body) 272 | i.valid = true; 273 | } 274 | 275 | void reset() 276 | { 277 | _header.clear(); 278 | _body.clear(); 279 | } 280 | 281 | /** 282 | * Return a compile-able version of the raw code. 283 | */ 284 | string toString() 285 | { 286 | import std.string : join; 287 | return "import std.traits, std.stdio, std.range, std.algorithm, std.conv;\n" ~ delve ~ 288 | _header.map!( x => x.code )().join("\n") ~ "\nvoid main() {\n" ~ 289 | _body.map!( x => x.code )().join("\n") ~ "\n}"; 290 | } 291 | } 292 | 293 | 294 | /** 295 | * REPL state. 296 | */ 297 | struct ReplContext 298 | { 299 | uint count = 0; 300 | RawCode rawCode; 301 | ReplShare share; 302 | string vtblFixup; 303 | uint debugLevel = Debug.none; 304 | 305 | Tuple!(string,"filename",string,"tempPath") paths; 306 | Tuple!(string,"path",string,"name",long,"modified")[] userModules; 307 | 308 | private bool _initalized = false; 309 | 310 | @property bool initalized() 311 | { 312 | return _initalized; 313 | } 314 | 315 | @property string filename() 316 | { 317 | return paths.filename ~ count.to!string(); 318 | } 319 | 320 | @property string fullName() 321 | { 322 | import std.path : dirSeparator; 323 | return paths.tempPath ~ dirSeparator ~ filename; 324 | } 325 | 326 | void init() 327 | { 328 | import std.path : dirSeparator; 329 | import std.file : readText, exists; 330 | 331 | paths.filename = "repl"; 332 | paths.tempPath = getTempDir() ~ dirSeparator; 333 | debugLevel = debugLevel; 334 | share.gc = gc_getProxy(); 335 | share.resultFile = paths.tempPath ~ "__dabbleTemp"; 336 | share.init(); 337 | 338 | DMDMessage[] errors; 339 | if (!buildUserModules(errors, true)) 340 | throw new Exception("Unable to build defs.d, " ~ errors.map!(x => text(x.sourceCode, ":", x.errorMessage)).join("\n")); 341 | 342 | _initalized = true; 343 | } 344 | 345 | void reset() 346 | { 347 | rawCode.reset(); 348 | share.reset(); 349 | userModules.clear(); 350 | vtblFixup.clear(); 351 | } 352 | } 353 | 354 | 355 | /** 356 | * Return a command-input prompt. 357 | */ 358 | string prompt(bool multiline = false) 359 | { 360 | return multiline ? ".. " : ">> "; 361 | } 362 | 363 | 364 | /** 365 | * Return a title for the session. 366 | */ 367 | string title() 368 | { 369 | import std.compiler, std.conv, std.array, std.format; 370 | auto writer = appender!string(); 371 | formattedWrite(writer, "DABBLE: (DMD %d.%03d)", version_major, version_minor); 372 | return writer.data; 373 | } 374 | 375 | 376 | /** 377 | * Clear the command window. 378 | */ 379 | void clearScreen() 380 | { 381 | import std.process : system; 382 | 383 | if (!consoleSession) 384 | return; 385 | 386 | version(Windows) 387 | { 388 | system("cls"); 389 | } 390 | else version(Posix) 391 | { 392 | system("clear"); 393 | } 394 | else 395 | { 396 | pragma(msg, "Need to implement clearScreen for this platform"); 397 | } 398 | } 399 | 400 | 401 | /** 402 | * Turn a debug level on or off, using a string to identify the debug level. 403 | */ 404 | void setDebugLevelFromString(string s)(string level) if (s == "on" || s == "off") 405 | { 406 | import std.string; 407 | 408 | static if (s == "on") 409 | enum string op = "context.debugLevel |= "; 410 | else static if (s == "off") 411 | enum string op = "context.debugLevel &= ~"; 412 | 413 | switch(toLower(level)) 414 | { 415 | case "times": goto case "showtimes"; 416 | case "showtimes": mixin(op ~ "Debug.times;"); break; 417 | case "parseonly": mixin(op ~ "Debug.parseOnly;"); break; 418 | default: break; 419 | } 420 | } 421 | 422 | 423 | /** 424 | * Evaluate code in the context of the supplied ReplContext. 425 | */ 426 | Tuple!(string, Stage) evaluate(string code) 427 | { 428 | import std.typecons : Tuple; 429 | import std.conv : to, text; 430 | import std.string : join; 431 | import std.algorithm : map; 432 | 433 | string parsedCode = ""; 434 | if (!timeIt("parse (total)", parse(code, parsedCode))) 435 | { 436 | context.share.prune(); 437 | context.rawCode.fail(); 438 | return tuple("", Stage.parse); 439 | } 440 | 441 | if (!parsedCode.length) 442 | assert(false); 443 | 444 | if (context.debugLevel & Debug.parseOnly) 445 | { 446 | auto summary = "Parse only:" ~ newl ~ parsedCode; 447 | consoleSession ? summary.send : json("id", "parse-parseOnly", "summary", parsedCode.escapeJSON()).send; 448 | return tuple("", Stage.parse); 449 | } 450 | 451 | if (!timeIt("build (total)", build(parsedCode))) 452 | { 453 | context.share.prune(); 454 | context.rawCode.fail(); 455 | return tuple("", Stage.build); 456 | } 457 | 458 | string replResult; 459 | auto callResult = timeIt("call (total)", call(replResult)); 460 | context.share.prune(); 461 | context.rawCode.pass(); 462 | 463 | hookNewClass(typeid(Object) /** dummy **/, null /** dummy **/, &context, !callResult); 464 | 465 | if (!callResult) 466 | { 467 | auto summary = "Internal error: " ~ replResult; 468 | consoleSession ? summary.send : json("id", "call-internal-error", "summary", replResult.escapeJSON()).send; 469 | return tuple("", Stage.call); 470 | } 471 | 472 | return tuple(replResult, Stage.call); 473 | } 474 | 475 | 476 | /** 477 | * Just see if the code can be parsed (generates no errors), for testing multiline input. 478 | */ 479 | bool canParse(string code) 480 | { 481 | void v(string,string,string,string) {} 482 | void d(bool,string,string) {} 483 | bool r(string) { return false; } 484 | parser.parse(code, &r, &d, &v); 485 | return parser.errors.length == 0; 486 | } 487 | 488 | 489 | /** 490 | * Do a full parse of the input. 491 | */ 492 | bool parse(string code, out string parsedCode) 493 | { 494 | import std.algorithm : canFind, countUntil; 495 | import std.conv : text; 496 | import std.string : join, splitLines; 497 | 498 | string[] dupSearchList; 499 | 500 | /** Handlers for the parser **/ 501 | void newVariable(string name, string type, string init, string source) 502 | { 503 | // let compiler deal with redefinition 504 | version(none) { if (context.share.vars.canFind!((a,b) => (a.name == b))(name)){} } 505 | context.rawCode.append(source, false); 506 | context.share.vars ~= Var(name, type, init); 507 | dupSearchList ~= name; 508 | } 509 | 510 | void newDeclaration(bool global, string type, string source) 511 | { 512 | context.rawCode.append(source, global); 513 | context.share.decls ~= Decl(source, global); 514 | } 515 | 516 | bool redirectVar(string name) 517 | { 518 | return context.share.vars.canFind!((a,b) => (a.name == b))(name); 519 | } 520 | /** ----------------------- **/ 521 | 522 | auto source = parser.parse(code, &redirectVar, &newDeclaration, &newVariable); 523 | 524 | if (parser.errors.length != 0) 525 | { 526 | auto lines = code.splitLines(); 527 | string[] niceErrors; 528 | foreach(e; parser.errors) 529 | { 530 | niceErrors ~= e[2]; 531 | if (e[0] > 0 && lines.length >= e[0]) 532 | { 533 | niceErrors ~= lines[e[0]-1]; 534 | if (e[1] > 0) 535 | niceErrors ~= iota(e[1]-1).map!(x => " ").join("") ~ "^"; 536 | } 537 | } 538 | 539 | auto summary = text("Parser error", parser.errors.length > 1 ? "s:" : ":", newl, niceErrors.join(newl)); 540 | consoleSession ? summary.send : 541 | json("id", "parse-error", "summary", summary.escapeJSON(), "errors", 542 | parser.errors.map!(t => tuple("source", t[0] > 0 ? lines[t[0]-1].escapeJSON() : "", "column", t[1], "error", t[2].escapeJSON())).array).send; 543 | return false; 544 | } 545 | 546 | auto c = context.share.generate(); 547 | 548 | foreach(d; dupSearchList) 549 | { 550 | // auto index = context.share.vars.countUntil!( (a,b) => a.name == b )(d); 551 | // workaround for countUtil -- bug? 552 | int index = -1; 553 | foreach(uint i, Var currVar; context.share.vars) 554 | if (currVar.name == d) 555 | index = i; 556 | 557 | assert(index >= 0, "Parser: undefined var in string dups"); 558 | c.suffix.put("if (!_repl_.vars[" ~ index.to!string() ~ "].func) { " 559 | "_REPL.dupSearch(*" ~ d ~ ", _repl_.imageBounds[0], _repl_.imageBounds[1], _repl_.keepAlive); }\n"); 560 | } 561 | 562 | auto codeOut = 563 | c.header.data ~ 564 | "string __expressionResult = ``; \n" 565 | "\n\nexport extern(C) int _main(ref _REPL.ReplShare _repl_)\n" 566 | "{\n" 567 | " import std.exception, std.stdio;\n" 568 | " version(Windows) gc_setProxy(_repl_.gc);\n" 569 | " auto saveOut = stdout;\n" 570 | " scope(exit) { stdout = saveOut; } \n" 571 | " stdout.open(_repl_.resultFile, `wt`);\n" 572 | " auto e = collectException!Throwable(_main2(_repl_));\n" 573 | " if (e) { writeln(e); return -1; }\n" 574 | " return 0;\n" 575 | "}\n\n" 576 | 577 | "void _main2(ref _REPL.ReplShare _repl_)\n" 578 | "{\n" ~ 579 | text(context.vtblFixup, c.prefix.data, source, c.suffix.data, 580 | "if (__expressionResult.length != 0) writeln(__expressionResult);\n") ~ 581 | "}\n" ~ genHeader() ~ delve; 582 | 583 | context.rawCode.append(parser.original, false); 584 | parsedCode = codeOut; 585 | return true; 586 | } 587 | 588 | 589 | /** 590 | * Attempt a build command, redirect errout to a text file. 591 | * Returns: array of tuples containing (source line, error message) 592 | */ 593 | bool attempt(string cmd, string codeFilename, out DMDMessage[] errors) 594 | { 595 | import std.process : system; 596 | import std.file : exists, readText; 597 | 598 | version(Windows) 599 | cmd ~= " 2> errout.txt"; 600 | else version(Posix) 601 | cmd ~= " > errout.txt"; 602 | else 603 | static assert(false, "Implement 'attempt' on this platform"); 604 | 605 | if (system(cmd)) 606 | { 607 | auto errFile = context.paths.tempPath ~ "errout.txt"; 608 | if (codeFilename.length && exists(errFile)) 609 | errors = parseDmdErrorFile(codeFilename, errFile, true); 610 | return false; 611 | } 612 | return true; 613 | } 614 | 615 | 616 | /** 617 | * Build a shared lib from supplied code. 618 | */ 619 | bool build(string code) 620 | { 621 | import std.stdio : File; 622 | import std.string : join; 623 | import std.algorithm : map; 624 | import std.file : exists, readText; 625 | import std.path : dirSeparator; 626 | import std.process : system, escapeShellFileName; 627 | import std.parallelism; 628 | import core.thread; 629 | import std.conv : text; 630 | 631 | auto file = File(context.fullName ~ ".d", "w"); 632 | timeIt("build - write", file.write(code)); 633 | file.close(); 634 | 635 | version(Windows) 636 | { 637 | if (!exists(context.fullName ~ ".def")) 638 | { 639 | file = File(context.fullName ~ ".def", "w"); 640 | 641 | enum def = "LIBRARY repl\n" 642 | "DESCRIPTION 'repl'\n" 643 | "EXETYPE NT\n" 644 | "CODE PRELOAD\n" 645 | "DATA PRELOAD"; 646 | 647 | file.write(def); 648 | file.close(); 649 | } 650 | } 651 | 652 | DMDMessage[] errors; 653 | 654 | if (!timeIt("build - userMod", buildUserModules(errors))) 655 | { 656 | auto summary = "Failed building user modules: errors follow:" ~ newl ~ errors.map!(e => e.toStr()).join(newl); 657 | consoleSession ? summary.send : 658 | json("id", "build-error-usermod", "summary", summary.escapeJSON(), "data", errors.map!(e => e.toTup()).array).send; 659 | } 660 | 661 | auto dirChange = "cd " ~ escapeShellFileName(context.paths.tempPath); 662 | 663 | version(Windows) 664 | auto dmdFlags = ["-L/NORELOCATIONCHECK", "-L/NOMAP", "-shared"]; 665 | else version(Posix) 666 | auto dmdFlags = ["-of" ~ context.filename ~ ".so", "-fPIC", "-shared", "-defaultlib=libphobos2.so"]; 667 | else 668 | static assert(false, "Implement 'build' for this platform"); 669 | 670 | debug dmdFlags ~= "-debug"; 671 | 672 | auto cmd = [dirChange, cmdJoin, "dmd", dmdFlags.join(" "), context.filename ~ ".d"]; 673 | version(Windows) cmd ~= context.filename ~ ".def"; 674 | cmd ~= "extra." ~ libExt; 675 | 676 | if (context.userModules.length) 677 | cmd ~= context.userModules.map!(a => "-I" ~ a.path)().join(" "); 678 | 679 | // Try to build the full hacked source 680 | if (timeIt("build - build", attempt(cmd.join(" "), context.fullName ~ ".d", errors))) 681 | return true; 682 | 683 | // If the full build fails, try to get a better error message by compiling the 684 | // raw code. (Originally this was done in a background thread along with the 685 | // full build, but it adds latency for all code, not just that which is wrong). 686 | auto fullBuildErrors = errors; 687 | if (timeIt("build - testCompile", testCompile(errors))) 688 | { 689 | auto summary = "Internal error: test compile passed, full build failed. Error follows:" ~ newl ~ 690 | fullBuildErrors.map!(e => e.toStr()).join(newl); 691 | 692 | consoleSession ? summary.send : 693 | json("id", "build-error-internal", "summary", summary.escapeJSON(), "data", fullBuildErrors.map!(e => e.toTup()).array).send; 694 | } 695 | else 696 | { 697 | auto summary = errors.map!(e => e.toStr()).join(newl); 698 | consoleSession ? summary.send : 699 | json("id", "build-error", "summary", summary.escapeJSON(), "data", errors.map!(e => e.toTup()).array).send; 700 | } 701 | return false; 702 | } 703 | 704 | 705 | /** 706 | * Test to see if raw code compiles, this allows us to generate error messages 707 | * which do not expose too many internals. 708 | * Returns: 709 | * error message string if compilation failed 710 | * else an empty string 711 | */ 712 | bool testCompile(out DMDMessage[] errors) 713 | { 714 | import std.stdio : File; 715 | import std.file : exists, remove; 716 | import std.process : system, escapeShellFileName; 717 | 718 | auto srcFile = context.paths.tempPath ~ "testCompile.d"; 719 | auto errFile = context.paths.tempPath ~ "testCompileErrout.txt"; 720 | 721 | auto rawFile = File(srcFile, "w"); 722 | rawFile.write(context.rawCode.toString()); 723 | rawFile.close(); 724 | 725 | if (errFile.exists()) 726 | try { errFile.remove(); } catch(Exception e) {} 727 | 728 | auto dirChange = "cd " ~ escapeShellFileName(context.paths.tempPath); 729 | auto cmd = [dirChange, cmdJoin, "dmd -c testCompile.d"].join(" "); 730 | 731 | version(Windows) 732 | cmd ~= " 2> testCompileErrout.txt"; 733 | else version(Posix) 734 | cmd ~= " > testCompileErrout.txt"; 735 | else 736 | static assert(false, "Implement 'testCompile' on this platform"); 737 | 738 | if (system(cmd)) 739 | { 740 | if (errFile.exists()) 741 | errors = parseDmdErrorFile(srcFile, errFile, false); 742 | return false; 743 | } 744 | return true; 745 | } 746 | 747 | 748 | /** 749 | * Rebuild user modules into a lib to link with. Only rebuild files that have changed. 750 | */ 751 | bool buildUserModules(out DMDMessage[] errors, bool init = false) 752 | { 753 | import std.stdio : File; 754 | import std.string : join; 755 | import std.algorithm : findSplitAfter, map; 756 | import std.datetime : SysTime; 757 | import std.path : dirSeparator, stripExtension; 758 | import std.file : getTimes, readText, getcwd, copy; 759 | 760 | bool rebuildLib = false; 761 | 762 | if (init) // Compile defs.d 763 | { 764 | rebuildLib = true; 765 | auto f = File(context.paths.tempPath ~ "defs.d", "w"); 766 | f.write("module defs;\n" ~ readText(replPath() ~ "/src/dabble/defs.d").findSplitAfter("module dabble.defs;")[1]); 767 | f.close(); 768 | 769 | auto command = ["cd", context.paths.tempPath, cmdJoin, "dmd -c -release -noboundscheck -O defs.d"]; 770 | version(Posix) command ~= "-fPIC"; 771 | if (!attempt(command.join(" "), context.paths.tempPath ~ "defs.d", errors)) 772 | return false; 773 | } 774 | 775 | if (context.userModules.length == 0 && !rebuildLib) 776 | return true; 777 | 778 | auto allIncludes = context.userModules.map!(a => "-I" ~ a.path).join(" "); 779 | 780 | SysTime access, modified; 781 | foreach(ref m; context.userModules) 782 | { 783 | auto fullPath = m.path ~ dirSeparator ~ m.name; 784 | getTimes(fullPath, access, modified); 785 | 786 | if (modified.stdTime() == m.modified) // file has not changed 787 | continue; 788 | 789 | rebuildLib = true; 790 | auto command = ["cd", context.paths.tempPath, cmdJoin, "dmd -c", allIncludes, fullPath]; 791 | version(Posix) command ~= "-fPIC"; 792 | 793 | if (!attempt(command.join(" "), fullPath, errors)) 794 | return false; 795 | 796 | getTimes(fullPath, access, modified); 797 | m.modified = modified.stdTime(); 798 | } 799 | 800 | if (rebuildLib) 801 | { 802 | auto objs = context.userModules.map!(a => stripExtension(a.name) ~ "." ~ objExt).join(" "); 803 | auto command = ["cd", context.paths.tempPath, cmdJoin, "dmd -lib -ofextra."~libExt, "defs."~objExt, objs].join(" "); 804 | if (!attempt(command, "", errors)) 805 | return false; 806 | } 807 | 808 | return true; 809 | } 810 | 811 | 812 | /** 813 | * Cleanup some dmd outputs in a another thread. 814 | */ 815 | void cleanup() 816 | { 817 | import std.file : exists, remove; 818 | 819 | auto clean = [ 820 | context.fullName ~ "." ~ objExt, 821 | context.fullName ~ ".map", 822 | context.paths.tempPath ~ "errout.txt" 823 | ]; 824 | 825 | foreach(f; clean) 826 | if (exists(f)) 827 | try { remove(f); } catch(Exception e) {} 828 | } 829 | 830 | 831 | /** 832 | * Load the shared lib, and call the _main function. Free the lib on exit. 833 | */ 834 | bool call(out string replResult) 835 | { 836 | import std.exception; 837 | import core.memory : GC; 838 | import std.string : stripRight; 839 | import std.file : DirEntry, readText, exists, remove; 840 | 841 | alias extern(C) int function(ref ReplShare) FuncType; 842 | alias extern(C) void* function() GCFunc; 843 | 844 | context.share.keepAlive = false; 845 | 846 | version(Windows) 847 | { 848 | string ext = ".dll"; 849 | } 850 | else version(Posix) 851 | { 852 | string ext = ".so"; 853 | } 854 | else 855 | { 856 | static assert(false, "Platform not supported"); 857 | } 858 | 859 | auto lib = SharedLib(context.fullName ~ ext); 860 | 861 | /** Experimental over-estimate image memory bounds */ 862 | 863 | context.share.imageBounds[0] = lib.handle; 864 | context.share.imageBounds[1] = lib.handle + DirEntry(context.fullName ~ ext).size(); 865 | 866 | /** ------------ */ 867 | 868 | if (!lib.loaded) 869 | { 870 | replResult = "Failed to load shared library"; 871 | return false; 872 | } 873 | 874 | version(Windows) 875 | { 876 | auto rangeBottom = (lib.getFunction!(GCFunc)("_gcRange"))(); 877 | GC.removeRange(rangeBottom); 878 | auto funcPtr = lib.getFunction!(FuncType)("_main"); 879 | if (funcPtr is null) 880 | { 881 | replResult = "Function ptr is null"; 882 | return false; 883 | } 884 | auto res = funcPtr(context.share); 885 | GC.removeRange(rangeBottom); 886 | } 887 | else version(Posix) 888 | { 889 | auto funcPtr = lib.getFunction!(FuncType)("_main"); 890 | auto res = funcPtr(context.share); 891 | } 892 | else 893 | { 894 | static assert(false, "Need to implement dabble.repl.call for this platform"); 895 | } 896 | 897 | if (exists(context.share.resultFile)) 898 | { 899 | try 900 | { 901 | replResult = readText(context.share.resultFile).stripRight(); 902 | if (replResult.length == 0) 903 | replResult = "OK"; 904 | remove(context.share.resultFile); 905 | } 906 | catch(Exception e) {} 907 | } 908 | 909 | if (context.share.keepAlive) 910 | { 911 | context.count ++; 912 | keepAlive ~= lib; 913 | } 914 | else 915 | { 916 | lib.free(); 917 | } 918 | 919 | if (res == -1) 920 | auto e = collectException!Throwable(GC.collect()); 921 | 922 | // Note that a runtime error is still treated as success 923 | return true; 924 | } 925 | 926 | 927 | /** 928 | * Return error message and line number from a DMD error string. 929 | */ 930 | Tuple!(string, int) stripDmdErrorLine(string line) 931 | { 932 | import std.regex : splitter, match, regex; 933 | import std.string : join; 934 | import std.array : array; 935 | 936 | Tuple!(string, int) err; 937 | auto split = splitter(line, regex(`:`, `g`)).array(); 938 | if (split.length >= 3) 939 | err[0] = split[2..$].join(":"); 940 | else 941 | err[0] = split[$-1]; 942 | 943 | auto lnum = match(line, regex(`\([0-9]+\)`, `g`)); 944 | if (!lnum.empty) 945 | err[1] = lnum.front.hit()[1..$-1].to!int() - 1; 946 | return err; 947 | } 948 | 949 | 950 | /** 951 | * Remove * from user defined vars. 952 | */ 953 | string deDereference(string line, bool parens) 954 | { 955 | import std.regex : replace, regex; 956 | 957 | // TODO: this should make sure the matches are user defined vars 958 | if (parens) 959 | return replace(line, regex(`(\(\*)([_a-zA-Z][_0-9a-zA-Z]*)(\))`, "g"), "$2"); 960 | else 961 | return replace(line, regex(`(\*)([_a-zA-Z][_0-9a-zA-Z]*)`, "g"), "$2"); 962 | } 963 | 964 | 965 | /** 966 | * Holds DMD error messages and corresponding source code. 967 | */ 968 | struct DMDMessage 969 | { 970 | string sourceCode; 971 | string errorMessage; 972 | 973 | string toStr() 974 | { 975 | import std.string: splitLines; 976 | return text("< ", sourceCode, " >", newl, errorMessage.splitLines().map!(l => l).join(newl)); 977 | } 978 | 979 | auto toTup() 980 | { 981 | return tuple("source", sourceCode.escapeJSON(), "error", errorMessage.escapeJSON()); 982 | } 983 | } 984 | 985 | 986 | /** 987 | * Given source code filename and error filename, generate formatted errors. 988 | * Returns: an array of DMDMessage 989 | */ 990 | DMDMessage[] parseDmdErrorFile(string srcFile, string errFile, bool dederef) 991 | { 992 | import std.regex; 993 | import std.path: baseName; 994 | import std.file : readText, exists; 995 | import std.string : splitLines, strip; 996 | 997 | if (!exists(srcFile)) 998 | throw new Exception("parseDmdErrorFile: srcFile does not exist"); 999 | if (!exists(errFile)) 1000 | throw new Exception("parseDmdErrorFile: errFile does not exist"); 1001 | 1002 | DMDMessage[] result; 1003 | auto previousLineNumber = -1; 1004 | auto src = readText(srcFile).splitLines(); 1005 | auto err = readText(errFile).splitLines(); 1006 | 1007 | foreach(l; err.filter!(e => e.length)) 1008 | { 1009 | auto split = stripDmdErrorLine(replace(l, regex(srcFile.baseName("d"), "g"), "")); 1010 | string srcLine, errLine = strip(split[0]); 1011 | 1012 | if (split[1] > 0 && split[1] < src.length) 1013 | srcLine = src[split[1]]; 1014 | 1015 | if (dederef) 1016 | { 1017 | srcLine = srcLine.deDereference(true); 1018 | errLine = errLine.deDereference(false); 1019 | } 1020 | 1021 | if (previousLineNumber != split[1]) 1022 | result ~= DMDMessage(srcLine, errLine); 1023 | else // error refers to same line as previous, don't repeat src 1024 | result[$-1].errorMessage ~= newl ~ errLine; 1025 | 1026 | previousLineNumber = split[1]; 1027 | } 1028 | return result; 1029 | } 1030 | 1031 | 1032 | @property void send(string s, bool newline = true) 1033 | { 1034 | if (newline) 1035 | consoleSession ? writeln(s) : writeln(sterm, s, sterm); 1036 | else 1037 | consoleSession ? write(s) : write(sterm, s, sterm); 1038 | stdout.flush(); 1039 | } 1040 | 1041 | 1042 | /** 1043 | * Make JSON string. 1044 | */ 1045 | protected string json(T...)(T t) 1046 | { 1047 | string result = "{"; 1048 | foreach(i, v; t) 1049 | { 1050 | static if (i % 2 == 0) 1051 | { 1052 | static if (is(typeof(v) == string)) 1053 | result ~= `"` ~ v ~ `":`; 1054 | } 1055 | else 1056 | { 1057 | static if (is(typeof(v) == string)) 1058 | result ~= `"` ~ v ~ `"`; 1059 | else static if (is(typeof(v) _ : Tuple!(X), X...)) 1060 | result ~= json(v.expand); 1061 | else static if (is(typeof(v) _ : Tuple!(X)[], X...)) 1062 | { 1063 | result ~= "["; 1064 | foreach(ii, tt; v) 1065 | { 1066 | result ~= json(tt.expand); 1067 | if (ii < v.length - 1) 1068 | result ~= ","; 1069 | } 1070 | result ~= "]"; 1071 | } 1072 | else 1073 | result ~= v.to!string; 1074 | 1075 | static if (i < t.length - 1) 1076 | result ~= ","; 1077 | } 1078 | } 1079 | return result ~ "}"; 1080 | } 1081 | 1082 | 1083 | string escapeJSON(string s) 1084 | { 1085 | import std.regex; 1086 | string replacer(Captures!(string) m) 1087 | { 1088 | final switch(m.hit) 1089 | { 1090 | case `"`: return `\"`; 1091 | case `\`: return `\\`; 1092 | case "\b": return ``; 1093 | case "\f": return ``; 1094 | case "\t": return ` `; 1095 | case "\r": return `\n`; 1096 | case "\n": return `\n`; 1097 | case "\r\n": return `\n`; 1098 | case "\r\r\n": return `\n`; 1099 | } 1100 | assert(false); 1101 | } 1102 | // to match control chars `|\p{Cc}` 1103 | return s.replaceAll!(replacer)(regex("\f|\b|\t|\r\r\n|\r\n|\r|\n|" ~ `\\|"`)); 1104 | } 1105 | 1106 | 1107 | private auto timeIt(E)(string stage, lazy E expr) 1108 | { 1109 | import std.datetime : StopWatch; 1110 | StopWatch sw; 1111 | sw.start(); 1112 | static if (is(typeof(expr) == void)) 1113 | expr; 1114 | else 1115 | auto result = expr; 1116 | sw.stop(); 1117 | timings ~= Tuple!(string,"stage",long,"msecs")(stage, sw.peek().msecs()); 1118 | 1119 | static if (!is(typeof(expr) == void)) 1120 | return result; 1121 | } 1122 | 1123 | 1124 | /** 1125 | * Try to find the absolute path of the repl executable 1126 | */ 1127 | package string replPath() 1128 | { 1129 | import std.string : join; 1130 | import std.file : thisExePath, exists; 1131 | import std.path : dirName, dirSeparator; 1132 | auto basePath = dirName(thisExePath()).splitter(dirSeparator).array()[0..$-1].join(dirSeparator); 1133 | auto dir = basePath ~ dirSeparator ~ "dabble"; 1134 | return exists(dir) ? dir : basePath; 1135 | } 1136 | 1137 | 1138 | /** 1139 | * This is taken from RDMD 1140 | */ 1141 | private string getTempDir() 1142 | { 1143 | import std.process, std.path, std.file, std.exception; 1144 | 1145 | auto tmpRoot = std.process.getenv("TEMP"); 1146 | 1147 | if (tmpRoot) 1148 | tmpRoot = std.process.getenv("TMP"); 1149 | 1150 | if (!tmpRoot) 1151 | tmpRoot = buildPath(".", ".dabble"); 1152 | else 1153 | tmpRoot ~= dirSeparator ~ ".dabble"; 1154 | 1155 | DirEntry tmpRootEntry; 1156 | const tmpRootExists = collectException(tmpRootEntry = DirEntry(tmpRoot)) is null; 1157 | 1158 | if (!tmpRootExists) 1159 | mkdirRecurse(tmpRoot); 1160 | else 1161 | enforce(tmpRootEntry.isDir, "Entry `"~tmpRoot~"' exists but is not a directory."); 1162 | 1163 | return tmpRoot; 1164 | } 1165 | 1166 | 1167 | private @property string newl() 1168 | { 1169 | return "\n"; 1170 | } 1171 | 1172 | 1173 | private @property string libExt() 1174 | { 1175 | version(Windows) 1176 | return "lib"; 1177 | else version(Posix) 1178 | return "a"; 1179 | else 1180 | static assert(false, "Implement 'libExt' on this platform"); 1181 | } 1182 | 1183 | 1184 | private @property string objExt() 1185 | { 1186 | version(Windows) 1187 | return "obj"; 1188 | else version(Posix) 1189 | return "o"; 1190 | else 1191 | static assert(false, "Implement 'objExt' on this platform"); 1192 | } 1193 | 1194 | 1195 | private @property string cmdJoin() 1196 | { 1197 | version(Windows) 1198 | { 1199 | return "&"; 1200 | } 1201 | else version(Posix) 1202 | { 1203 | return "&&"; 1204 | } 1205 | else 1206 | static assert(false, "Implement 'cmdJoin' on this platform"); 1207 | } 1208 | -------------------------------------------------------------------------------- /src/dabble/sharedlib.d: -------------------------------------------------------------------------------- 1 | /** 2 | Written in the D programming language. 3 | 4 | Handle shared-lib loading / unloading. 5 | 6 | Copyright: Copyright Callum Anderson 2013 7 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Callum Anderson 9 | **/ 10 | 11 | module dabble.sharedlib; 12 | 13 | /** 14 | * Encapsulate shared lib functions. 15 | */ 16 | 17 | version(Windows) 18 | { 19 | import core.sys.windows.windows; 20 | extern(Windows) void* GetProcAddress(HMODULE hModule, LPCSTR lpProcName); 21 | } 22 | else version(Posix) 23 | { 24 | import core.sys.posix.dlfcn; 25 | } 26 | else 27 | static assert(false, "Implement SharedLib for this platform"); 28 | 29 | import core.runtime; 30 | import std.string : toStringz; 31 | 32 | struct SharedLib 33 | { 34 | string filename; 35 | void* handle = null; 36 | 37 | @property bool loaded() { return handle !is null; } 38 | 39 | /** 40 | * Load a shared lib given a filename. 41 | */ 42 | this(string file) 43 | { 44 | filename = file; 45 | load(); 46 | } 47 | 48 | bool load() 49 | { 50 | version(Windows) 51 | handle = Runtime.loadLibrary(filename); 52 | else version(Posix) 53 | handle = dlopen(cast(char*)filename.toStringz(), RTLD_LAZY); 54 | else 55 | static assert(false); 56 | 57 | return handle !is null; 58 | } 59 | 60 | void free() 61 | { 62 | if (handle is null) 63 | return; 64 | 65 | version(Windows) 66 | { 67 | if (Runtime.unloadLibrary(handle)) 68 | handle = null; 69 | } 70 | else version(Posix) 71 | { 72 | dlclose(handle); 73 | handle = null; 74 | } 75 | else 76 | static assert(false); 77 | } 78 | 79 | T getFunction(T)(string name) in {assert(handle !is null, "Null handle");} body 80 | { 81 | version(Windows) 82 | return cast(T) GetProcAddress(handle, cast(char*)(name.toStringz())); 83 | else version(Posix) 84 | return cast(T) dlsym(handle, cast(char*)(name.toStringz())); 85 | else 86 | static assert(false); 87 | } 88 | } -------------------------------------------------------------------------------- /src/dabble/testing.d: -------------------------------------------------------------------------------- 1 | 2 | module dabble.testing; 3 | 4 | import 5 | std.conv, 6 | std.stdio; 7 | 8 | import dabble.repl; 9 | 10 | 11 | void testAll() 12 | { 13 | setDebugLevel(Debug.times); 14 | stress(); 15 | funcLiteral(); 16 | } 17 | 18 | void expect(string code, string expected) 19 | { 20 | import std.string : strip; 21 | 22 | auto res = eval(code); 23 | assert(res[1] == Stage.call); 24 | assert(strip(res[0]) == strip(expected), res[0]); 25 | } 26 | 27 | 28 | 29 | /** 30 | * Seperately eval an array of strings. 31 | */ 32 | void run(string[] code) 33 | { 34 | string err; 35 | evaluate("import std.stdio, std.conv, std.traits, std.typecons, std.algorithm, std.range;"); 36 | 37 | foreach(i, c; code) 38 | { 39 | writeln("Line: ", i, " -> ", c); 40 | auto res = evaluate(c); 41 | assert(res[1] == Stage.call); 42 | writeln(res[0]); 43 | } 44 | 45 | resetSession(); 46 | } 47 | 48 | 49 | void stress() 50 | { 51 | run([ 52 | "auto err0 = `1.2`.to!int;", 53 | "struct S {int x, y = 5; }", 54 | "auto structS = S();", 55 | "auto arr0 = [1,2,3,4];", 56 | "auto arr1 = arr0.sort();", 57 | "auto arr2 = arr1;", 58 | "foreach(ref i; arr2) i++;", 59 | "writeln(arr2);", 60 | "writeln(structS);", 61 | "class C { int a; string b; }", 62 | "auto classC = new C;", 63 | "auto str0 = `hello there`;", 64 | "foreach(i; iota(150)) { str0 ~= `x`;}", 65 | "writeln(str0);", 66 | "writeln(classC);", 67 | "str0 = str0[0..$-20];", 68 | "writeln(str0);", 69 | "auto aa0 = [`one`:1, `two`:2, `three`:3, `four`:4];", 70 | "writeln(aa0[`two`]);", 71 | "writeln(aa0);", 72 | "writeln(arr0);", 73 | "enum Enum { one, two = 5, three = 7 }", 74 | "auto ee = Enum.two;", 75 | "write(ee, \"\n\");", 76 | "auto int0 = 0;", 77 | "for(auto i=0; i<50; i++) int0++;", 78 | "import std.array;", 79 | "auto app0 = appender!string;", 80 | "for(auto i=0; i<50; i++) app0.put(`blah`);", 81 | "writeln(app0.data);", 82 | "import std.container;", 83 | "auto arr3 = Array!int(4, 6, 2, 3, 8, 0, 2);", 84 | "foreach(val; arr3[]){ writeln(val); }", 85 | "int foo(int i) { return 5*i + 1; }", 86 | "foo(100);", 87 | "immutable int int1 = 45;", 88 | "const(int) int2 = foo(3);", 89 | "Array!int arr4;", 90 | "arr4 ~= [1,2,3,4];", 91 | "writeln(arr4[]);", 92 | "T boo(T)(T t) { return T.init; }", 93 | "auto short0 = boo(cast(short)5);", 94 | "if (true) { auto b = [1,2,3]; writeln(b); } else { auto b = `hello`; writeln(b); }", 95 | "auto counter0 = 10;", 96 | "while(counter0-- > 1) { if (false) { auto _temp = 8; } else { writeln(counter0);} }", 97 | "auto func0 = (int i) { return i + 5; };", 98 | "func0(10);", 99 | "auto func1 = func0;", 100 | "func1(10);", 101 | "auto func2 = (int i) => i + 5;", 102 | "auto func3 = (int i) => (i + 5);", 103 | "func1(func2(func3(5)));", 104 | "import std.algorithm, std.range;", 105 | "auto arr5 = [1,2,3,4,5];", 106 | "arr5 = arr5.map!( a => a + 4).array();", 107 | "writeln(arr5);" 108 | ]); 109 | } 110 | 111 | 112 | void libTest() 113 | { 114 | import std.stdio; 115 | string err; 116 | 117 | void test(string i) 118 | { 119 | writeln(i); 120 | auto res = eval(i); 121 | assert(res[1] == Stage.call); 122 | } 123 | 124 | test("import std.typecons;"); 125 | test("Nullable!int a;"); 126 | test("a = 5;"); 127 | test("a;"); 128 | test("int b;"); 129 | test("auto bref = NullableRef!int(&b);"); 130 | test("bref = 5;"); 131 | test("bref;"); 132 | test("auto c = tuple(1, `hello`);"); 133 | //test("class C { int x; }; Unique!C f = new C;"); 134 | //test("f.x = 7;"); 135 | 136 | resetSession(); 137 | 138 | test("import std.algorithm, std.range;"); 139 | test("auto r0 = iota(0, 50, 10);"); 140 | test("r0.find(20);"); 141 | test("balancedParens(`((()))`, '(', ')');"); 142 | test("`hello`.countUntil('l');"); 143 | test("`hello`.findSplitAfter(`el`);"); 144 | test("[1,2,3,4,5].bringToFront([3,4,5]);"); 145 | test("[1,2,3,4,5].filter!(a => a > 3).array();"); 146 | test("[1,2,3,4,5].isSorted();"); 147 | 148 | resetSession(); 149 | 150 | test("import std.range;"); 151 | test("auto r0 = iota(0, 50, 10);"); 152 | test("while(!r0.empty) { r0.popFront(); }"); 153 | test("auto r1 = stride(iota(0, 50, 1), 5);"); 154 | test("writeln(r1);"); 155 | test("drop(iota(20), 12);"); 156 | test("auto r2 = iota(20);"); 157 | test("popFrontN(r2, 7);"); 158 | test("takeOne(retro(iota(20)));"); 159 | test("takeExactly(iota(20), 5);"); 160 | test("radial(iota(20).array(), 10);"); 161 | 162 | resetSession(); 163 | 164 | test("import std.container;"); 165 | test("SList!int slist0;"); 166 | test("slist0.insertFront([1,2,3]);"); 167 | test("auto slist1 = SList!int(1,2,3);"); 168 | test("slist1.insertFront([1,2,3]);"); 169 | test("DList!int dlist0;"); 170 | test("dlist0.insertFront([1,2,3]);"); 171 | test("auto dlist1 = DList!int(1,2,3);"); 172 | test("dlist1.insertFront([1,2,3]);"); 173 | test("Array!int array0;"); 174 | test("array0 ~= [1,2,3];"); 175 | test("auto array1 = Array!int(1,2,3);"); 176 | test("array1 ~= [1,2,3];"); 177 | test("auto tree0 = redBlackTree!true(1,2,3,4,5);"); 178 | test("tree0.insert(5);"); 179 | test("RedBlackTree!int tree1 = new RedBlackTree!int();"); 180 | test("tree1.insert(5);"); 181 | test("BinaryHeap!(Array!int) heap0 = BinaryHeap!(Array!int)(Array!int(1,2,3,4));"); 182 | //test("heap0.insert(1);"); 183 | 184 | resetSession(); 185 | 186 | test("import std.regex;"); 187 | test("auto r0 = regex(`[a-z]*`,`g`);"); 188 | test("auto m0 = match(`abdjsadfjg`,r0);"); 189 | test("auto r1 = regex(`[0-9]+`,`g`);"); 190 | test("auto m1 = match(`12345`,r1);"); 191 | 192 | } 193 | 194 | 195 | 196 | auto funcLiteral() 197 | { 198 | /** Test re-writes of vars with same name as param inside func literals **/ 199 | expect(`int a = 7;`, `7`); 200 | expect(`[1,2].map!(a => a + 1);`, `[2, 3]`); 201 | eval(`auto foo = (int a) => (a + 1);`); 202 | expect(`foo(1);`, `2`); 203 | } 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/dabble/util.d: -------------------------------------------------------------------------------- 1 | /** 2 | Written in the D programming language. 3 | 4 | Utilities included in the DLL. 5 | 6 | Copyright: Copyright Callum Anderson 2013 7 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Callum Anderson 9 | **/ 10 | 11 | module dabble.util; 12 | 13 | import std.conv : to; 14 | 15 | import 16 | dabble.repl, 17 | dabble.defs; 18 | 19 | /** 20 | * Delve stuff 21 | */ 22 | enum delve = q{ 23 | 24 | struct delve 25 | { 26 | private enum sterm = "\u0006"; 27 | 28 | static string escapeJSON(string s) 29 | { 30 | import std.regex : replaceAll, regex, Captures; 31 | string replacer(Captures!(string) m) 32 | { 33 | final switch(m.hit) 34 | { 35 | case `"`: return `\"`; 36 | case `\`: return `\\`; 37 | case "\b": return ``; 38 | case "\f": return ``; 39 | case "\t": return ` `; 40 | case "\r": return `\n`; 41 | case "\n": return `\n`; 42 | case "\r\n": return `\n`; 43 | case "\r\r\n": return `\n`; 44 | } 45 | assert(false); 46 | } 47 | // to match control chars `|\p{Cc}` 48 | return s.replaceAll!(replacer)(regex("\f|\b|\t|\r\r\n|\r\n|\r|\n|" ~ `\\|"`)); 49 | } 50 | 51 | static void html(string data) { 52 | import std.stdio : writeln, stdout; 53 | auto str = `{"handler":"html", "data":"` ~ delve.escapeJSON(data) ~ `"}`; 54 | writeln(sterm, str, sterm); 55 | stdout.flush(); 56 | } 57 | 58 | static void plot(X,Y)(X x, Y y) { 59 | import std.stdio : writeln, stdout; 60 | import std.algorithm : map; 61 | import std.conv : to; 62 | import std.range : zip; 63 | import std.string : join; 64 | // escape any json when titles are made configurable 65 | auto title = "Series 1"; 66 | auto data = `[` ~ zip(x,y).map!( z => `{"x":` ~ z[0].to!string() ~ `,"y":` ~ z[1].to!string() ~ `}` ).join(`,`) ~ `]`; 67 | auto opts = `[{"key":"` ~ title ~ `","values":` ~ data ~ `,"color":"#0000ff"}]`; 68 | auto fin = `{"handler":"plot", "data":` ~ opts ~ `}`; 69 | writeln(sterm,fin,sterm); 70 | stdout.flush(); 71 | } 72 | } 73 | 74 | }; 75 | 76 | extern(C) void* gc_getProxy(); 77 | 78 | /** 79 | * The DLL replaces the runtime _d_newclass in order to intercept 80 | * class allocations, and redirect the classes to point at copies 81 | * of the vtables on the heap. This function is called from within 82 | * the DLL for each new class allocation. 83 | */ 84 | extern(C) void hookNewClass(TypeInfo_Class ti, 85 | void* cptr, 86 | void* repl /** ReplContext* **/, 87 | bool clear = false) 88 | { 89 | import std.algorithm : countUntil, canFind; 90 | import std.c.string : memcpy; 91 | import std.array; 92 | 93 | struct _Info { string name; void*[] vtbl; void* classPtr; } 94 | 95 | static __gshared uint count = 0; 96 | static __gshared _Info[] infos; 97 | 98 | if (count == 0 && ti.name == "core.thread.Thread") 99 | return; 100 | 101 | if (repl is null) 102 | { 103 | count++; 104 | infos ~= _Info(ti.name.idup, ti.vtbl.dup, cptr); 105 | } 106 | else if (clear) 107 | { 108 | count = 0; 109 | infos.clear(); 110 | return; 111 | } 112 | else 113 | { 114 | auto _repl = cast(ReplContext*)repl; 115 | 116 | foreach(i; infos) 117 | { 118 | void* vtblPtr = null; 119 | size_t index = countUntil!"a.name == b"(_repl.share.vtbls, i.name); 120 | 121 | if (index == -1) // No entry exists, need to dup the vtable 122 | { 123 | _repl.share.vtbls ~= Vtbl(i.name, i.vtbl); 124 | index = _repl.share.vtbls.length - 1; 125 | 126 | // Template classes have an extra .Name on the end, remove it 127 | auto n = i.name.idup; 128 | if (n.canFind('!')) 129 | { 130 | while( !n.empty && n.back != '.' ) 131 | n.popBack(); 132 | 133 | if (!n.empty && n.back == '.') 134 | n.popBack(); 135 | 136 | i.name = n; 137 | } 138 | _repl.vtblFixup ~= genFixup(i.name, index); 139 | } 140 | 141 | vtblPtr = _repl.share.vtbls[index].vtbl.ptr; 142 | assert(vtblPtr !is null, "Null vtbl pointer"); 143 | 144 | // Now redirect the vtable pointer in the class 145 | memcpy(i.classPtr, &vtblPtr, (void*).sizeof); 146 | } 147 | count = 0; 148 | infos.clear(); 149 | } 150 | } 151 | 152 | string genFixup(string name, size_t index) 153 | { 154 | return "memcpy(_repl_.vtbls["~to!(string)(index)~"].vtbl.ptr, " 155 | "typeid("~name~").vtbl.ptr, " 156 | "typeid("~name~").vtbl.length * (void*).sizeof);\n"; 157 | } 158 | 159 | 160 | /** 161 | * Generate the DLL header. This needs to be done dynamically, as we 162 | * take the address of hookNewClass, and hard-code it in the DLL (!) 163 | */ 164 | version(Windows) 165 | { 166 | 167 | string genHeader() 168 | { 169 | return 170 | ` 171 | // ################################################################################ 172 | 173 | import std.traits, std.stdio, std.range, std.algorithm, std.conv; 174 | import core.sys.windows.dll, core.thread, core.runtime, core.memory; 175 | import std.c.string, std.c.stdlib, std.c.windows.windows; 176 | 177 | extern(C) void gc_setProxy(void*); 178 | extern(C) void gc_clrProxy(); 179 | extern(C) void* gc_getProxy(); 180 | 181 | import _REPL = defs; 182 | 183 | HINSTANCE g_hInst; 184 | 185 | extern(Windows) BOOL DllMain(HINSTANCE hInstance,DWORD ulReason,LPVOID lpvReserved) 186 | { 187 | final switch (ulReason) 188 | { 189 | case DLL_PROCESS_ATTACH: 190 | Runtime.initialize(); 191 | break; 192 | case DLL_PROCESS_DETACH: 193 | _fcloseallp = null; 194 | gc_clrProxy(); 195 | Runtime.terminate(); 196 | break; 197 | case DLL_THREAD_ATTACH: 198 | break; 199 | case DLL_THREAD_DETACH: 200 | break; 201 | } 202 | return true; 203 | } 204 | 205 | extern(C) { extern __gshared { int _xi_a; } } 206 | export extern(C) void* _gcRange() { return &_xi_a; } 207 | 208 | extern (C) Object _d_newclass(const ClassInfo ci) 209 | { 210 | import core.memory, std.string, core.sys.windows.stacktrace; 211 | void* p; 212 | bool leak = false; 213 | bool hook = true; 214 | auto curr = cast(ClassInfo)ci; 215 | 216 | while(curr) 217 | { 218 | if (curr == typeid(Throwable) || curr == typeid(StackTrace)) 219 | { 220 | leak = true; 221 | hook = false; 222 | break; 223 | } 224 | curr = curr.base; 225 | } 226 | 227 | 228 | if (leak) 229 | { 230 | p = malloc(ci.init.length); // let it leak 231 | } 232 | else 233 | { 234 | GC.BlkAttr attr; 235 | if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers) 236 | attr |= GC.BlkAttr.NO_SCAN; 237 | p = GC.malloc(ci.init.length, attr); 238 | } 239 | 240 | (cast(byte*) p)[0 .. ci.init.length] = ci.init[]; 241 | auto obj = cast(Object) p; 242 | 243 | if (hook) 244 | { 245 | alias extern(C) void function(TypeInfo_Class, void*, void*, bool) cb; 246 | auto fp = cast(cb)(0x` ~ (&hookNewClass).to!string() ~ `); 247 | fp(typeid(obj), p, null, false); 248 | } 249 | 250 | return obj; 251 | } 252 | `; 253 | } 254 | 255 | } 256 | else version(Posix) 257 | { 258 | 259 | string genHeader() 260 | { 261 | return 262 | ` 263 | import std.traits, std.stdio, std.range, std.algorithm, std.conv; 264 | import _REPL = defs; 265 | `; 266 | } 267 | 268 | } 269 | else 270 | { 271 | static assert(false, "Implement 'genHeader' on this platform"); 272 | } 273 | 274 | -------------------------------------------------------------------------------- /ui/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: Droid Sans Mono; 6 | height: 10px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow-x: auto; 11 | overflow-y: hidden; 12 | } 13 | 14 | /* PADDING */ 15 | 16 | .CodeMirror-lines { 17 | padding: 4px 0; /* Vertical padding around content */ 18 | } 19 | .CodeMirror pre { 20 | padding: 0 4px; /* Horizontal padding of content */ 21 | } 22 | 23 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 24 | background-color: white; /* The little square between H and V scrollbars */ 25 | } 26 | 27 | /* GUTTER */ 28 | 29 | .CodeMirror-gutters { 30 | border-right: 1px solid #ddd; 31 | background-color: #f7f7f7; 32 | white-space: nowrap; 33 | } 34 | .CodeMirror-linenumbers {} 35 | .CodeMirror-linenumber { 36 | padding: 0 3px 0 5px; 37 | min-width: 20px; 38 | text-align: right; 39 | color: #999; 40 | } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror div.CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | z-index: 3; 47 | } 48 | /* Shown when moving in bi-directional text */ 49 | .CodeMirror div.CodeMirror-secondarycursor { 50 | border-left: 1px solid silver; 51 | } 52 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 53 | width: auto; 54 | border: 0; 55 | background: #7e7; 56 | z-index: 1; 57 | } 58 | /* Can style cursor different in overwrite (non-insert) mode */ 59 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 60 | 61 | .cm-tab { display: inline-block; } 62 | 63 | /* DEFAULT THEME */ 64 | 65 | .cm-s-default .cm-keyword {color: #708;} 66 | .cm-s-default .cm-atom {color: #219;} 67 | .cm-s-default .cm-number {color: #164;} 68 | .cm-s-default .cm-def {color: #00f;} 69 | .cm-s-default .cm-variable {color: black;} 70 | .cm-s-default .cm-variable-2 {color: #05a;} 71 | .cm-s-default .cm-variable-3 {color: #085;} 72 | .cm-s-default .cm-property {color: black;} 73 | .cm-s-default .cm-operator {color: black;} 74 | .cm-s-default .cm-comment {color: #a50;} 75 | .cm-s-default .cm-string {color: #a11;} 76 | .cm-s-default .cm-string-2 {color: #f50;} 77 | .cm-s-default .cm-meta {color: #555;} 78 | .cm-s-default .cm-error {color: #f00;} 79 | .cm-s-default .cm-qualifier {color: #555;} 80 | .cm-s-default .cm-builtin {color: #30a;} 81 | .cm-s-default .cm-bracket {color: #997;} 82 | .cm-s-default .cm-tag {color: #170;} 83 | .cm-s-default .cm-attribute {color: #00c;} 84 | .cm-s-default .cm-header {color: blue;} 85 | .cm-s-default .cm-quote {color: #090;} 86 | .cm-s-default .cm-hr {color: #999;} 87 | .cm-s-default .cm-link {color: #00c;} 88 | 89 | .cm-negative {color: #d44;} 90 | .cm-positive {color: #292;} 91 | .cm-header, .cm-strong {font-weight: bold;} 92 | .cm-em {font-style: italic;} 93 | .cm-link {text-decoration: underline;} 94 | 95 | .cm-invalidchar {color: #f00;} 96 | 97 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 98 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 99 | 100 | /* STOP */ 101 | 102 | /* The rest of this file contains styles related to the mechanics of 103 | the editor. You probably shouldn't touch them. */ 104 | 105 | .CodeMirror { 106 | line-height: 1; 107 | position: relative; 108 | overflow: hidden; 109 | background: white; 110 | color: black; 111 | } 112 | 113 | .CodeMirror-scroll { 114 | /* 30px is the magic margin used to hide the element's real scrollbars */ 115 | /* See overflow: hidden in .CodeMirror */ 116 | margin-bottom: -30px; margin-right: -30px; 117 | padding-bottom: 30px; padding-right: 30px; 118 | height: 100%; 119 | outline: none; /* Prevent dragging from highlighting the element */ 120 | position: relative; 121 | } 122 | .CodeMirror-sizer { 123 | position: relative; 124 | } 125 | 126 | /* The fake, visible scrollbars. Used to force redraw during scrolling 127 | before actuall scrolling happens, thus preventing shaking and 128 | flickering artifacts. */ 129 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 130 | position: absolute; 131 | z-index: 6; 132 | display: none; 133 | } 134 | .CodeMirror-vscrollbar { 135 | right: 0; top: 0; 136 | overflow-x: hidden; 137 | overflow-y: scroll; 138 | } 139 | .CodeMirror-hscrollbar { 140 | bottom: 0; left: 0; 141 | overflow-y: hidden; 142 | overflow-x: scroll; 143 | } 144 | .CodeMirror-scrollbar-filler { 145 | right: 0; bottom: 0; 146 | } 147 | .CodeMirror-gutter-filler { 148 | left: 0; bottom: 0; 149 | } 150 | 151 | .CodeMirror-gutters { 152 | position: absolute; left: 0; top: 0; 153 | padding-bottom: 30px; 154 | z-index: 3; 155 | } 156 | .CodeMirror-gutter { 157 | white-space: normal; 158 | height: 100%; 159 | padding-bottom: 30px; 160 | margin-bottom: -32px; 161 | display: inline-block; 162 | /* Hack to make IE7 behave */ 163 | *zoom:1; 164 | *display:inline; 165 | } 166 | .CodeMirror-gutter-elt { 167 | position: absolute; 168 | cursor: default; 169 | z-index: 4; 170 | } 171 | 172 | .CodeMirror-lines { 173 | cursor: text; 174 | } 175 | .CodeMirror pre { 176 | /* Reset some styles that the rest of the page might have set */ 177 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 178 | border-width: 0; 179 | background: transparent; 180 | font-family: inherit; 181 | font-size: inherit; 182 | margin: 0; 183 | white-space: pre; 184 | word-wrap: normal; 185 | line-height: inherit; 186 | color: inherit; 187 | z-index: 2; 188 | position: relative; 189 | overflow: visible; 190 | } 191 | .CodeMirror-wrap pre { 192 | word-wrap: break-word; 193 | white-space: pre-wrap; 194 | word-break: normal; 195 | } 196 | .CodeMirror-linebackground { 197 | position: absolute; 198 | left: 0; right: 0; top: 0; bottom: 0; 199 | z-index: 0; 200 | } 201 | 202 | .CodeMirror-linewidget { 203 | position: relative; 204 | z-index: 2; 205 | overflow: auto; 206 | } 207 | 208 | .CodeMirror-widget { 209 | display: inline-block; 210 | } 211 | 212 | .CodeMirror-wrap .CodeMirror-scroll { 213 | overflow-x: hidden; 214 | } 215 | 216 | .CodeMirror-measure { 217 | position: absolute; 218 | width: 100%; height: 0px; 219 | overflow: hidden; 220 | visibility: hidden; 221 | } 222 | .CodeMirror-measure pre { position: static; } 223 | 224 | .CodeMirror div.CodeMirror-cursor { 225 | position: absolute; 226 | visibility: hidden; 227 | border-right: none; 228 | width: 0; 229 | } 230 | .CodeMirror-focused div.CodeMirror-cursor { 231 | visibility: visible; 232 | } 233 | 234 | .CodeMirror-selected { background: #d9d9d9; } 235 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 236 | 237 | .cm-searching { 238 | background: #ffa; 239 | background: rgba(255, 255, 0, .4); 240 | } 241 | 242 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 243 | .CodeMirror span { *vertical-align: text-bottom; } 244 | 245 | @media print { 246 | /* Hide the cursor when printing */ 247 | .CodeMirror div.CodeMirror-cursor { 248 | visibility: hidden; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /ui/css/nv.d3.css: -------------------------------------------------------------------------------- 1 | 2 | /******************** 3 | * HTML CSS 4 | */ 5 | 6 | 7 | .chartWrap { 8 | margin: 0; 9 | padding: 0; 10 | overflow: hidden; 11 | } 12 | 13 | /******************** 14 | Box shadow and border radius styling 15 | */ 16 | .nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip { 17 | -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); 18 | -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); 19 | box-shadow: 0 5px 10px rgba(0,0,0,.2); 20 | 21 | -webkit-border-radius: 6px; 22 | -moz-border-radius: 6px; 23 | border-radius: 6px; 24 | } 25 | 26 | /******************** 27 | * TOOLTIP CSS 28 | */ 29 | 30 | .nvtooltip { 31 | position: absolute; 32 | background-color: rgba(255,255,255,1.0); 33 | padding: 1px; 34 | border: 1px solid rgba(0,0,0,.2); 35 | z-index: 10000; 36 | 37 | font-family: Arial; 38 | font-size: 13px; 39 | text-align: left; 40 | pointer-events: none; 41 | 42 | white-space: nowrap; 43 | 44 | -webkit-touch-callout: none; 45 | -webkit-user-select: none; 46 | -khtml-user-select: none; 47 | -moz-user-select: none; 48 | -ms-user-select: none; 49 | user-select: none; 50 | } 51 | 52 | /*Give tooltips that old fade in transition by 53 | putting a "with-transitions" class on the container div. 54 | */ 55 | .nvtooltip.with-transitions, .with-transitions .nvtooltip { 56 | transition: opacity 250ms linear; 57 | -moz-transition: opacity 250ms linear; 58 | -webkit-transition: opacity 250ms linear; 59 | 60 | transition-delay: 250ms; 61 | -moz-transition-delay: 250ms; 62 | -webkit-transition-delay: 250ms; 63 | } 64 | 65 | .nvtooltip.x-nvtooltip, 66 | .nvtooltip.y-nvtooltip { 67 | padding: 8px; 68 | } 69 | 70 | .nvtooltip h3 { 71 | margin: 0; 72 | padding: 4px 14px; 73 | line-height: 18px; 74 | font-weight: normal; 75 | background-color: rgba(247,247,247,0.75); 76 | text-align: center; 77 | 78 | border-bottom: 1px solid #ebebeb; 79 | 80 | -webkit-border-radius: 5px 5px 0 0; 81 | -moz-border-radius: 5px 5px 0 0; 82 | border-radius: 5px 5px 0 0; 83 | } 84 | 85 | .nvtooltip p { 86 | margin: 0; 87 | padding: 5px 14px; 88 | text-align: center; 89 | } 90 | 91 | .nvtooltip span { 92 | display: inline-block; 93 | margin: 2px 0; 94 | } 95 | 96 | .nvtooltip table { 97 | margin: 6px; 98 | border-spacing:0; 99 | } 100 | 101 | 102 | .nvtooltip table td { 103 | padding: 2px 9px 2px 0; 104 | vertical-align: middle; 105 | } 106 | 107 | .nvtooltip table td.key { 108 | font-weight:normal; 109 | } 110 | .nvtooltip table td.value { 111 | text-align: right; 112 | font-weight: bold; 113 | } 114 | 115 | .nvtooltip table tr.highlight td { 116 | padding: 1px 9px 1px 0; 117 | border-bottom-style: solid; 118 | border-bottom-width: 1px; 119 | border-top-style: solid; 120 | border-top-width: 1px; 121 | } 122 | 123 | .nvtooltip table td.legend-color-guide div { 124 | width: 8px; 125 | height: 8px; 126 | vertical-align: middle; 127 | } 128 | 129 | .nvtooltip .footer { 130 | padding: 3px; 131 | text-align: center; 132 | } 133 | 134 | 135 | .nvtooltip-pending-removal { 136 | position: absolute; 137 | pointer-events: none; 138 | } 139 | 140 | 141 | /******************** 142 | * SVG CSS 143 | */ 144 | 145 | 146 | svg { 147 | -webkit-touch-callout: none; 148 | -webkit-user-select: none; 149 | -khtml-user-select: none; 150 | -moz-user-select: none; 151 | -ms-user-select: none; 152 | user-select: none; 153 | /* Trying to get SVG to act like a greedy block in all browsers */ 154 | display: block; 155 | width:100%; 156 | height:100%; 157 | } 158 | 159 | 160 | svg text { 161 | font: normal 12px Arial; 162 | } 163 | 164 | svg .title { 165 | font: bold 14px Arial; 166 | } 167 | 168 | .nvd3 .nv-background { 169 | fill: white; 170 | fill-opacity: 0; 171 | /* 172 | pointer-events: none; 173 | */ 174 | } 175 | 176 | .nvd3.nv-noData { 177 | font-size: 18px; 178 | font-weight: bold; 179 | } 180 | 181 | 182 | /********** 183 | * Brush 184 | */ 185 | 186 | .nv-brush .extent { 187 | fill-opacity: .125; 188 | shape-rendering: crispEdges; 189 | } 190 | 191 | 192 | 193 | /********** 194 | * Legend 195 | */ 196 | 197 | .nvd3 .nv-legend .nv-series { 198 | cursor: pointer; 199 | } 200 | 201 | .nvd3 .nv-legend .disabled circle { 202 | fill-opacity: 0; 203 | } 204 | 205 | 206 | 207 | /********** 208 | * Axes 209 | */ 210 | .nvd3 .nv-axis { 211 | pointer-events:none; 212 | } 213 | 214 | .nvd3 .nv-axis path { 215 | fill: none; 216 | stroke: #000; 217 | stroke-opacity: .75; 218 | shape-rendering: crispEdges; 219 | } 220 | 221 | .nvd3 .nv-axis path.domain { 222 | stroke-opacity: .75; 223 | } 224 | 225 | .nvd3 .nv-axis.nv-x path.domain { 226 | stroke-opacity: 0; 227 | } 228 | 229 | .nvd3 .nv-axis line { 230 | fill: none; 231 | stroke: #e5e5e5; 232 | shape-rendering: crispEdges; 233 | } 234 | 235 | .nvd3 .nv-axis .zero line, 236 | /*this selector may not be necessary*/ .nvd3 .nv-axis line.zero { 237 | stroke-opacity: .75; 238 | } 239 | 240 | .nvd3 .nv-axis .nv-axisMaxMin text { 241 | font-weight: bold; 242 | } 243 | 244 | .nvd3 .x .nv-axis .nv-axisMaxMin text, 245 | .nvd3 .x2 .nv-axis .nv-axisMaxMin text, 246 | .nvd3 .x3 .nv-axis .nv-axisMaxMin text { 247 | text-anchor: middle 248 | } 249 | 250 | 251 | 252 | /********** 253 | * Brush 254 | */ 255 | 256 | .nv-brush .resize path { 257 | fill: #eee; 258 | stroke: #666; 259 | } 260 | 261 | 262 | 263 | /********** 264 | * Bars 265 | */ 266 | 267 | .nvd3 .nv-bars .negative rect { 268 | zfill: brown; 269 | } 270 | 271 | .nvd3 .nv-bars rect { 272 | zfill: steelblue; 273 | fill-opacity: .75; 274 | 275 | transition: fill-opacity 250ms linear; 276 | -moz-transition: fill-opacity 250ms linear; 277 | -webkit-transition: fill-opacity 250ms linear; 278 | } 279 | 280 | .nvd3 .nv-bars rect.hover { 281 | fill-opacity: 1; 282 | } 283 | 284 | .nvd3 .nv-bars .hover rect { 285 | fill: lightblue; 286 | } 287 | 288 | .nvd3 .nv-bars text { 289 | fill: rgba(0,0,0,0); 290 | } 291 | 292 | .nvd3 .nv-bars .hover text { 293 | fill: rgba(0,0,0,1); 294 | } 295 | 296 | 297 | /********** 298 | * Bars 299 | */ 300 | 301 | .nvd3 .nv-multibar .nv-groups rect, 302 | .nvd3 .nv-multibarHorizontal .nv-groups rect, 303 | .nvd3 .nv-discretebar .nv-groups rect { 304 | stroke-opacity: 0; 305 | 306 | transition: fill-opacity 250ms linear; 307 | -moz-transition: fill-opacity 250ms linear; 308 | -webkit-transition: fill-opacity 250ms linear; 309 | } 310 | 311 | .nvd3 .nv-multibar .nv-groups rect:hover, 312 | .nvd3 .nv-multibarHorizontal .nv-groups rect:hover, 313 | .nvd3 .nv-discretebar .nv-groups rect:hover { 314 | fill-opacity: 1; 315 | } 316 | 317 | .nvd3 .nv-discretebar .nv-groups text, 318 | .nvd3 .nv-multibarHorizontal .nv-groups text { 319 | font-weight: bold; 320 | fill: rgba(0,0,0,1); 321 | stroke: rgba(0,0,0,0); 322 | } 323 | 324 | /*********** 325 | * Pie Chart 326 | */ 327 | 328 | .nvd3.nv-pie path { 329 | stroke-opacity: 0; 330 | transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 331 | -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 332 | -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; 333 | 334 | } 335 | 336 | .nvd3.nv-pie .nv-slice text { 337 | stroke: #000; 338 | stroke-width: 0; 339 | } 340 | 341 | .nvd3.nv-pie path { 342 | stroke: #fff; 343 | stroke-width: 1px; 344 | stroke-opacity: 1; 345 | } 346 | 347 | .nvd3.nv-pie .hover path { 348 | fill-opacity: .7; 349 | } 350 | .nvd3.nv-pie .nv-label { 351 | pointer-events: none; 352 | } 353 | .nvd3.nv-pie .nv-label rect { 354 | fill-opacity: 0; 355 | stroke-opacity: 0; 356 | } 357 | 358 | /********** 359 | * Lines 360 | */ 361 | 362 | .nvd3 .nv-groups path.nv-line { 363 | fill: none; 364 | stroke-width: 1.5px; 365 | /* 366 | stroke-linecap: round; 367 | shape-rendering: geometricPrecision; 368 | 369 | transition: stroke-width 250ms linear; 370 | -moz-transition: stroke-width 250ms linear; 371 | -webkit-transition: stroke-width 250ms linear; 372 | 373 | transition-delay: 250ms 374 | -moz-transition-delay: 250ms; 375 | -webkit-transition-delay: 250ms; 376 | */ 377 | } 378 | 379 | .nvd3 .nv-groups path.nv-line.nv-thin-line { 380 | stroke-width: 1px; 381 | } 382 | 383 | 384 | .nvd3 .nv-groups path.nv-area { 385 | stroke: none; 386 | /* 387 | stroke-linecap: round; 388 | shape-rendering: geometricPrecision; 389 | 390 | stroke-width: 2.5px; 391 | transition: stroke-width 250ms linear; 392 | -moz-transition: stroke-width 250ms linear; 393 | -webkit-transition: stroke-width 250ms linear; 394 | 395 | transition-delay: 250ms 396 | -moz-transition-delay: 250ms; 397 | -webkit-transition-delay: 250ms; 398 | */ 399 | } 400 | 401 | .nvd3 .nv-line.hover path { 402 | stroke-width: 6px; 403 | } 404 | 405 | /* 406 | .nvd3.scatter .groups .point { 407 | fill-opacity: 0.1; 408 | stroke-opacity: 0.1; 409 | } 410 | */ 411 | 412 | .nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { 413 | fill-opacity: 0; 414 | stroke-opacity: 0; 415 | } 416 | 417 | .nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { 418 | fill-opacity: .5 !important; 419 | stroke-opacity: .5 !important; 420 | } 421 | 422 | 423 | .with-transitions .nvd3 .nv-groups .nv-point { 424 | transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 425 | -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 426 | -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; 427 | 428 | } 429 | 430 | .nvd3.nv-scatter .nv-groups .nv-point.hover, 431 | .nvd3 .nv-groups .nv-point.hover { 432 | stroke-width: 7px; 433 | fill-opacity: .95 !important; 434 | stroke-opacity: .95 !important; 435 | } 436 | 437 | 438 | .nvd3 .nv-point-paths path { 439 | stroke: #aaa; 440 | stroke-opacity: 0; 441 | fill: #eee; 442 | fill-opacity: 0; 443 | } 444 | 445 | 446 | 447 | .nvd3 .nv-indexLine { 448 | cursor: ew-resize; 449 | } 450 | 451 | 452 | /********** 453 | * Distribution 454 | */ 455 | 456 | .nvd3 .nv-distribution { 457 | pointer-events: none; 458 | } 459 | 460 | 461 | 462 | /********** 463 | * Scatter 464 | */ 465 | 466 | /* **Attempting to remove this for useVoronoi(false), need to see if it's required anywhere 467 | .nvd3 .nv-groups .nv-point { 468 | pointer-events: none; 469 | } 470 | */ 471 | 472 | .nvd3 .nv-groups .nv-point.hover { 473 | stroke-width: 20px; 474 | stroke-opacity: .5; 475 | } 476 | 477 | .nvd3 .nv-scatter .nv-point.hover { 478 | fill-opacity: 1; 479 | } 480 | 481 | /* 482 | .nv-group.hover .nv-point { 483 | fill-opacity: 1; 484 | } 485 | */ 486 | 487 | 488 | /********** 489 | * Stacked Area 490 | */ 491 | 492 | .nvd3.nv-stackedarea path.nv-area { 493 | fill-opacity: .7; 494 | /* 495 | stroke-opacity: .65; 496 | fill-opacity: 1; 497 | */ 498 | stroke-opacity: 0; 499 | 500 | transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 501 | -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 502 | -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear; 503 | 504 | /* 505 | transition-delay: 500ms; 506 | -moz-transition-delay: 500ms; 507 | -webkit-transition-delay: 500ms; 508 | */ 509 | 510 | } 511 | 512 | .nvd3.nv-stackedarea path.nv-area.hover { 513 | fill-opacity: .9; 514 | /* 515 | stroke-opacity: .85; 516 | */ 517 | } 518 | /* 519 | .d3stackedarea .groups path { 520 | stroke-opacity: 0; 521 | } 522 | */ 523 | 524 | 525 | 526 | .nvd3.nv-stackedarea .nv-groups .nv-point { 527 | stroke-opacity: 0; 528 | fill-opacity: 0; 529 | } 530 | 531 | /* 532 | .nvd3.nv-stackedarea .nv-groups .nv-point.hover { 533 | stroke-width: 20px; 534 | stroke-opacity: .75; 535 | fill-opacity: 1; 536 | }*/ 537 | 538 | 539 | 540 | /********** 541 | * Line Plus Bar 542 | */ 543 | 544 | .nvd3.nv-linePlusBar .nv-bar rect { 545 | fill-opacity: .75; 546 | } 547 | 548 | .nvd3.nv-linePlusBar .nv-bar rect:hover { 549 | fill-opacity: 1; 550 | } 551 | 552 | 553 | /********** 554 | * Bullet 555 | */ 556 | 557 | .nvd3.nv-bullet { font: 10px sans-serif; } 558 | .nvd3.nv-bullet .nv-measure { fill-opacity: .8; } 559 | .nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; } 560 | .nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; } 561 | .nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; } 562 | .nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; } 563 | .nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; } 564 | .nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; } 565 | .nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; } 566 | .nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; } 567 | .nvd3.nv-bullet .nv-subtitle { fill: #999; } 568 | 569 | 570 | .nvd3.nv-bullet .nv-range { 571 | fill: #bababa; 572 | fill-opacity: .4; 573 | } 574 | .nvd3.nv-bullet .nv-range:hover { 575 | fill-opacity: .7; 576 | } 577 | 578 | 579 | 580 | /********** 581 | * Sparkline 582 | */ 583 | 584 | .nvd3.nv-sparkline path { 585 | fill: none; 586 | } 587 | 588 | .nvd3.nv-sparklineplus g.nv-hoverValue { 589 | pointer-events: none; 590 | } 591 | 592 | .nvd3.nv-sparklineplus .nv-hoverValue line { 593 | stroke: #333; 594 | stroke-width: 1.5px; 595 | } 596 | 597 | .nvd3.nv-sparklineplus, 598 | .nvd3.nv-sparklineplus g { 599 | pointer-events: all; 600 | } 601 | 602 | .nvd3 .nv-hoverArea { 603 | fill-opacity: 0; 604 | stroke-opacity: 0; 605 | } 606 | 607 | .nvd3.nv-sparklineplus .nv-xValue, 608 | .nvd3.nv-sparklineplus .nv-yValue { 609 | /* 610 | stroke: #666; 611 | */ 612 | stroke-width: 0; 613 | font-size: .9em; 614 | font-weight: normal; 615 | } 616 | 617 | .nvd3.nv-sparklineplus .nv-yValue { 618 | stroke: #f66; 619 | } 620 | 621 | .nvd3.nv-sparklineplus .nv-maxValue { 622 | stroke: #2ca02c; 623 | fill: #2ca02c; 624 | } 625 | 626 | .nvd3.nv-sparklineplus .nv-minValue { 627 | stroke: #d62728; 628 | fill: #d62728; 629 | } 630 | 631 | .nvd3.nv-sparklineplus .nv-currentValue { 632 | /* 633 | stroke: #444; 634 | fill: #000; 635 | */ 636 | font-weight: bold; 637 | font-size: 1.1em; 638 | } 639 | 640 | /********** 641 | * historical stock 642 | */ 643 | 644 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick { 645 | stroke-width: 2px; 646 | } 647 | 648 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover { 649 | stroke-width: 4px; 650 | } 651 | 652 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive { 653 | stroke: #2ca02c; 654 | } 655 | 656 | .nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative { 657 | stroke: #d62728; 658 | } 659 | 660 | .nvd3.nv-historicalStockChart .nv-axis .nv-axislabel { 661 | font-weight: bold; 662 | } 663 | 664 | .nvd3.nv-historicalStockChart .nv-dragTarget { 665 | fill-opacity: 0; 666 | stroke: none; 667 | cursor: move; 668 | } 669 | 670 | .nvd3 .nv-brush .extent { 671 | /* 672 | cursor: ew-resize !important; 673 | */ 674 | fill-opacity: 0 !important; 675 | } 676 | 677 | .nvd3 .nv-brushBackground rect { 678 | stroke: #000; 679 | stroke-width: .4; 680 | fill: #fff; 681 | fill-opacity: .7; 682 | } 683 | 684 | 685 | 686 | /********** 687 | * Indented Tree 688 | */ 689 | 690 | 691 | /** 692 | * TODO: the following 3 selectors are based on classes used in the example. I should either make them standard and leave them here, or move to a CSS file not included in the library 693 | */ 694 | .nvd3.nv-indentedtree .name { 695 | margin-left: 5px; 696 | } 697 | 698 | .nvd3.nv-indentedtree .clickable { 699 | color: #08C; 700 | cursor: pointer; 701 | } 702 | 703 | .nvd3.nv-indentedtree span.clickable:hover { 704 | color: #005580; 705 | text-decoration: underline; 706 | } 707 | 708 | 709 | .nvd3.nv-indentedtree .nv-childrenCount { 710 | display: inline-block; 711 | margin-left: 5px; 712 | } 713 | 714 | .nvd3.nv-indentedtree .nv-treeicon { 715 | cursor: pointer; 716 | /* 717 | cursor: n-resize; 718 | */ 719 | } 720 | 721 | .nvd3.nv-indentedtree .nv-treeicon.nv-folded { 722 | cursor: pointer; 723 | /* 724 | cursor: s-resize; 725 | */ 726 | } 727 | 728 | /********** 729 | * Parallel Coordinates 730 | */ 731 | 732 | .nvd3 .background path { 733 | fill: none; 734 | stroke: #ccc; 735 | stroke-opacity: .4; 736 | shape-rendering: crispEdges; 737 | } 738 | 739 | .nvd3 .foreground path { 740 | fill: none; 741 | stroke: steelblue; 742 | stroke-opacity: .7; 743 | } 744 | 745 | .nvd3 .brush .extent { 746 | fill-opacity: .3; 747 | stroke: #fff; 748 | shape-rendering: crispEdges; 749 | } 750 | 751 | .nvd3 .axis line, .axis path { 752 | fill: none; 753 | stroke: #000; 754 | shape-rendering: crispEdges; 755 | } 756 | 757 | .nvd3 .axis text { 758 | text-shadow: 0 1px 0 #fff; 759 | } 760 | 761 | /**** 762 | Interactive Layer 763 | */ 764 | .nvd3 .nv-interactiveGuideLine { 765 | pointer-events:none; 766 | } 767 | .nvd3 line.nv-guideline { 768 | stroke: #ccc; 769 | } -------------------------------------------------------------------------------- /ui/css/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | overflow: hidden; 7 | } 8 | 9 | body { 10 | font-family: DroidSansMono; 11 | background-color: #1d1e1c; 12 | font-size: 12px; 13 | color: white; 14 | position: relative; 15 | } 16 | 17 | #header { 18 | position: absolute; 19 | top: 0px; 20 | height: 30px; 21 | width: 100%; 22 | background-color: black; 23 | border-bottom: 1px solid rgb(60,60,60); 24 | z-index: 999; 25 | } 26 | 27 | #title { 28 | margin-right: 5px; 29 | font-family: OpenSans; 30 | font-weight: bold; 31 | float: right; 32 | font-size: 20px; 33 | height: 100%; 34 | } 35 | 36 | #footer { 37 | position: absolute; 38 | bottom: 0px; 39 | height: 30px; 40 | width: 100%; 41 | background-color: black; 42 | border-top: 1px solid rgb(60,60,60); 43 | z-index: 999; 44 | } 45 | 46 | #repl-status { 47 | padding-left: 5px; 48 | height: 100%; 49 | color: white; 50 | font-size: 15px; 51 | } 52 | 53 | #logo { 54 | height: 100%; 55 | float: right; 56 | } 57 | 58 | #content { 59 | position: absolute; 60 | top: 30px; 61 | bottom: 30px; 62 | width: 100%; 63 | } 64 | 65 | #code-pane { 66 | display: inline-block; 67 | width: 100%; 68 | height: 100%; 69 | transition: all .1s ease-in-out; 70 | } 71 | 72 | #code-pane > div:first-child { 73 | max-height: 75%; 74 | overflow-y: auto; 75 | } 76 | 77 | #code-pane > div:nth-child(2) { 78 | height: 20%; 79 | overflow-y: auto; 80 | } 81 | 82 | #code { 83 | width: 100%; 84 | } 85 | 86 | /* Repl input panel */ 87 | #code + .CodeMirror { 88 | background-color: rgb(55,55,55); 89 | border-left: 20px solid rgb(0,80,80); 90 | padding-left: 5px; 91 | } 92 | 93 | .slider-pane { 94 | top: 0px; 95 | right: 0px; 96 | height: 100%; 97 | position: absolute; 98 | visibility: hidden; 99 | overflow: hidden; 100 | transition: all .1s ease-in-out; 101 | border-left: 1px solid rgb(60,60,60); 102 | } 103 | 104 | .pane-header { 105 | position: absolute; 106 | top: 0px; 107 | right: 10px; 108 | left: 10px; 109 | } 110 | 111 | .pane-header > h3 { 112 | border-bottom: 1px solid lightgrey; 113 | } 114 | 115 | .pane-content { 116 | position: absolute; 117 | bottom: 0px; 118 | left: 10px; 119 | right: 10px; 120 | overflow-y: auto; 121 | overflow-x: hidden; 122 | } 123 | 124 | .docsearch-pane-header { 125 | height: 100px; 126 | } 127 | 128 | .docsearch-pane-content { 129 | top: 100px; 130 | } 131 | 132 | .settings-pane-header { 133 | height: 10px; 134 | } 135 | 136 | .settings-pane-content { 137 | top: 50px; 138 | } 139 | 140 | .doc-panel { 141 | cursor: pointer; 142 | vertical-align: middle; 143 | white-space: nowrap; 144 | font-size: 12px; 145 | margin-top: 3px; 146 | margin-bottom: 3px; 147 | border-radius:0px; 148 | background-color: #333333; 149 | padding-left: 5px; 150 | padding-top: 5px; 151 | padding-bottom: 0px; 152 | user-select: none; 153 | width: 100%; 154 | overflow-x: hidden; 155 | text-overflow: ellipsis; 156 | } 157 | 158 | .doc-panel-expanded { 159 | background-color: black; 160 | margin-left: -5px; 161 | margin-right: 0px; 162 | width: 100%; 163 | overflow-x: auto; 164 | } 165 | 166 | .doc-panel-expanded > pre { 167 | font: inherit; 168 | } 169 | 170 | #ajax-loader { 171 | vertical-align: top; 172 | visibility: hidden; 173 | } 174 | 175 | #search-box { 176 | width: 80%; 177 | border-radius: 5px; 178 | padding: 5px; 179 | border: 0px; 180 | font: inherit; 181 | color: black; 182 | background-color: rgb(240,240,240); 183 | border: 1px solid lightgrey; 184 | } 185 | 186 | #settings-pane table { 187 | table-layout: fixed; 188 | width: 95%; 189 | } 190 | 191 | #settings-pane table tr td:first-child { 192 | font-size: 1.2em; 193 | padding-right: 10px; 194 | text-align: right; 195 | width: 25%; 196 | } 197 | 198 | #settings-pane table input { 199 | width: 100%; 200 | border-radius: 5px; 201 | padding: 5px; 202 | border: 0px; 203 | font: inherit; 204 | color: black; 205 | background-color: rgb(240,240,240); 206 | } 207 | 208 | .CodeMirror { 209 | font-family: inherit; 210 | border: 0px solid; 211 | height: auto; 212 | line-height: 1.3; 213 | } 214 | 215 | .CodeMirror-scroll { 216 | overflow-y: hidden; 217 | overflow-x: hidden; 218 | } 219 | 220 | .cm-s-dabble.CodeMirror { background: #1d1e1c; color: #A4A49D; } 221 | .cm-s-dabble .CodeMirror-selected { background: #253B76 !important;} 222 | .cm-s-dabble .CodeMirror-gutters { background: transparent; border-right: 0; } 223 | .cm-s-dabble .CodeMirror-linenumber { color: #A7B5C4; } 224 | .cm-s-dabble .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important;} 225 | 226 | /* TEAL #339966 */ 227 | /* PURPLE #6600FF */ 228 | /* LIGHT BLUE #3385AD */ 229 | 230 | .cm-s-dabble .cm-keyword { color: #70B894; } 231 | .cm-s-dabble .cm-atom { color: #6600FF; } 232 | .cm-s-dabble .cm-number { color: #99B2E6; } 233 | .cm-s-dabble .cm-def { color: #8DA6CE; } 234 | .cm-s-dabble .cm-variable { color: #FFFFFF; } 235 | .cm-s-dabble .cm-operator { color: #80CC99;} 236 | .cm-s-dabble .cm-comment { color: #AEAEAE; } 237 | .cm-s-dabble .cm-string { color: #66FF66; } 238 | .cm-s-dabble .cm-string-2 { color: #CCCCFF; } 239 | .cm-s-dabble .cm-meta { color: #D8FA3C; } 240 | .cm-s-dabble .cm-error { background: #9D1E15; color: #F8F8F8; } 241 | .cm-s-dabble .cm-builtin { color: #C2A3FF; } 242 | .cm-s-dabble .cm-tag { color: #8DA6CE; } 243 | .cm-s-dabble .cm-attribute{ color: #8DA6CE; } 244 | .cm-s-dabble .cm-header { color: #FF6400; } 245 | .cm-s-dabble .cm-hr { color: #AEAEAE; } 246 | .cm-s-dabble .cm-link { color: #8DA6CE; } 247 | 248 | .CodeMirror-hints { 249 | width: 200px; 250 | position: absolute; 251 | z-index: 10; 252 | overflow: hidden; 253 | list-style: none; 254 | margin: 0; 255 | padding: 2px; 256 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 257 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 258 | border: 0px solid silver; 259 | background: rgba(0,0,0,.6); 260 | font-size: 90%; 261 | font-family: monospace; 262 | max-height: 20em; 263 | overflow-y: auto; 264 | } 265 | 266 | .CodeMirror-hint { 267 | font-family: DroidSansMono; 268 | font-size: 1.2em; 269 | margin: 0; 270 | margin-right: 0px; 271 | padding: 0 4px; 272 | max-width: 100%; 273 | overflow: hidden; 274 | white-space: pre; 275 | color: white; 276 | cursor: pointer; 277 | } 278 | 279 | .CodeMirror-hint-active { 280 | background: rgba(0,250,250,.4); 281 | color: white; 282 | } 283 | 284 | .hint-text { 285 | display: inline-block; 286 | margin-left: 10px; 287 | vertical-align: bottom; 288 | } 289 | 290 | .hint-icon { 291 | vertical-align: bottom; 292 | height: 15px; 293 | width: 20px; 294 | } 295 | 296 | ::-webkit-scrollbar { 297 | width: 10px; 298 | height: 10px; 299 | } 300 | 301 | ::-webkit-scrollbar-button:start:decrement, 302 | ::-webkit-scrollbar-button:end:increment { 303 | display: none; 304 | } 305 | 306 | ::-webkit-scrollbar-track-piece { 307 | background-color: transparent; 308 | -webkit-border-radius: 6px; 309 | } 310 | 311 | ::-webkit-scrollbar-thumb:vertical { 312 | -webkit-border-radius: 6px; 313 | background: #666 no-repeat center; 314 | } 315 | 316 | ::-webkit-scrollbar-thumb:horizontal 317 | { 318 | -webkit-border-radius: 6px; 319 | background: #666 no-repeat center; 320 | } 321 | 322 | .resultWidget 323 | { 324 | border-radius: 5px; 325 | padding: 2px; 326 | padding-left: 5px; 327 | margin: 5px 0px; 328 | /*background-color: rgb(40,40,40);*/ 329 | } 330 | 331 | @font-face { 332 | font-family: 'OpenSans'; 333 | src: url('../fonts/OpenSans-Regular-webfont.woff') format('woff'); 334 | } 335 | 336 | @font-face { 337 | font-family: 'DroidSansMono'; 338 | src: url('../fonts/DroidSansMono-webfont.woff') format('woff'); 339 | } 340 | 341 | -------------------------------------------------------------------------------- /ui/fonts/DroidSansMono-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/fonts/DroidSansMono-webfont.woff -------------------------------------------------------------------------------- /ui/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /ui/html/child.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ui/html/repl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dabble 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Search Documentation

31 |
32 | 33 | 34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 |

Settings

42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 | 50 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ui/images/c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/c.png -------------------------------------------------------------------------------- /ui/images/dlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/dlogo.png -------------------------------------------------------------------------------- /ui/images/dlogosmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/dlogosmall.png -------------------------------------------------------------------------------- /ui/images/f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/f.png -------------------------------------------------------------------------------- /ui/images/k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/k.png -------------------------------------------------------------------------------- /ui/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/loader.gif -------------------------------------------------------------------------------- /ui/images/s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/s.png -------------------------------------------------------------------------------- /ui/images/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callumenator/dabble/223ba9457c3d16493658ccc9e38d41a50cdee241/ui/images/v.png -------------------------------------------------------------------------------- /ui/js/handlers.js: -------------------------------------------------------------------------------- 1 | 2 | handlers = {}; 3 | 4 | function handle(json) { 5 | if (json.hasOwnProperty('data') && 6 | json.hasOwnProperty('handler') && 7 | handlers.hasOwnProperty(json.handler)) { 8 | handlers[json.handler](json.data); 9 | } 10 | } 11 | 12 | handlers.html = function(data) { 13 | result(data); 14 | }; 15 | 16 | handlers.plot = function(data) { 17 | 18 | result(''); 19 | 20 | $("#chart").height(window.innerHeight - 20); 21 | 22 | nv.addGraph(function() { 23 | var chart = nv.models.lineChart() 24 | .margin({left: 100}) //Adjust chart margins to give the x-axis some breathing room. 25 | .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! 26 | .transitionDuration(350) //how fast do you want the lines to transition? 27 | .showLegend(true) //Show the legend, allowing users to turn on/off line series. 28 | .showYAxis(true) //Show the y-axis 29 | .showXAxis(true) //Show the x-axis 30 | ; 31 | 32 | chart.xAxis 33 | .axisLabel('X-Axis') 34 | .tickFormat(d3.format(',r')); 35 | 36 | chart.yAxis 37 | .axisLabel('y-Axis') 38 | .tickFormat(d3.format('.02f')); 39 | 40 | d3.select('#chart') 41 | .datum(data) 42 | .call(chart); 43 | 44 | //Update the chart when window resizes. 45 | nv.utils.windowResize(function() { chart.update() }); 46 | return chart; 47 | }); 48 | }; -------------------------------------------------------------------------------- /ui/js/main.js: -------------------------------------------------------------------------------- 1 | 2 | var editor, history, engine, browser; 3 | var browserAction = null, browserStatus = ''; 4 | var historyBuffer = []; 5 | var maxHistory = 200; 6 | var lineIndex = 0; // for moving through the history 7 | var lineWidgetCount = 0; 8 | var resultWindow = null; 9 | var autocompleteCode = ""; 10 | var handlers = {}; 11 | 12 | var autocomplete = { 13 | callback: null, 14 | token: null, 15 | cursor: 0 16 | }; 17 | 18 | /** 19 | * Init global settings. 20 | */ 21 | var globalSettings = { 22 | phobosPath: phobosPath(), 23 | autocompleteOn: true, 24 | autocompleteMinLength: 4 25 | }; 26 | 27 | 28 | /** 29 | * Restore settings from localStorage if present. 30 | */ 31 | if (localStorage.hasOwnProperty("globalSettings")) 32 | globalSettings = JSON.parse(localStorage.globalSettings); 33 | 34 | 35 | /** 36 | * On close, store settings. 37 | */ 38 | require('nw.gui').Window.get().on('close', function() { 39 | localStorage.globalSettings = JSON.stringify(globalSettings); 40 | this.close(true); 41 | }); 42 | 43 | 44 | /** 45 | * Trim string proto. 46 | */ 47 | if (typeof(String.prototype.trim) === "undefined") { 48 | String.prototype.trim = function() { 49 | return String(this).replace(/^\s+|\s+$/g, ''); 50 | }; 51 | } 52 | 53 | 54 | /** 55 | * Uniq array proto. 56 | */ 57 | Array.prototype.getUnique = function(selector){ 58 | var u = {}, a = []; 59 | for(var i = 0, l = this.length; i < l; ++i){ 60 | if(u.hasOwnProperty(selector(this[i]))) { 61 | continue; 62 | } 63 | a.push(this[i]); 64 | u[selector(this[i])] = 1; 65 | } 66 | return a; 67 | } 68 | 69 | 70 | /** 71 | * Autocomplete. 72 | */ 73 | CodeMirror.commands.autocomplete = function(cm) { 74 | CodeMirror.showHint(cm, dcdHint, { 75 | async: true, 76 | completeSingle: false 77 | }); 78 | } 79 | 80 | 81 | /** 82 | * Devtools shortcut 83 | */ 84 | shortcut.add("Ctrl+Shift+J",function() { 85 | require('nw.gui').Window.get().showDevTools(); 86 | }); 87 | 88 | 89 | /** 90 | * Fullscreen toggle shortcut 91 | */ 92 | shortcut.add("Ctrl+M",function() { 93 | require('nw.gui').Window.get().toggleFullscreen(); 94 | }); 95 | 96 | 97 | /** 98 | * Doc search toggle shortcut 99 | */ 100 | shortcut.add("Ctrl+H",function() { 101 | togglePane('docsearch-pane'); 102 | }); 103 | 104 | 105 | /** 106 | * Options toggle shortcut 107 | */ 108 | shortcut.add("Ctrl+O",function() { 109 | togglePane('settings-pane'); 110 | }); 111 | 112 | 113 | /** 114 | * On-load setup 115 | */ 116 | $(document).ready(function () { 117 | $("#repl-status").html("Initializing..."); 118 | initPanes(); 119 | initSettingsEditor(); 120 | initCodemirrors(); 121 | initRepl(); 122 | initBrowser(); 123 | reloadStyles(); 124 | monitorStylesheet(); 125 | setTimeout( function() { 126 | $(window).trigger("resize"); 127 | history.refresh(); 128 | editor.refresh(); 129 | }, 50); 130 | }); 131 | 132 | /** 133 | * Monitor stylesheet for changes . 134 | */ 135 | function monitorStylesheet() { 136 | require('fs').watch('../dabble/ui/css/style.css', function (event, name) { reloadStyles(); }); 137 | } 138 | 139 | function reloadStyles() { 140 | var queryString = '?reload=' + new Date().getTime(); 141 | $('link[rel="stylesheet"]').each(function () { 142 | this.href = this.href.replace(/\?.*|$/, queryString); 143 | }); 144 | } 145 | 146 | /** 147 | * Initialize codemirror editors. 148 | */ 149 | function initCodemirrors() { 150 | editor = CodeMirror.fromTextArea(document.getElementById("code"), { 151 | mode: "text/x-d", 152 | viewportMargin: Infinity, 153 | lineWrapping: true, 154 | smartIndent: false 155 | }); 156 | editor.setOption("readOnly", true); // disable until repl is started 157 | 158 | history = CodeMirror.fromTextArea(document.getElementById("history"), { 159 | mode: "text/x-d", 160 | viewportMargin: Infinity, 161 | readOnly: true, 162 | lineNumbers: true, 163 | lineWrapping: true 164 | }); 165 | 166 | editor.setOption("theme", "dabble"); 167 | history.setOption("theme", "dabble"); 168 | 169 | editor.options.onKeyEvent = function (cm, e) { 170 | 171 | if (!e || !(e instanceof KeyboardEvent)) return; 172 | var completing = false; 173 | if ('completionActive' in editor.state && editor.state.completionActive == true && $(".CodeMirror-hints").length) 174 | completing = true; 175 | if (e.ctrlKey == true || completing) return; 176 | 177 | if (e.type == 'keydown' && e.shiftKey == false && e.keyCode == 13) { // enter key press 178 | replInput(); 179 | e.preventDefault(); 180 | return true; 181 | } else if (e.type == 'keydown' && editor.lineCount() == 1 && (e.keyCode == 38 || e.keyCode == 40)) { // arrow up/down 182 | var line = ""; 183 | e.keyCode == 38 ? line = retrieveHistory('up') : line = retrieveHistory('down'); 184 | if (typeof line != "undefined") { 185 | editor.setValue(line); 186 | setTimeout( function() { editor.setCursor(editor.lineCount(), 0); }, 50); 187 | } 188 | } else { 189 | if (globalSettings.autocompleteOn) { 190 | var completeIt = true; 191 | var token = editor.getTokenAt(editor.getCursor()); 192 | if (token.string.length < globalSettings.autocompleteMinLength) 193 | completeIt = false; 194 | if (token.string == "." || token.string == "(") 195 | completeIt = true; 196 | if (completeIt) 197 | CodeMirror.showHint(editor, dcdHint, { async: true, completeSingle: false }); 198 | } 199 | } 200 | }; 201 | } 202 | 203 | /** 204 | * Start repl. 205 | */ 206 | function initRepl() { 207 | var repl_buffer = {data:""}; 208 | engine = require('child_process').spawn('../dabble/bin/repl', ['--noConsole']); 209 | engine.stdout.on('data', function (data) { 210 | var messages = messageProtocol(data, repl_buffer); 211 | if (messages.length == 0) return; 212 | for(var i = 0; m = messages[i], i < messages.length; i++) 213 | handleMessage(m); 214 | }); 215 | send("version"); 216 | send("history"); 217 | } 218 | 219 | /** 220 | * Start browser. 221 | */ 222 | function initBrowser() { 223 | var browser_buffer = {data:""}; 224 | browser = require('child_process').spawn('../dabble/bin/browser'); 225 | browser.stdout.on('data', function (data) { 226 | var messages = messageProtocol(data, browser_buffer); 227 | if (messages.length == 0) return; 228 | for(var i = 0; m = messages[i], i < messages.length; i++) { 229 | if (m.hasOwnProperty("status")) { 230 | browserStatus = m.status; 231 | } 232 | else if (browserAction !== null) 233 | browserAction(m); 234 | } 235 | }); 236 | if (globalSettings.phobosPath !== undefined && globalSettings.phobosPath != "") 237 | browser.stdin.write(new Buffer("phobos-path:" + globalSettings.phobosPath + "\u0006")); 238 | } 239 | 240 | 241 | /** 242 | * Handle child process messages. 243 | */ 244 | function messageProtocol(incomming, buffer) { 245 | buffer.data += incomming.toString(); 246 | if (incomming.slice(-1)[0] != 10 && incomming.slice(-2)[0] != 6) return []; 247 | var parts = buffer.data.split(/\u0006/g), 248 | jsonArray = []; 249 | for(var i = 0; p = parts[i], i < parts.length; i++) { 250 | p = p.trim(); 251 | if (p.length == 0) 252 | continue; 253 | try { 254 | var json = JSON.parse(p); 255 | jsonArray.push(json); 256 | } catch(err) { 257 | console.log("Message proto: json parse error: ", err, p); 258 | } 259 | } 260 | buffer.data = ""; 261 | return jsonArray; 262 | } 263 | 264 | 265 | /** 266 | * Take input text, handle it. 267 | */ 268 | function replInput() { 269 | var text = editor.getValue(); 270 | if (text.trim() == "clear") clear(); 271 | else { 272 | appendHistory(text); 273 | updateResult(text, false); 274 | send(text); 275 | } 276 | editor.setValue(""); 277 | } 278 | 279 | 280 | /** 281 | * Clear history and send request for version/title string. 282 | */ 283 | function clear() { 284 | history.setValue(""); 285 | send("version"); 286 | send("history"); 287 | } 288 | 289 | 290 | /** 291 | * Handle json messages. 292 | */ 293 | function handleMessage(json) 294 | { 295 | var multiline = false; 296 | switch (json.id) { 297 | case "parse-multiline": 298 | multiline = true; 299 | break; 300 | case "repl-result": 301 | updateResult(json.summary, true); 302 | send("history"); 303 | break; 304 | case "repl-message": 305 | var jsonMsg = null; 306 | try { 307 | jsonMsg = JSON.parse(json.message); 308 | } catch(err) { 309 | console.log("Error parsing repl-message", err); 310 | } 311 | if (jsonMsg !== null) 312 | handleReplMessage(jsonMsg); 313 | break; 314 | case "meta": 315 | if (json.cmd == "version") { 316 | updateResult(json.summary, false); 317 | $("#repl-status").html(""); 318 | editor.setOption("readOnly", false); 319 | editor.focus(); 320 | } else if (json.cmd == "history") { 321 | autocompleteCode = json.summary; 322 | } else { 323 | updateResult(json.summary, true); 324 | } 325 | break; 326 | default: 327 | if (json.hasOwnProperty("summary")) 328 | updateResult(json.summary, true); 329 | else 330 | console.log("Unhandled message: ", json); 331 | break; 332 | } 333 | if (multiline) 334 | $("#repl-status").html("Multi-line input"); 335 | else 336 | $("#repl-status").html(""); 337 | } 338 | 339 | 340 | /** 341 | * Got a result from the repl. 342 | */ 343 | function updateResult(data, lwidget) { 344 | if (data.trim().length == 0) 345 | return; 346 | if (lwidget) { 347 | var id = "lineWidget" + (lineWidgetCount++).toString(); 348 | $("body").append("
" + data, + "
"); 349 | history.addLineWidget(history.lineCount() - 1, $("#" + id)[0]); 350 | } else { 351 | cmAppend(history, data); 352 | } 353 | scrollToBottom($("#code-pane > div:first-child")); 354 | } 355 | 356 | 357 | /** 358 | * Append text to codemirror text. 359 | */ 360 | function cmAppend(cm, text) { 361 | text = text.replace(/(\r\r\n|\r\n|\r)/g, "\n"); 362 | text = text.split("\n").filter(function (el) { return el.length; }).join("\n") 363 | if (cm.getValue() !== "") text = "\n" + text; 364 | cm.replaceRange(text, CodeMirror.Pos(cm.lastLine())); 365 | } 366 | 367 | 368 | /** 369 | * Append text to history. 370 | */ 371 | function appendHistory(text) { 372 | if (text.length == 0 || (historyBuffer.length > 1 && historyBuffer[historyBuffer.length-1] == text)) return; 373 | historyBuffer.push(text); 374 | if (historyBuffer.length > maxHistory) 375 | for(var i = 0; i < 10; i++) 376 | historyBuffer.shift(); 377 | lineIndex = historyBuffer.length; 378 | } 379 | 380 | 381 | /** 382 | * History lookup. 383 | */ 384 | function retrieveHistory(dir) { 385 | if (dir == 'up') { 386 | if (lineIndex > 0) 387 | lineIndex--; 388 | } else if (dir == 'down') { 389 | if (lineIndex < historyBuffer.length - 1) 390 | lineIndex++; 391 | } 392 | return historyBuffer[lineIndex]; 393 | } 394 | 395 | 396 | /** 397 | * Send input to the repl. 398 | */ 399 | function send(text) { 400 | engine.stdin.write(new Buffer(text + "\n")); 401 | } 402 | 403 | 404 | function searchInput() { 405 | var prefix = $("#search-box").val(); 406 | if (prefix.length == 0) { 407 | $("#ajax-loader").css("visibility", "hidden"); 408 | clearSuggestions(); 409 | return; 410 | } 411 | browserAction = function(json) { 412 | $("#ajax-loader").css("visibility", "hidden"); 413 | if (json.length == 0) 414 | clearSuggestions(); 415 | else 416 | $("#suggestionsPane").html(listToHTML(json.result)); 417 | }; 418 | $("#ajax-loader").css("visibility", "visible"); 419 | browser.stdin.write(new Buffer('search-names:' + prefix.toLowerCase() + "\u0006")); 420 | } 421 | 422 | function listToHTML(list) { 423 | var html = ""; 424 | list.forEach( function(e) { 425 | html+="
"+e.name+"
"; 426 | }); 427 | return html; 428 | } 429 | 430 | function panelClick(name) { 431 | var el = event.target; 432 | if (typeof el.dataset.expanded == "undefined") 433 | return; 434 | if (el.dataset.expanded != "true") 435 | expandPanel(name, el); 436 | else 437 | collapsePanel(name, el); 438 | } 439 | 440 | function expandPanel(uuid, parent) { 441 | browserAction = function(json) { 442 | $(parent).append(symbolToHTML(json.result)); 443 | parent.dataset.expanded = "true"; 444 | }; 445 | browser.stdin.write(new Buffer('get-uuid:' + uuid + "\u0006")); 446 | } 447 | 448 | function collapsePanel(name, parent) { 449 | parent.removeChild(parent.firstChild.nextSibling); 450 | parent.dataset.expanded = "false"; 451 | } 452 | 453 | function symbolToHTML(symbol) { 454 | return "
" + 455 | symbol.parent + "
" + symbol.pretty + "
" + 
456 |     symbol.comment.replace(/\\n/g, "\n") + "
"; 457 | } 458 | 459 | function clearSuggestions() { 460 | $("#suggestionsPane").html(""); 461 | } 462 | 463 | 464 | function scrollToBottom(jqEl) { 465 | jqEl.scrollTop(jqEl[0].scrollHeight); 466 | } 467 | 468 | /* Try to auto-detect the phobos lib path. */ 469 | function phobosPath() { 470 | var os = require('os'); 471 | if (os.type() == "Windows_NT") { 472 | var path = process.env.path.split(";").filter( function(e) { return e.indexOf("dmd") != -1; })[0]; 473 | path = path.slice(0, path.indexOf("dmd2")) + "dmd2\\src\\phobos\\std\\"; 474 | if (require('fs').existsSync(path)) 475 | return path; 476 | } else if (os.type() == "Linux") { 477 | var path = "/usr/include/dlang/dmd/std"; 478 | if (require('fs').existsSync(path)) 479 | return path; 480 | } 481 | } 482 | 483 | 484 | /** 485 | * Set up sliding panes. 486 | */ 487 | function initPanes() { 488 | $(".slider-pane").each(function(i,e) { 489 | e.setAttribute("data-shown", "false"); 490 | }); 491 | } 492 | 493 | 494 | /** 495 | * Toggle visibility of given pane. 496 | */ 497 | function togglePane(id) { 498 | ($("#"+id).attr("data-shown") == "false") ? showPane(id) : hidePane(id); 499 | } 500 | 501 | 502 | /** 503 | * Slide-in the given pane. 504 | */ 505 | function showPane(id) { 506 | hidePane(); 507 | var cp = document.getElementById('code-pane'), 508 | sp = document.getElementById(id); 509 | cp.style.width = "39%"; 510 | sp.style.width = "59%"; 511 | sp.style.visibility = "visible"; 512 | sp.setAttribute("data-shown", "true"); 513 | if (sp.getAttribute("data-onshow")) 514 | eval(sp.getAttribute("data-onshow")); 515 | } 516 | 517 | 518 | /** 519 | * Hide active pane. 520 | */ 521 | function hidePane() { 522 | var active = $(".slider-pane[data-shown='true']"); 523 | if (active.length == 0) return; 524 | var cp = document.getElementById('code-pane'), 525 | sp = active[0]; 526 | sp.style.width = "0%"; 527 | sp.style.visibility = "hidden"; 528 | cp.style.width = "99%"; 529 | editor.focus(); 530 | active[0].setAttribute("data-shown", "false"); 531 | if (active.attr("data-onhide")) 532 | eval(active.attr("data-onhide")); 533 | } 534 | 535 | 536 | /** 537 | * Create the settings edit fields. 538 | */ 539 | function initSettingsEditor() { 540 | $("#settings-list").append("Phobos path:"); 541 | $("#settings-list").append("Autocomplete min length:"); 542 | } 543 | 544 | 545 | /** 546 | * Called when settings pane gets hidden, used to store modified settings. 547 | */ 548 | function settingsPaneHide() { 549 | var prevPhobosPath = globalSettings.phobosPath; 550 | 551 | $("#settings-list tr td input").each(function(i,e) { 552 | globalSettings[e.getAttribute("data-key")] = e.value; 553 | }); 554 | 555 | // Update phobos path if it has changed 556 | if (globalSettings.phobosPath !== undefined && 557 | globalSettings.phobosPath != "" && 558 | globalSettings.phobosPath != prevPhobosPath) { 559 | browser.stdin.write(new Buffer("phobos-path:" + globalSettings.phobosPath + "\u0006")); 560 | } 561 | } 562 | 563 | 564 | /********** Autocomplete stuff ***********/ 565 | 566 | function dcdResponse(type, json) { 567 | 568 | var sort_map = { 569 | 'variable': 0, 570 | 'function': 1, 571 | 'struct': 4, 572 | 'class': 5, 573 | 'keyword': 6 574 | } 575 | 576 | function dcd_sort(a,b) { 577 | if (a.kind == b.kind) 578 | return a.completion.localeCompare(b.completion); 579 | else 580 | return sort_map[a.kind] - sort_map[b.kind] 581 | } 582 | 583 | if (json.length) { 584 | json.sort(dcd_sort); 585 | var completions = []; 586 | 587 | for(var i = 0; i < json.length; i++) { 588 | if (type == "completion") { 589 | if (json[i].completion == autocomplete.token.string) 590 | continue; 591 | completions.push( 592 | { 593 | text:json[i].completion, 594 | render: (function(index) { 595 | return function(element, self, data) { 596 | var hint = document.createElement("div"), 597 | html = "", 598 | image = ""; 599 | switch (json[index].kind) { 600 | case 'class': image = 'c.png'; break; 601 | case 'struct': image = 's.png'; break; 602 | case 'variable': image = 'v.png'; break; 603 | case 'function': image = 'f.png'; break; 604 | case 'keyword': image = 'k.png'; break; 605 | default: break; 606 | } 607 | 608 | if (image != "") 609 | html += ""; 610 | 611 | html += "
" + json[index].completion + "
"; 612 | hint.innerHTML = html; 613 | element.appendChild(hint); 614 | }; 615 | })(i) 616 | }); 617 | 618 | } else if (type == "calltips") { 619 | completions.push({ 620 | text:'', 621 | displayText:json[i] 622 | }); 623 | } 624 | } 625 | 626 | if (autocomplete.token.string == ".") { 627 | var from = autocomplete.token.start + 1, to = autocomplete.token.start + 1; 628 | } else { 629 | var from = autocomplete.token.start, to = autocomplete.token.end; 630 | } 631 | 632 | var completionsObject = { 633 | list: completions, 634 | from: CodeMirror.Pos(autocomplete.cursor.line, from), 635 | to: CodeMirror.Pos(autocomplete.cursor.line, to) 636 | }; 637 | 638 | autocomplete.callback(completionsObject); 639 | } 640 | }; 641 | 642 | function dcdHint(cm, callback, options) { 643 | 644 | var cursor = editor.getCursor(), 645 | tk = editor.getTokenAt(cursor); 646 | 647 | var tempCode = autocompleteCode.slice(0, -1) + "\n" + editor.getValue(); 648 | offset = tempCode.length; 649 | if (tk.string == "(") 650 | tempCode += ")"; // try to get calltips 651 | 652 | autocomplete = { 653 | callback: callback, 654 | token: tk, 655 | cursor: cursor 656 | }; 657 | 658 | /* 659 | console.log("Hinting: offset - ", offset); 660 | console.log("Hinting: code at offset - ", tempCode[offset]); 661 | console.log("Hinting: token - ", tk); 662 | console.log("Hinting: code - ", tempCode); 663 | */ 664 | 665 | browserAction = function (json) { 666 | if (json.length == 0) return; 667 | dcdResponse(json.type, json.result); 668 | }; 669 | browser.stdin.write(new Buffer("autocomplete: -c" + offset.toString() + " " + tempCode + "\u0006")); 670 | }; 671 | 672 | 673 | 674 | 675 | function handleReplMessage(json) { 676 | openResultWindow( function() { resultWindow.window.handle(json); }); 677 | } 678 | 679 | function openResultWindow(onload) { 680 | if (resultWindow == null) { 681 | resultWindow = require('nw.gui').Window.open('../html/child.html', { 682 | position: 'center', 683 | title: "Results", 684 | toolbar: true, 685 | frame: true, 686 | width: 500, 687 | height: 500 688 | }); 689 | resultWindow.on('closed', function() { resultWindow = null; }); 690 | resultWindow.on('loaded', function() { onload(); }); 691 | } else { 692 | onload(); 693 | } 694 | } 695 | 696 | -------------------------------------------------------------------------------- /ui/js/mode/d.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("d", function(config, parserConfig) { 2 | var indentUnit = config.indentUnit, 3 | statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, 4 | keywords = parserConfig.keywords || {}, 5 | builtin = parserConfig.builtin || {}, 6 | blockKeywords = parserConfig.blockKeywords || {}, 7 | atoms = parserConfig.atoms || {}, 8 | hooks = parserConfig.hooks || {}, 9 | multiLineStrings = parserConfig.multiLineStrings; 10 | var isOperatorChar = /[+\-*&%=<>!?|\/]/; 11 | 12 | var curPunc; 13 | 14 | function tokenBase(stream, state) { 15 | var ch = stream.next(); 16 | if (hooks[ch]) { 17 | var result = hooks[ch](stream, state); 18 | if (result !== false) return result; 19 | } 20 | if (ch == '"' || ch == "'" || ch == "`") { 21 | state.tokenize = tokenString(ch); 22 | return state.tokenize(stream, state); 23 | } 24 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 25 | curPunc = ch; 26 | return null; 27 | } 28 | if (/\d/.test(ch)) { 29 | stream.eatWhile(/[\w\.]/); 30 | return "number"; 31 | } 32 | if (ch == "/") { 33 | if (stream.eat("+")) { 34 | state.tokenize = tokenComment; 35 | return tokenNestedComment(stream, state); 36 | } 37 | if (stream.eat("*")) { 38 | state.tokenize = tokenComment; 39 | return tokenComment(stream, state); 40 | } 41 | if (stream.eat("/")) { 42 | stream.skipToEnd(); 43 | return "comment"; 44 | } 45 | } 46 | if (isOperatorChar.test(ch)) { 47 | stream.eatWhile(isOperatorChar); 48 | return "operator"; 49 | } 50 | stream.eatWhile(/[\w\$_]/); 51 | var cur = stream.current(); 52 | if (keywords.propertyIsEnumerable(cur)) { 53 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; 54 | return "keyword"; 55 | } 56 | if (builtin.propertyIsEnumerable(cur)) { 57 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; 58 | return "builtin"; 59 | } 60 | if (atoms.propertyIsEnumerable(cur)) return "atom"; 61 | return "variable"; 62 | } 63 | 64 | function tokenString(quote) { 65 | return function(stream, state) { 66 | var escaped = false, next, end = false; 67 | while ((next = stream.next()) != null) { 68 | if (next == quote && !escaped) {end = true; break;} 69 | escaped = !escaped && next == "\\"; 70 | } 71 | if (end || !(escaped || multiLineStrings)) 72 | state.tokenize = null; 73 | return "string"; 74 | }; 75 | } 76 | 77 | function tokenComment(stream, state) { 78 | var maybeEnd = false, ch; 79 | while (ch = stream.next()) { 80 | if (ch == "/" && maybeEnd) { 81 | state.tokenize = null; 82 | break; 83 | } 84 | maybeEnd = (ch == "*"); 85 | } 86 | return "comment"; 87 | } 88 | 89 | function tokenNestedComment(stream, state) { 90 | var maybeEnd = false, ch; 91 | while (ch = stream.next()) { 92 | if (ch == "/" && maybeEnd) { 93 | state.tokenize = null; 94 | break; 95 | } 96 | maybeEnd = (ch == "+"); 97 | } 98 | return "comment"; 99 | } 100 | 101 | function Context(indented, column, type, align, prev) { 102 | this.indented = indented; 103 | this.column = column; 104 | this.type = type; 105 | this.align = align; 106 | this.prev = prev; 107 | } 108 | function pushContext(state, col, type) { 109 | var indent = state.indented; 110 | if (state.context && state.context.type == "statement") 111 | indent = state.context.indented; 112 | return state.context = new Context(indent, col, type, null, state.context); 113 | } 114 | function popContext(state) { 115 | var t = state.context.type; 116 | if (t == ")" || t == "]" || t == "}") 117 | state.indented = state.context.indented; 118 | return state.context = state.context.prev; 119 | } 120 | 121 | // Interface 122 | 123 | return { 124 | startState: function(basecolumn) { 125 | return { 126 | tokenize: null, 127 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), 128 | indented: 0, 129 | startOfLine: true 130 | }; 131 | }, 132 | 133 | token: function(stream, state) { 134 | var ctx = state.context; 135 | if (stream.sol()) { 136 | if (ctx.align == null) ctx.align = false; 137 | state.indented = stream.indentation(); 138 | state.startOfLine = true; 139 | } 140 | if (stream.eatSpace()) return null; 141 | curPunc = null; 142 | var style = (state.tokenize || tokenBase)(stream, state); 143 | if (style == "comment" || style == "meta") return style; 144 | if (ctx.align == null) ctx.align = true; 145 | 146 | if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state); 147 | else if (curPunc == "{") pushContext(state, stream.column(), "}"); 148 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 149 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 150 | else if (curPunc == "}") { 151 | while (ctx.type == "statement") ctx = popContext(state); 152 | if (ctx.type == "}") ctx = popContext(state); 153 | while (ctx.type == "statement") ctx = popContext(state); 154 | } 155 | else if (curPunc == ctx.type) popContext(state); 156 | else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement")) 157 | pushContext(state, stream.column(), "statement"); 158 | state.startOfLine = false; 159 | return style; 160 | }, 161 | 162 | indent: function(state, textAfter) { 163 | if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; 164 | var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); 165 | if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; 166 | var closing = firstChar == ctx.type; 167 | if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); 168 | else if (ctx.align) return ctx.column + (closing ? 0 : 1); 169 | else return ctx.indented + (closing ? 0 : indentUnit); 170 | }, 171 | 172 | electricChars: "{}" 173 | }; 174 | }); 175 | 176 | (function() { 177 | function words(str) { 178 | var obj = {}, words = str.split(" "); 179 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 180 | return obj; 181 | } 182 | 183 | var blockKeywords = "body catch class do else enum for foreach foreach_reverse if in interface mixin " + 184 | "out scope struct switch try union unittest version while with"; 185 | 186 | CodeMirror.defineMIME("text/x-d", { 187 | name: "d", 188 | keywords: words("abstract alias align asm assert auto break case cast cdouble cent cfloat const continue " + 189 | "debug default delegate delete deprecated export extern final finally function goto immutable " + 190 | "import inout invariant is lazy macro module new nothrow override package pragma private " + 191 | "protected public pure ref return shared short static super synchronized template this " + 192 | "throw typedef typeid typeof volatile __FILE__ __LINE__ __gshared __traits __vector __parameters " + 193 | blockKeywords), 194 | blockKeywords: words(blockKeywords), 195 | builtin: words("bool byte char creal dchar double float idouble ifloat int ireal long real short ubyte " + 196 | "ucent uint ulong ushort wchar wstring void size_t sizediff_t"), 197 | atoms: words("exit failure success true false null"), 198 | hooks: { 199 | "@": function(stream, _state) { 200 | stream.eatWhile(/[\w\$_]/); 201 | return "meta"; 202 | } 203 | } 204 | }); 205 | }()); 206 | -------------------------------------------------------------------------------- /ui/js/shortcut.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http://www.openjs.com/scripts/events/keyboard_shortcuts/ 3 | * Version : 2.01.B 4 | * By Binny V A 5 | * License : BSD 6 | */ 7 | shortcut = { 8 | 'all_shortcuts':{},//All the shortcuts are stored in this array 9 | 'add': function(shortcut_combination,callback,opt) { 10 | //Provide a set of default options 11 | var default_options = { 12 | 'type':'keydown', 13 | 'propagate':false, 14 | 'disable_in_input':false, 15 | 'target':document, 16 | 'keycode':false 17 | } 18 | if(!opt) opt = default_options; 19 | else { 20 | for(var dfo in default_options) { 21 | if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo]; 22 | } 23 | } 24 | 25 | var ele = opt.target; 26 | if(typeof opt.target == 'string') ele = document.getElementById(opt.target); 27 | var ths = this; 28 | shortcut_combination = shortcut_combination.toLowerCase(); 29 | 30 | //The function to be called at keypress 31 | var func = function(e) { 32 | e = e || window.event; 33 | 34 | if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields 35 | var element; 36 | if(e.target) element=e.target; 37 | else if(e.srcElement) element=e.srcElement; 38 | if(element.nodeType==3) element=element.parentNode; 39 | 40 | if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return; 41 | } 42 | 43 | //Find Which key is pressed 44 | if (e.keyCode) code = e.keyCode; 45 | else if (e.which) code = e.which; 46 | var character = String.fromCharCode(code).toLowerCase(); 47 | 48 | if(code == 188) character=","; //If the user presses , when the type is onkeydown 49 | if(code == 190) character="."; //If the user presses , when the type is onkeydown 50 | 51 | var keys = shortcut_combination.split("+"); 52 | //Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked 53 | var kp = 0; 54 | 55 | //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken 56 | var shift_nums = { 57 | "`":"~", 58 | "1":"!", 59 | "2":"@", 60 | "3":"#", 61 | "4":"$", 62 | "5":"%", 63 | "6":"^", 64 | "7":"&", 65 | "8":"*", 66 | "9":"(", 67 | "0":")", 68 | "-":"_", 69 | "=":"+", 70 | ";":":", 71 | "'":"\"", 72 | ",":"<", 73 | ".":">", 74 | "/":"?", 75 | "\\":"|" 76 | } 77 | //Special Keys - and their codes 78 | var special_keys = { 79 | 'esc':27, 80 | 'escape':27, 81 | 'tab':9, 82 | 'space':32, 83 | 'return':13, 84 | 'enter':13, 85 | 'backspace':8, 86 | 87 | 'scrolllock':145, 88 | 'scroll_lock':145, 89 | 'scroll':145, 90 | 'capslock':20, 91 | 'caps_lock':20, 92 | 'caps':20, 93 | 'numlock':144, 94 | 'num_lock':144, 95 | 'num':144, 96 | 97 | 'pause':19, 98 | 'break':19, 99 | 100 | 'insert':45, 101 | 'home':36, 102 | 'delete':46, 103 | 'end':35, 104 | 105 | 'pageup':33, 106 | 'page_up':33, 107 | 'pu':33, 108 | 109 | 'pagedown':34, 110 | 'page_down':34, 111 | 'pd':34, 112 | 113 | 'left':37, 114 | 'up':38, 115 | 'right':39, 116 | 'down':40, 117 | 118 | 'f1':112, 119 | 'f2':113, 120 | 'f3':114, 121 | 'f4':115, 122 | 'f5':116, 123 | 'f6':117, 124 | 'f7':118, 125 | 'f8':119, 126 | 'f9':120, 127 | 'f10':121, 128 | 'f11':122, 129 | 'f12':123 130 | } 131 | 132 | var modifiers = { 133 | shift: { wanted:false, pressed:false}, 134 | ctrl : { wanted:false, pressed:false}, 135 | alt : { wanted:false, pressed:false}, 136 | meta : { wanted:false, pressed:false} //Meta is Mac specific 137 | }; 138 | 139 | if(e.ctrlKey) modifiers.ctrl.pressed = true; 140 | if(e.shiftKey) modifiers.shift.pressed = true; 141 | if(e.altKey) modifiers.alt.pressed = true; 142 | if(e.metaKey) modifiers.meta.pressed = true; 143 | 144 | for(var i=0; k=keys[i],i 1) { //If it is a special key 159 | if(special_keys[k] == code) kp++; 160 | } else if(opt['keycode']) { 161 | if(opt['keycode'] == code) kp++; 162 | } else { //The special keys did not match 163 | if(character == k) kp++; 164 | else { 165 | if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase 166 | character = shift_nums[character]; 167 | if(character == k) kp++; 168 | } 169 | } 170 | } 171 | } 172 | 173 | if(kp == keys.length && 174 | modifiers.ctrl.pressed == modifiers.ctrl.wanted && 175 | modifiers.shift.pressed == modifiers.shift.wanted && 176 | modifiers.alt.pressed == modifiers.alt.wanted && 177 | modifiers.meta.pressed == modifiers.meta.wanted) { 178 | callback(e); 179 | 180 | if(!opt['propagate']) { //Stop the event 181 | //e.cancelBubble is supported by IE - this will kill the bubbling process. 182 | e.cancelBubble = true; 183 | e.returnValue = false; 184 | 185 | //e.stopPropagation works in Firefox. 186 | if (e.stopPropagation) { 187 | e.stopPropagation(); 188 | e.preventDefault(); 189 | } 190 | return false; 191 | } 192 | } 193 | } 194 | this.all_shortcuts[shortcut_combination] = { 195 | 'callback':func, 196 | 'target':ele, 197 | 'event': opt['type'] 198 | }; 199 | //Attach the function with the event 200 | if(ele.addEventListener) ele.addEventListener(opt['type'], func, false); 201 | else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func); 202 | else ele['on'+opt['type']] = func; 203 | }, 204 | 205 | //Remove the shortcut - just specify the shortcut and I will remove the binding 206 | 'remove':function(shortcut_combination) { 207 | shortcut_combination = shortcut_combination.toLowerCase(); 208 | var binding = this.all_shortcuts[shortcut_combination]; 209 | delete(this.all_shortcuts[shortcut_combination]) 210 | if(!binding) return; 211 | var type = binding['event']; 212 | var ele = binding['target']; 213 | var callback = binding['callback']; 214 | 215 | if(ele.detachEvent) ele.detachEvent('on'+type, callback); 216 | else if(ele.removeEventListener) ele.removeEventListener(type, callback, false); 217 | else ele['on'+type] = false; 218 | } 219 | } -------------------------------------------------------------------------------- /ui/js/show-hint.js: -------------------------------------------------------------------------------- 1 | CodeMirror.showHint = function(cm, getHints, options) { 2 | if (!options) options = {}; 3 | var startCh = cm.getCursor().ch, continued = false; 4 | var closeOn = options.closeCharacters || /[\s()\[\]{};:>]/; 5 | 6 | function startHinting() { 7 | // We want a single cursor position. 8 | if (cm.somethingSelected()) return; 9 | 10 | if (options.async) 11 | getHints(cm, showHints, options); 12 | else 13 | return showHints(getHints(cm, options)); 14 | } 15 | 16 | function getText(completion) { 17 | if (typeof completion == "string") return completion; 18 | else return completion.text; 19 | } 20 | 21 | function pickCompletion(cm, data, completion) { 22 | if (completion.hint) completion.hint(cm, data, completion); 23 | else cm.replaceRange(getText(completion), data.from, data.to); 24 | } 25 | 26 | function buildKeyMap(options, handle) { 27 | var baseMap = { 28 | Up: function() {handle.moveFocus(-1);}, 29 | Down: function() {handle.moveFocus(1);}, 30 | PageUp: function() {handle.moveFocus(-handle.menuSize());}, 31 | PageDown: function() {handle.moveFocus(handle.menuSize());}, 32 | Home: function() {handle.setFocus(0);}, 33 | End: function() {handle.setFocus(handle.length);}, 34 | Enter: handle.pick, 35 | Tab: handle.pick, 36 | Esc: handle.close 37 | }; 38 | var ourMap = options.customKeys ? {} : baseMap; 39 | function addBinding(key, val) { 40 | var bound; 41 | if (typeof val != "string") 42 | bound = function(cm) { return val(cm, handle); }; 43 | // This mechanism is deprecated 44 | else if (baseMap.hasOwnProperty(val)) 45 | bound = baseMap[val]; 46 | else 47 | bound = val; 48 | ourMap[key] = bound; 49 | } 50 | if (options.customKeys) 51 | for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key)) 52 | addBinding(key, options.customKeys[key]); 53 | if (options.extraKeys) 54 | for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key)) 55 | addBinding(key, options.extraKeys[key]); 56 | return ourMap; 57 | } 58 | 59 | function showHints(data) { 60 | if (!data || !data.list.length) { 61 | if (continued) { 62 | cm.state.completionActive = false; 63 | CodeMirror.signal(data, "close"); 64 | } 65 | return; 66 | } 67 | 68 | var completions = data.list; 69 | if (!continued && options.completeSingle != false && completions.length == 1) { 70 | pickCompletion(cm, data, completions[0]); 71 | return true; 72 | } 73 | 74 | // Build the select widget 75 | var hints = document.createElement("ul"), selectedHint = 0; 76 | hints.className = "CodeMirror-hints"; 77 | for (var i = 0; i < completions.length; ++i) { 78 | var elt = hints.appendChild(document.createElement("li")), completion = completions[i]; 79 | var className = "CodeMirror-hint" + (i ? "" : " CodeMirror-hint-active"); 80 | if (completion.className != null) className = completion.className + " " + className; 81 | elt.className = className; 82 | if (completion.render) completion.render(elt, data, completion); 83 | else elt.appendChild(document.createTextNode(completion.displayText || getText(completion))); 84 | elt.hintId = i; 85 | } 86 | var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null); 87 | var left = pos.left, top = pos.bottom, below = true; 88 | hints.style.left = left + "px"; 89 | hints.style.top = top + "px"; 90 | (options.container || document.body).appendChild(hints); 91 | CodeMirror.signal(data, "shown"); 92 | 93 | // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. 94 | var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); 95 | var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); 96 | var box = hints.getBoundingClientRect(); 97 | var overlapX = box.right - winW, overlapY = box.bottom - winH; 98 | if (overlapX > 0) { 99 | if (box.right - box.left > winW) { 100 | hints.style.width = (winW - 5) + "px"; 101 | overlapX -= (box.right - box.left) - winW; 102 | } 103 | hints.style.left = (left = pos.left - overlapX) + "px"; 104 | } 105 | if (overlapY > 0) { 106 | var height = box.bottom - box.top; 107 | if (box.top - (pos.bottom - pos.top) - height > 0) { 108 | overlapY = height + (pos.bottom - pos.top); 109 | below = false; 110 | } else if (height > winH) { 111 | hints.style.height = (winH - 5) + "px"; 112 | overlapY -= height - winH; 113 | } 114 | hints.style.top = (top = pos.bottom - overlapY) + "px"; 115 | } 116 | 117 | function changeActive(i) { 118 | i = Math.max(0, Math.min(i, completions.length - 1)); 119 | if (selectedHint == i) return; 120 | var node = hints.childNodes[selectedHint]; 121 | node.className = node.className.replace(" CodeMirror-hint-active", ""); 122 | node = hints.childNodes[selectedHint = i]; 123 | node.className += " CodeMirror-hint-active"; 124 | if (node.offsetTop < hints.scrollTop) 125 | hints.scrollTop = node.offsetTop - 3; 126 | else if (node.offsetTop + node.offsetHeight > hints.scrollTop + hints.clientHeight) 127 | hints.scrollTop = node.offsetTop + node.offsetHeight - hints.clientHeight + 3; 128 | CodeMirror.signal(data, "select", completions[selectedHint], node); 129 | } 130 | 131 | function screenAmount() { 132 | return Math.floor(hints.clientHeight / hints.firstChild.offsetHeight) || 1; 133 | } 134 | 135 | var keyMap = buildKeyMap(options, { 136 | moveFocus: function(n) { changeActive(selectedHint + n); }, 137 | setFocus: function(n) { changeActive(n); }, 138 | menuSize: function() { return screenAmount(); }, 139 | length: completions.length, 140 | close: close, 141 | pick: pick 142 | }); 143 | 144 | cm.state.completionActive = true; 145 | cm.addKeyMap(keyMap); 146 | cm.on("cursorActivity", cursorActivity); 147 | var closingOnBlur; 148 | function onBlur(){ closingOnBlur = setTimeout(close, 100); }; 149 | function onFocus(){ clearTimeout(closingOnBlur); }; 150 | if (options.closeOnUnfocus !== false) { 151 | cm.on("blur", onBlur); 152 | cm.on("focus", onFocus); 153 | } 154 | var startScroll = cm.getScrollInfo(); 155 | function onScroll() { 156 | var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect(); 157 | var newTop = top + startScroll.top - curScroll.top, point = newTop; 158 | if (!below) point += hints.offsetHeight; 159 | if (point <= editor.top || point >= editor.bottom) return close(); 160 | hints.style.top = newTop + "px"; 161 | hints.style.left = (left + startScroll.left - curScroll.left) + "px"; 162 | } 163 | cm.on("scroll", onScroll); 164 | CodeMirror.on(hints, "dblclick", function(e) { 165 | var t = e.target || e.srcElement; 166 | if (t.hintId != null) {selectedHint = t.hintId; pick();} 167 | }); 168 | CodeMirror.on(hints, "click", function(e) { 169 | var t = e.target || e.srcElement; 170 | if (t.hintId != null) changeActive(t.hintId); 171 | }); 172 | CodeMirror.on(hints, "mousedown", function() { 173 | setTimeout(function(){cm.focus();}, 20); 174 | }); 175 | 176 | var done = false, once; 177 | function close(willContinue) { 178 | if (done) return; 179 | done = true; 180 | clearTimeout(once); 181 | hints.parentNode.removeChild(hints); 182 | cm.removeKeyMap(keyMap); 183 | cm.off("cursorActivity", cursorActivity); 184 | if (options.closeOnUnfocus !== false) { 185 | cm.off("blur", onBlur); 186 | cm.off("focus", onFocus); 187 | } 188 | cm.off("scroll", onScroll); 189 | if (willContinue !== true) { 190 | CodeMirror.signal(data, "close"); 191 | cm.state.completionActive = false; 192 | } 193 | } 194 | function pick() { 195 | pickCompletion(cm, data, completions[selectedHint]); 196 | close(); 197 | } 198 | var once, lastPos = cm.getCursor(), lastLen = cm.getLine(lastPos.line).length; 199 | function cursorActivity() { 200 | clearTimeout(once); 201 | 202 | var pos = cm.getCursor(), line = cm.getLine(pos.line); 203 | if (pos.line != lastPos.line || line.length - pos.ch != lastLen - lastPos.ch || 204 | pos.ch < startCh || cm.somethingSelected() || 205 | (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) 206 | close(); 207 | else { 208 | once = setTimeout(function(){close(true); continued = true; startHinting();}, 170); 209 | } 210 | } 211 | CodeMirror.signal(data, "select", completions[0], hints.firstChild); 212 | return true; 213 | } 214 | 215 | return startHinting(); 216 | }; 217 | --------------------------------------------------------------------------------