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 }