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 }