├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── dscanner.ini ├── dub.json ├── dub.selections.json └── src ├── console.d └── drepl ├── engines ├── dmd.d ├── echo.d └── package.d ├── interpreter.d └── package.d /.gitignore: -------------------------------------------------------------------------------- 1 | # D 2 | .dub 3 | 4 | # Build files 5 | /bin 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | d: 3 | - dmd 4 | - ldc 5 | 6 | sudo: false 7 | 8 | install: 9 | # linenoise 10 | - curl -fsSL --retry 5 --retry-max-time 120 https://github.com/antirez/linenoise/archive/1.0.tar.gz | tar -zxf - 11 | - (cd linenoise-1.0 && cc -shared -fPIC linenoise.c -o liblinenoise.so) 12 | - export LIBRARY_PATH="./linenoise-1.0:$LIBRARY_PATH" 13 | - export LD_LIBRARY_PATH="./linenoise-1.0:$LD_LIBRARY_PATH" 14 | 15 | script: 16 | - dub lint 17 | - dub test --compiler=$DC 18 | - dub build --compiler=$DC 19 | - echo 10+3 | dub --compiler=$DC | grep 13 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | drepl 2 | ===== 3 | 4 | A REPL for D. 5 | 6 | [![DUB Package](https://img.shields.io/dub/v/drepl.svg)](https://code.dlang.org/packages/drepl) 7 | [![Build Status](https://travis-ci.org/dlang-community/drepl.png)](https://travis-ci.org/dlang-community/drepl) 8 | 9 | How to run? 10 | ----------- 11 | 12 | Install DUB (D's package manager) from the [official releases](https://dlang.org/download.html). 13 | Then fetch and start the `drepl`: 14 | 15 | ```bash 16 | dub fetch drepl 17 | dub run drepl 18 | ``` 19 | 20 | How to try it out with Docker? 21 | ------------------------------ 22 | 23 | Just run `docker run -ti dlanguage/drepl` 24 | 25 | Details: https://hub.docker.com/r/dlanguage/drepl/ 26 | 27 | Supported OS 28 | ------------------------------ 29 | Works on any OS with full shared library support by DMD (currently linux, OSX, and FreeBSD). 30 | -------------------------------------------------------------------------------- /dscanner.ini: -------------------------------------------------------------------------------- 1 | [analysis.config.StaticAnalysisConfig] 2 | undocumented_declaration_check="disabled" 3 | unused_result="disabled" 4 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drepl", 3 | "description": "A D interpreter.", 4 | "copyright": "Copyright © 2014-2016, DREPL team", 5 | "license": "BSL-1.0", 6 | "authors": ["Martin Nowak"], 7 | "lflags-linux-dmd": ["-l:libphobos2.so"], 8 | "lflags-linux-ldc": ["-l:libphobos2-ldc-shared.so", "-l:libdruntime-ldc-shared.so"], 9 | "lflags-linux": ["-ldl"], 10 | "targetPath": "bin/", 11 | "configurations": [ 12 | { 13 | "name": "console", 14 | "mainSourceFile": "src/console.d", 15 | "dependencies": { 16 | "linenoise": "~>1.1.0" 17 | }, 18 | "targetType": "executable", 19 | "subConfigurations": { 20 | "linenoise": "vendored" 21 | } 22 | }, 23 | { 24 | "name": "library", 25 | "excludedSourceFiles": ["src/console.d"], 26 | "targetType": "library", 27 | }, 28 | ], 29 | "dependencies": { 30 | "colorize": "~>1.0.5", 31 | "libdparse": "~>0.17.0" 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "colorize": "1.0.5", 5 | "libdparse": "0.17.0", 6 | "linenoise": "1.1.0+1.0.0", 7 | "stdx-allocator": "2.77.5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/console.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: Martin Nowak 2013 - 3 | License: Subject to the terms of the MIT license, as written in the included LICENSE file. 4 | Authors: $(WEB code.dawg.eu, Martin Nowak) 5 | */ 6 | import std.stdio, std.string, std.path, std.process; 7 | import core.stdc.string : strlen; 8 | import deimos.linenoise; 9 | import drepl; 10 | 11 | void main() 12 | { 13 | import colorize : color, cwriteln, fg; 14 | 15 | writeln("Welcome to D REPL."); 16 | 17 | auto history = buildPath(environment.get("HOME", ""), ".drepl_history").toStringz(); 18 | linenoiseHistoryLoad(history); 19 | 20 | auto intp = interpreter(dmdEngine()); 21 | 22 | char *line; 23 | const(char) *prompt = "D> "; 24 | while((line = linenoise(prompt)) !is null) 25 | { 26 | linenoiseHistoryAdd(line); 27 | linenoiseHistorySave(history); 28 | 29 | auto res = intp.interpret(line[0 .. strlen(line)]); 30 | final switch (res.state) with(InterpreterResult.State) 31 | { 32 | case incomplete: 33 | prompt = " | "; 34 | break; 35 | 36 | case success: 37 | case error: 38 | if (res.stderr.length) cwriteln(res.stderr.color(fg.red)); 39 | if (res.stdout.length) cwriteln(res.stdout.color(fg.green)); 40 | prompt = "D> "; 41 | break; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/drepl/engines/dmd.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: Martin Nowak 2013 - 3 | License: Subject to the terms of the MIT license, as written in the included LICENSE file. 4 | Authors: $(WEB code.dawg.eu, Martin Nowak) 5 | */ 6 | module drepl.engines.dmd; 7 | import drepl.engines; 8 | import std.algorithm, std.exception, std.file, std.path, std.process, std.range, std.stdio, std.string, std.typecons; 9 | 10 | //------------------------------------------------------------------------------ 11 | // tmpfile et. al. 12 | 13 | // should be in core.stdc.stdlib 14 | version (Posix) extern(C) char* mkdtemp(char* template_); 15 | 16 | string mkdtemp() 17 | { 18 | version (Posix) 19 | { 20 | import core.stdc.string : strlen; 21 | auto tmp = buildPath(tempDir(), "drepl.XXXXXX\0").dup; 22 | auto dir = mkdtemp(tmp.ptr); 23 | return dir[0 .. strlen(dir)].idup; 24 | } 25 | else 26 | { 27 | import std.format : format; 28 | import std.random: uniform; 29 | 30 | string path; 31 | do 32 | { 33 | path = buildPath(tempDir(), format("drepl.%06X\0", uniform(0, 0xFFFFFF))); 34 | } while (path.exists); 35 | return path; 36 | } 37 | } 38 | 39 | //------------------------------------------------------------------------------ 40 | 41 | DMDEngine dmdEngine() 42 | { 43 | version(DigitalMars) 44 | auto compiler = environment.get("DMD", "dmd"); 45 | else version(LDC) 46 | auto compiler = environment.get("DMD", "ldmd2"); 47 | auto tmpDir = mkdtemp(); 48 | return DMDEngine(compiler, tmpDir); 49 | } 50 | 51 | struct DMDEngine 52 | { 53 | string _compiler; 54 | string _tmpDir; 55 | size_t _id; 56 | 57 | @disable this(this); 58 | 59 | this(string compiler, string tmpDir) 60 | { 61 | _compiler = compiler; 62 | _tmpDir = tmpDir; 63 | if (_tmpDir.exists) rmdirRecurse(_tmpDir); 64 | mkdirRecurse(_tmpDir); 65 | } 66 | 67 | ~this() 68 | { 69 | if (_tmpDir) rmdirRecurse(_tmpDir); 70 | } 71 | 72 | EngineResult evalDecl(in char[] decl) 73 | { 74 | auto m = newModule(); 75 | m.f.writefln(q{ 76 | // for public imports 77 | public %1$s 78 | 79 | extern(C) void _decls() 80 | { 81 | import std.algorithm, std.stdio; 82 | writef("%%-(%%s, %%)", [__traits(allMembers, _mod%2$s)][1 .. $] 83 | .filter!(d => !d.startsWith("_"))); 84 | } 85 | }.outdent(), decl, _id); 86 | m.f.close(); 87 | 88 | if (auto err = compileModule(m.path)) 89 | return EngineResult(false, "", err); 90 | 91 | ++_id; 92 | 93 | auto func = cast(void function())loadFunc(m.path, "_decls"); 94 | return captureOutput(func); 95 | } 96 | 97 | EngineResult evalExpr(in char[] expr) 98 | { 99 | auto m = newModule(); 100 | m.f.writefln(q{ 101 | extern(C) void _expr() 102 | { 103 | import std.stdio; 104 | static if (is(typeof((() => (%1$s))()) == void)) 105 | (%1$s), write("void"); 106 | else 107 | write((%1$s)); 108 | } 109 | }.outdent(), expr); 110 | m.f.close(); 111 | 112 | if (auto err = compileModule(m.path)) 113 | return EngineResult(false, "", err); 114 | 115 | ++_id; 116 | 117 | auto func = cast(void function())loadFunc(m.path, "_expr"); 118 | return captureOutput(func); 119 | } 120 | 121 | EngineResult evalStmt(in char[] stmt) 122 | { 123 | auto m = newModule(); 124 | m.f.writefln(q{ 125 | extern(C) void _run() 126 | { 127 | %s 128 | } 129 | }, stmt); 130 | m.f.close(); 131 | 132 | if (auto err = compileModule(m.path)) 133 | return EngineResult(false, "", err); 134 | 135 | ++_id; 136 | 137 | auto func = cast(void function())loadFunc(m.path, "_run"); 138 | return captureOutput(func); 139 | } 140 | 141 | private: 142 | EngineResult captureOutput(void function() dg) 143 | { 144 | // TODO: cleanup, error checking... 145 | import core.sys.posix.fcntl, core.sys.posix.unistd, std.conv : octal; 146 | 147 | .stdout.flush(); 148 | .stderr.flush(); 149 | immutable 150 | saveOut = dup(.stdout.fileno), 151 | saveErr = dup(.stderr.fileno), 152 | capOut = open(toStringz(_tmpDir~"/_stdout"), O_WRONLY|O_CREAT|O_TRUNC, octal!600), 153 | capErr = open(toStringz(_tmpDir~"/_stderr"), O_WRONLY|O_CREAT|O_TRUNC, octal!600); 154 | dup2(capOut, .stdout.fileno); 155 | dup2(capErr, .stderr.fileno); 156 | 157 | bool success = true; 158 | try 159 | { 160 | dg(); 161 | } 162 | catch (Exception e) 163 | { 164 | success = false; 165 | stderr.writeln(e.toString()); 166 | } 167 | .stdout.flush(); 168 | .stderr.flush(); 169 | close(.stdout.fileno); 170 | close(.stderr.fileno); 171 | dup2(saveOut, .stdout.fileno); 172 | dup2(saveErr, .stderr.fileno); 173 | close(saveOut); 174 | close(saveErr); 175 | return EngineResult( 176 | success, readText(_tmpDir~"/_stdout"), readText(_tmpDir~"/_stderr")); 177 | } 178 | 179 | Tuple!(File, "f", string, "path") newModule() 180 | { 181 | auto path = buildPath(_tmpDir, format("_mod%s", _id)); 182 | auto f = File(path~".d", "w"); 183 | writeHeader(f); 184 | return typeof(return)(f, path); 185 | } 186 | 187 | void writeHeader(ref File f) 188 | { 189 | if (_id > 0) 190 | { 191 | f.write("import _mod0"); 192 | foreach (i; 1 .. _id) 193 | f.writef(", _mod%s", i); 194 | f.write(";"); 195 | } 196 | } 197 | 198 | string compileModule(string path) 199 | { 200 | string[] linkerArgs; 201 | final switch(_compiler) 202 | { 203 | case "dmd": 204 | linkerArgs = ["-L-l:libphobos2.so"]; 205 | break; 206 | case "ldmd": 207 | case "ldmd2": 208 | linkerArgs = ["-L-l:libphobos2-ldc-shared.so"]; 209 | break; 210 | } 211 | 212 | import std.regex: ctRegex, replaceAll; 213 | auto args = [_compiler, "-I"~_tmpDir, "-of"~path~".so", "-fPIC", 214 | "-shared", path] ~ linkerArgs; 215 | foreach (i; 0 .. _id) 216 | args ~= "-L"~_tmpDir~format("/_mod%s.so", i); 217 | auto dmd = execute(args); 218 | enum cleanErr = ctRegex!(`^.*Error: `, "m"); 219 | if (dmd.status != 0) 220 | return dmd.output.replaceAll(cleanErr, ""); 221 | if (!exists(path~".so")) 222 | return path~".so not found"; 223 | return null; 224 | } 225 | 226 | void* loadFunc(string path, string name) 227 | { 228 | import core.runtime: Runtime; 229 | import core.sys.posix.dlfcn: dlerror, dlsym; 230 | 231 | auto lib = Runtime.loadLibrary(path~".so"); 232 | if (lib is null) 233 | { 234 | auto msg = dlerror(); import core.stdc.string : strlen; 235 | throw new Exception("failed to load "~path~".so ("~msg[0 .. strlen(msg)].idup~")"); 236 | } 237 | return dlsym(lib, toStringz(name)); 238 | } 239 | } 240 | 241 | static assert(isEngine!DMDEngine); 242 | 243 | unittest 244 | { 245 | alias ER = EngineResult; 246 | 247 | DMDEngine dmd; 248 | 249 | dmd = dmdEngine(); 250 | assert(dmd.evalExpr("5+2") == ER(true, "7")); 251 | assert(dmd.evalDecl("string foo() { return \"bar\"; }") == ER(true, "foo")); 252 | assert(dmd.evalExpr("foo()") == ER(true, "bar")); 253 | assert(!dmd.evalExpr("bar()").success); 254 | 255 | assert(dmd.evalDecl("struct Foo { }") == ER(true, "Foo")); 256 | assert(dmd.evalDecl("Foo f;") == ER(true, "f")); 257 | assert(dmd.evalStmt("f = Foo();") == ER(true, "")); 258 | 259 | dmd = dmdEngine(); 260 | assert(dmd.evalDecl("void foo() {}") == ER(true, "foo")); 261 | assert(dmd.evalExpr("foo()") == ER(true, "void")); 262 | 263 | dmd = dmdEngine(); 264 | assert(dmd.evalDecl("import std.stdio;").success); 265 | assert(dmd.evalStmt("writeln(\"foo\");") == ER(true, "foo\n")); 266 | } 267 | 268 | unittest 269 | { 270 | alias ER = EngineResult; 271 | auto dmd = dmdEngine(); 272 | assert(dmd.evalDecl("void foo(int) {}") == ER(true, "foo")); 273 | const auto er = dmd.evalStmt("foo(\"foo\");"); 274 | assert(!er.success); 275 | assert(er.stdout.empty); 276 | assert(!er.stderr.empty); 277 | } 278 | -------------------------------------------------------------------------------- /src/drepl/engines/echo.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: Martin Nowak 2013 - 3 | License: Subject to the terms of the MIT license, as written in the included LICENSE file. 4 | Authors: $(WEB code.dawg.eu, Martin Nowak) 5 | */ 6 | module drepl.engines.echo; 7 | import drepl.engines; 8 | import std.typecons, std.string : stripRight; 9 | 10 | EchoEngine echoEngine() 11 | { 12 | return EchoEngine(); 13 | } 14 | 15 | struct EchoEngine 16 | { 17 | EngineResult evalDecl(in char[] decl) 18 | { 19 | return EngineResult(true, decl.stripRight.idup); 20 | } 21 | 22 | EngineResult evalExpr(in char[] expr) 23 | { 24 | return EngineResult(true, expr.stripRight.idup); 25 | } 26 | 27 | EngineResult evalStmt(in char[] stmt) 28 | { 29 | return EngineResult(true, stmt.stripRight.idup); 30 | } 31 | } 32 | 33 | static assert(isEngine!EchoEngine); 34 | 35 | unittest 36 | { 37 | alias ER = EngineResult; 38 | 39 | auto e = echoEngine(); 40 | assert(e.evalDecl("void foo() {\n}") == ER(true, "void foo() {\n}")); 41 | assert(e.evalExpr("3 * foo") == ER(true, "3 * foo")); 42 | assert(e.evalStmt("writeln(`foobar`);") == ER(true, "writeln(`foobar`);")); 43 | } 44 | -------------------------------------------------------------------------------- /src/drepl/engines/package.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: Martin Nowak 2013 - 3 | License: Subject to the terms of the MIT license, as written in the included LICENSE file. 4 | Authors: $(WEB code.dawg.eu, Martin Nowak) 5 | */ 6 | module drepl.engines; 7 | public import drepl.engines.dmd, drepl.engines.echo; 8 | import std.typecons; 9 | 10 | struct EngineResult 11 | { 12 | bool success; 13 | string stdout, stderr; 14 | } 15 | 16 | template isEngine(Engine) 17 | { 18 | enum isEngine = 19 | is(typeof(Engine.evalDecl("")) == EngineResult) && 20 | is(typeof(Engine.evalExpr("")) == EngineResult) && 21 | is(typeof(Engine.evalStmt("")) == EngineResult); 22 | } 23 | -------------------------------------------------------------------------------- /src/drepl/interpreter.d: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: Martin Nowak 2013 - 3 | License: Subject to the terms of the MIT license, as written in the included LICENSE file. 4 | Authors: $(WEB code.dawg.eu, Martin Nowak) 5 | */ 6 | module drepl.interpreter; 7 | import drepl.engines; 8 | import std.algorithm, std.array, std.conv, std.string, std.typecons; 9 | 10 | struct InterpreterResult 11 | { 12 | enum State 13 | { 14 | success, 15 | error, 16 | incomplete 17 | } 18 | 19 | State state; 20 | string stdout, stderr; 21 | } 22 | 23 | struct Interpreter(Engine) if (isEngine!Engine) 24 | { 25 | alias IR = InterpreterResult; 26 | IR interpret(const(char)[] line) 27 | { 28 | line = stripRight(line); 29 | // ignore empty lines or comment without incomplete input 30 | if (!_incomplete.data.length && (!line.length || byToken(cast(ubyte[])line).empty)) 31 | return IR(IR.State.success); 32 | 33 | if (line.endsWith("\\")) { 34 | _incomplete.put(line[0 .. line.lastIndexOf("\\")]); 35 | return IR(IR.State.incomplete); 36 | } 37 | 38 | _incomplete.put(line); 39 | _incomplete.put('\n'); 40 | auto input = _incomplete.data; 41 | 42 | // dismiss buffer after two consecutive empty lines 43 | if (input.endsWith("\n\n\n")) 44 | { 45 | _incomplete.clear(); 46 | return IR(IR.State.error, "", "You typed two blank lines. Starting a new command."); 47 | } 48 | 49 | immutable kind = classify(input); 50 | EngineResult res; 51 | final switch (kind) 52 | { 53 | case Kind.Decl: 54 | res = _engine.evalDecl(input); 55 | break; 56 | case Kind.Stmt: 57 | res = _engine.evalStmt(input); 58 | break; 59 | case Kind.Expr: 60 | res = _engine.evalExpr(input); 61 | break; 62 | 63 | case Kind.WhiteSpace: 64 | return IR(IR.State.success); 65 | 66 | case Kind.Incomplete: 67 | return IR(IR.State.incomplete); 68 | 69 | case Kind.Error: 70 | _incomplete.clear(); 71 | return IR(IR.State.error, "", "Error parsing '"~input.strip.idup~"'."); 72 | } 73 | _incomplete.clear(); 74 | return IR(res.success ? IR.State.success : IR.State.error, res.stdout, res.stderr); 75 | } 76 | 77 | enum Kind { Decl, Stmt, Expr, WhiteSpace, Incomplete, Error, } 78 | 79 | import dparse.lexer : getTokensForParser, LexerConfig, byToken, Token, StringCache, tok; 80 | import dparse.parser : Parser; 81 | 82 | Kind classify(in char[] input) 83 | { 84 | scope cache = new StringCache(StringCache.defaultBucketCount); 85 | auto tokens = getTokensForParser(cast(ubyte[])input, LexerConfig(), cache); 86 | if (tokens.empty) return Kind.WhiteSpace; 87 | 88 | auto tokenIds = tokens.map!(t => t.type)(); 89 | if (!tokenIds.balancedParens(tok!"{", tok!"}") || 90 | !tokenIds.balancedParens(tok!"(", tok!")") || 91 | !tokenIds.balancedParens(tok!"[", tok!"]")) 92 | return Kind.Incomplete; 93 | 94 | import std.typetuple : TypeTuple; 95 | foreach (kind; TypeTuple!(Kind.Decl, Kind.Stmt, Kind.Expr)) 96 | if (parse!kind(tokens)) 97 | return kind; 98 | return Kind.Error; 99 | } 100 | 101 | bool parse(Kind kind)(in Token[] tokens) 102 | { 103 | import dparse.rollback_allocator : RollbackAllocator; 104 | scope parser = new Parser(); 105 | RollbackAllocator allocator; 106 | static bool hasErr; 107 | hasErr = false; 108 | parser.fileName = "drepl"; 109 | parser.setTokens(tokens); 110 | parser.allocator = &allocator; 111 | 112 | void messageDelegate (string, size_t, size_t, string, bool isErr) { 113 | if (isErr) hasErr = true; 114 | } 115 | parser.messageDelegate = &messageDelegate; 116 | static if (kind == Kind.Decl) 117 | { 118 | do 119 | { 120 | if (!parser.parseDeclaration()) return false; 121 | } while (parser.moreTokens()); 122 | } 123 | else static if (kind == Kind.Stmt) 124 | { 125 | do 126 | { 127 | if (!parser.parseStatement()) return false; 128 | } while (parser.moreTokens()); 129 | } 130 | else static if (kind == Kind.Expr) 131 | { 132 | if (!parser.parseExpression() || parser.moreTokens()) 133 | return false; 134 | } 135 | return !hasErr; 136 | } 137 | 138 | static if (is(Engine == EchoEngine)) 139 | { 140 | unittest 141 | { 142 | auto intp = interpreter(echoEngine()); 143 | assert(intp.classify("3+2") == Kind.Expr); 144 | // only single expressions 145 | assert(intp.classify("3+2 foo()") == Kind.Error); 146 | assert(intp.classify("3+2;") == Kind.Stmt); 147 | // multiple statements 148 | assert(intp.classify("3+2; foo();") == Kind.Stmt); 149 | assert(intp.classify("struct Foo {}") == Kind.Decl); 150 | // multiple declarations 151 | assert(intp.classify("void foo() {} void bar() {}") == Kind.Decl); 152 | // can't currently mix declarations and statements 153 | assert(intp.classify("void foo() {} foo();") == Kind.Error); 154 | // or declarations and expressions 155 | assert(intp.classify("void foo() {} foo()") == Kind.Error); 156 | // or statments and expressions 157 | assert(intp.classify("foo(); foo()") == Kind.Error); 158 | 159 | assert(intp.classify("import std.stdio;") == Kind.Decl); 160 | } 161 | } 162 | 163 | Engine _engine; 164 | Appender!(char[]) _incomplete; 165 | } 166 | 167 | Interpreter!Engine interpreter(Engine)(return scope Engine e) if (isEngine!Engine) 168 | { 169 | // workaround Issue 18540 170 | return Interpreter!Engine(() @trusted { return move(e); }()); 171 | } 172 | 173 | unittest 174 | { 175 | // test instantiated 176 | const auto i = interpreter(dmdEngine()); 177 | 178 | // mark this as used 179 | cast(void) i; 180 | } 181 | 182 | unittest 183 | { 184 | alias IR = InterpreterResult; 185 | auto intp = interpreter(echoEngine()); 186 | assert(intp.interpret("3 * foo") == IR(IR.State.success, "3 * foo")); 187 | assert(intp.interpret("stmt!(T)();") == IR(IR.State.success, "stmt!(T)();")); 188 | assert(intp.interpret("auto a = 3 * foo;") == IR(IR.State.success, "auto a = 3 * foo;")); 189 | 190 | void testMultiline(string input) 191 | { 192 | import std.string : splitLines; 193 | auto lines = splitLines(input); 194 | foreach (line; lines[0 .. $-1]) 195 | assert(intp.interpret(line) == IR(IR.State.incomplete, "")); 196 | assert(intp.interpret(lines[$-1]) == IR(IR.State.success, input)); 197 | } 198 | 199 | testMultiline( 200 | q{void foo() { 201 | }}); 202 | 203 | testMultiline( 204 | q{int foo() { 205 | auto bar(int v) { return v; } 206 | auto v = 3 * 12; 207 | return bar(v); 208 | }}); 209 | 210 | testMultiline( 211 | q{struct Foo(T) { 212 | void bar() { 213 | } 214 | }}); 215 | 216 | testMultiline( 217 | q{struct Foo(T) { 218 | void bar() { 219 | 220 | } 221 | }}); 222 | } 223 | 224 | unittest 225 | { 226 | alias IR = InterpreterResult; 227 | auto intp = interpreter(echoEngine()); 228 | assert(intp.interpret("struct Foo {").state == IR.State.incomplete); 229 | assert(intp.interpret("").state == IR.State.incomplete); 230 | assert(intp.interpret("").state == IR.State.error); 231 | 232 | assert(intp.interpret("struct Foo {").state == IR.State.incomplete); 233 | assert(intp.interpret("").state == IR.State.incomplete); 234 | assert(intp.interpret("}").state == IR.State.success); 235 | } 236 | 237 | unittest 238 | { 239 | alias IR = InterpreterResult; 240 | auto intp = interpreter(echoEngine()); 241 | assert(intp.interpret("//comment").state == IR.State.success); 242 | assert(intp.interpret("//comment").state == IR.State.success); 243 | 244 | assert(intp.interpret("struct Foo {").state == IR.State.incomplete); 245 | assert(intp.interpret("//comment").state == IR.State.incomplete); 246 | assert(intp.interpret("//comment").state == IR.State.incomplete); 247 | assert(intp.interpret("").state == IR.State.incomplete); 248 | assert(intp.interpret("//comment").state == IR.State.incomplete); 249 | assert(intp.interpret("").state == IR.State.incomplete); 250 | assert(intp.interpret("").state == IR.State.error); 251 | } 252 | -------------------------------------------------------------------------------- /src/drepl/package.d: -------------------------------------------------------------------------------- 1 | module drepl; 2 | public import drepl.interpreter; 3 | public import drepl.engines.echo; 4 | public import drepl.engines.dmd; 5 | --------------------------------------------------------------------------------