1 module zua;
2 public import zua.vm.engine : Traceframe, LuaError;
3 public import zua.vm.std.stdlib : GlobalOptions;
4 public import zua.interop : DConsumable;
5 public import zua.interop.table : Table;
6 import zua.vm.engine : Value, TableValue;
7 import zua.interop;
8 import zua.compiler.sourcemap;
9 import zua.vm.std.stdlib;
10 import zua.diagnostic;
11 import std.algorithm;
12 import std.array;
13 import std.typecons;
14 import std.uuid;
15 import std.traits;
16 
17 /*
18 
19 Zua uses a client-server model to secure debug information while still keeping it accessible to developers.
20 The client sends the server the bytecode location where an error (or other diagnostic) occurred, and the server decodes this into source location.
21 How the client and server communicate is up to the library user to implement.
22 However, if they are running on the same machine, there is a class (Common) that combines the two.
23 
24 */
25 
26 /** A bytecode unit; consists of an engine ID and the actual bytecode content */
27 final class BCUnit {
28 	/** Engine ID */
29 	UUID id;
30 
31 	/** Raw content */
32 	immutable(ubyte)[] content;
33 }
34 
35 /** Represents a single trace frame with resolved debugging symbols */
36 struct ResolvedTraceframe {
37 	/** The file in which this frame occurred, or null if it is a D function */
38 	Nullable!string filename;
39 
40 	/** The start index of the AST node being executed at the time of this stack frame */
41 	size_t start;
42 
43 	/** The end index of the AST node being executed at the time of this stack frame */
44 	size_t end;
45 }
46 
47 /** The result of a compilation */
48 final class CompilationResult {
49 	/** Bytecode unit; MAY BE NULL if any errors occurred during compilation */
50 	BCUnit unit;
51 
52 	/** A list of diagnostic messages */
53 	Diagnostic[] diagnostics;
54 }
55 
56 /** A bytecode-producing server */
57 final class Server {
58 	private string[UUID] fileMap;
59 	private SourceMap[UUID] sourceMaps;
60 
61 	/** Compile source code into a CompilationResult */
62 	CompilationResult compile(string filename, string source) {
63 		import zua.parser.lexer : Lexer;
64 		import zua.parser.parser : Parser;
65 		import zua.parser.analysis : performAnalysis;
66 		import zua.compiler.ir : compileAST;
67 		import zua.compiler.compiler : compile;
68 
69 		auto id = randomUUID();
70 		auto res = new CompilationResult;
71 		fileMap[id] = filename;
72 		auto map = new SourceMap;
73 		sourceMaps[id] = map;
74 		Lexer lexer = new Lexer(source, res.diagnostics);
75 		Parser parser = new Parser(lexer, res.diagnostics);
76 		auto toplevel = parser.toplevel();
77 		performAnalysis(res.diagnostics, toplevel);
78 		foreach (d; res.diagnostics) {
79 			if (d.type == DiagnosticType.Error) {
80 				res.unit = null;
81 				return res;
82 			}
83 		}
84 		res.unit = new BCUnit;
85 		res.unit.id = id;
86 		auto func = compileAST(toplevel);
87 		res.unit.content = compile(map, func);
88 		return res;
89 	}
90 
91 	/** Resolve the stack trace of a Lua error */
92 	ResolvedTraceframe[] resolve(Traceframe[] stack) {
93 		ResolvedTraceframe[] res;
94 		foreach (frame; stack) {
95 			ResolvedTraceframe resf;
96 			string* filename = frame.id in fileMap;
97 			if (filename == null) {
98 				resf.filename = Nullable!string();
99 			}
100 			else {
101 				const indices = sourceMaps[frame.id].resolve(frame.ip);
102 				resf.start = indices.start;
103 				resf.end = indices.end;
104 				resf.filename = (*filename).nullable;
105 			}
106 			res ~= resf;
107 		}
108 		return res;
109 	}
110 }
111 
112 /** An executing client */
113 final class Client {
114 	/** The global environment of this client */
115 	Table env;
116 
117 	/** Create a new Client */
118 	this(GlobalOptions context) {
119 		env = DConsumable(Value(stdenv(context))).get!Table;
120 	}
121 
122 	/** Run a bytecode unit */
123 	DConsumable[] run(BCUnit unit, DConsumable[] args) {
124 		import zua.vm.vm : VmEngine;
125 
126 		VmEngine engine = new VmEngine(unit.content, unit.id);
127 		return engine.getToplevel(env._internalTable.table)
128 			.ccall(args.map!(x => x.makeInternalValue).array).makeConsumable;
129 	}
130 
131 	/** Run a bytecode unit with the given parameters */
132 	DConsumable[] run(T...)(BCUnit unit, T args) {
133 		import zua.vm.vm : VmEngine;
134 
135 		VmEngine engine = new VmEngine(unit.content, unit.id);
136 		Value[] luaArgs;
137 		luaArgs.reserve(T.length);
138 		static foreach (i; 0..T.length) {
139 			static assert(isConvertible!(T[i]));
140 			luaArgs ~= DConsumable(args[i]).makeInternalValue;
141 		}
142 		return engine.getToplevel(env._internalTable.table).ccall(luaArgs).makeConsumable;
143 	}
144 }
145 
146 /** A class capable both of executing and producing bytecode */
147 final class Common {
148 	/** The server component of this Common */
149 	Server server;
150 
151 	/** The client component of this Common */
152 	Client client;
153 
154 	alias client this;
155 
156 	/** Create a new Common */
157 	this(GlobalOptions context) {
158 		server = new Server;
159 		client = new Client(context);
160 	}
161 
162 	/** Run some source */
163 	Tuple!(Diagnostic[], DConsumable[]) run(string filename, string source, DConsumable[] args) {
164 		auto compiled = server.compile(filename, source);
165 		if (compiled.unit is null) {
166 			return tuple(compiled.diagnostics, cast(DConsumable[])[]);
167 		}
168 		else {
169 			return tuple(compiled.diagnostics, client.run(compiled.unit, args));
170 		}
171 	}
172 
173 	/** Run some source */
174 	Tuple!(Diagnostic[], DConsumable[]) run(T...)(string filename, string source, T args) {
175 		auto compiled = server.compile(filename, source);
176 		if (compiled.unit is null) {
177 			return tuple(compiled.diagnostics, cast(DConsumable[])[]);
178 		}
179 		else {
180 			return tuple(compiled.diagnostics, client.run(compiled.unit, args));
181 		}
182 	}
183 
184 	/** Resolve the stack trace of a Lua error */
185 	ResolvedTraceframe[] resolve(Traceframe[] stack) {
186 		return server.resolve(stack);
187 	}
188 }