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 }