Appearance
| 1 | namespace Driver is | |
| 2 | use System.Exception | |
| 3 | ||
| 4 | use Collections.LIST | |
| 5 | ||
| 6 | use IO.File | |
| 7 | use IO.Path | |
| 8 | ||
| 9 | use Analysis.Protocol.COMPILE_REPLY | |
| 10 | use Analysis.Protocol.COMPILE_REQUEST | |
| 11 | use Analysis.Protocol.DIAGNOSTIC | |
| 12 | use Analysis.Protocol.JSON_PROTOCOL | |
| 13 | ||
| 14 | // Compiles submissions one after another in one process, so each pays | |
| 15 | // for its own few lines rather than for starting a runtime and importing | |
| 16 | // the reference set. Requests and replies are one line of JSON each; the | |
| 17 | // first line out says the server is ready. | |
| 18 | // | |
| 19 | // Every setting a compile depends on is set again for each request, and | |
| 20 | // everything the previous compile left - its diagnostics, its symbols, | |
| 21 | // its queued files, its emitter - is cleared first, so that a request is | |
| 22 | // answered exactly as a batch compile of the same file would be. What | |
| 23 | // does carry over is the reference set, which only grows: each request's | |
| 24 | // references are imported into it, and that is the point of keeping the | |
| 25 | // process. | |
| 26 | class COMPILE_SERVER( | |
| 27 | _compiler: Compiler.COMPILER, | |
| 28 | _container: IoC.CONTAINER, | |
| 29 | _module_version: string, | |
| 30 | _input: IO.TextReader, | |
| 31 | _output: IO.TextWriter | |
| 32 | ) is | |
| 33 | serve() is | |
| 34 | _write(COMPILE_REPLY.ready_reply()) | |
| 35 | ||
| 36 | do | |
| 37 | let line = _input.read_line() | |
| 38 | ||
| 39 | if !line? then | |
| 40 | return | |
| 41 | fi | |
| 42 | ||
| 43 | _write(answer(line)) | |
| 44 | od | |
| 45 | si | |
| 46 | ||
| 47 | // The reply to one request line. Nothing that goes wrong inside a | |
| 48 | // compile escapes: it becomes a reply with status 2, and the server | |
| 49 | // carries on with the next line. | |
| 50 | answer(line: string) -> COMPILE_REPLY is | |
| 51 | let request: COMPILE_REQUEST? mut = null | |
| 52 | ||
| 53 | try | |
| 54 | request = System.Text.Json.JsonSerializer.deserialize[COMPILE_REQUEST](line, JSON_PROTOCOL.options) | |
| 55 | catch e: Exception | |
| 56 | return COMPILE_REPLY.failure("malformed request: {e.message}") | |
| 57 | yrt | |
| 58 | ||
| 59 | if !request? then | |
| 60 | return COMPILE_REPLY.failure("malformed request: expected an object") | |
| 61 | fi | |
| 62 | ||
| 63 | if request.check then | |
| 64 | return check(request) | |
| 65 | fi | |
| 66 | ||
| 67 | let problem = COMPILE_REQUEST_CHECK.problem_with(request) | |
| 68 | ||
| 69 | if problem? then | |
| 70 | return COMPILE_REPLY.failure(problem) | |
| 71 | fi | |
| 72 | ||
| 73 | try | |
| 74 | return compile(request) | |
| 75 | catch e: Exception | |
| 76 | return COMPILE_REPLY.failure("{e.get_type().name}: {e.message}") | |
| 77 | yrt | |
| 78 | si | |
| 79 | ||
| 80 | // Whether a request's source is complete, parsed with a logger and a | |
| 81 | // parser state of its own: nothing a compile reads is touched, so a | |
| 82 | // check between two compiles changes neither. | |
| 83 | check(request: COMPILE_REQUEST) -> COMPILE_REPLY is | |
| 84 | if !request.text? /\ !request.source? then | |
| 85 | return COMPILE_REPLY.failure("check request has neither source nor text") | |
| 86 | fi | |
| 87 | ||
| 88 | let path = COMPILE_REQUEST_CHECK.source_path(request) | |
| 89 | ||
| 90 | try | |
| 91 | let reader: IO.TextReader = | |
| 92 | if let text = request.text then | |
| 93 | IO.StringReader(text) | |
| 94 | else | |
| 95 | File.open_text(path) | |
| 96 | fi | |
| 97 | ||
| 98 | let use disposing_reader = reader | |
| 99 | ||
| 100 | let checked = _compiler.check(path, reader) | |
| 101 | ||
| 102 | return COMPILE_REPLY.checked( | |
| 103 | case checked.completeness | |
| 104 | when Syntax.Parsers.COMPLETENESS.COMPLETE then "complete" | |
| 105 | when Syntax.Parsers.COMPLETENESS.INCOMPLETE then "incomplete" | |
| 106 | else "invalid" | |
| 107 | esac, | |
| 108 | checked.last_statement) | |
| 109 | catch e: Exception | |
| 110 | return COMPILE_REPLY.failure("{e.get_type().name}: {e.message}") | |
| 111 | yrt | |
| 112 | si | |
| 113 | ||
| 114 | compile(request: COMPILE_REQUEST) -> COMPILE_REPLY is | |
| 115 | _reset(request) | |
| 116 | ||
| 117 | let path = COMPILE_REQUEST_CHECK.source_path(request) | |
| 118 | ||
| 119 | let reader: IO.TextReader = | |
| 120 | if let text = request.text then | |
| 121 | IO.StringReader(text) | |
| 122 | else | |
| 123 | File.open_text(path) | |
| 124 | fi | |
| 125 | ||
| 126 | let use disposing_reader = reader | |
| 127 | ||
| 128 | _compiler.parse_and_queue( | |
| 129 | path, | |
| 130 | reader, | |
| 131 | _container.build_flags.want_compile_up_to_expressions, | |
| 132 | _container.build_flags.want_compile_expressions, | |
| 133 | false) | |
| 134 | ||
| 135 | _compiler.post_parse() | |
| 136 | _compiler.build() | |
| 137 | ||
| 138 | let diagnostics = LIST[DIAGNOSTIC]() | |
| 139 | ||
| 140 | Analysis.DIAGNOSTICS_COLLECTOR.collect_into(_container.logger, diagnostics, LIST[string]()) | |
| 141 | ||
| 142 | let logger = _container.logger | |
| 143 | ||
| 144 | if logger.any_errors then | |
| 145 | return COMPILE_REPLY(false, 1, null, diagnostics) | |
| 146 | elif logger.is_poisoned then | |
| 147 | return COMPILE_REPLY(false, 2, "internal error", diagnostics) | |
| 148 | fi | |
| 149 | ||
| 150 | _container.ir_context.srm_assembly_emitter.write_to(request.output) | |
| 151 | ||
| 152 | return COMPILE_REPLY(false, 0, null, diagnostics) | |
| 153 | si | |
| 154 | ||
| 155 | _reset(request: COMPILE_REQUEST) is | |
| 156 | let flags = _container.build_flags | |
| 157 | ||
| 158 | flags.submission_name = request.name | |
| 159 | flags.want_library = true | |
| 160 | flags.want_executable = false | |
| 161 | ||
| 162 | _container.value_boxer.want_boxing = true | |
| 163 | ||
| 164 | _clear_diagnostics() | |
| 165 | ||
| 166 | _container.state_store_registry.clear_all() | |
| 167 | ||
| 168 | _compiler.clear_queue() | |
| 169 | _compiler.clear_symbols() | |
| 170 | ||
| 171 | Semantic.DotNet.ADDITIONAL_REFERENCES.add(_container, request.references ?? Collections.LIST[string]()) | |
| 172 | ||
| 173 | _container.ir_context.install_srm_assembly_emitter( | |
| 174 | Path.get_file_name_without_extension(request.output) ?? "module", | |
| 175 | _module_version, | |
| 176 | true) | |
| 177 | si | |
| 178 | ||
| 179 | _clear_diagnostics() is | |
| 180 | let logger = _container.logger | |
| 181 | ||
| 182 | for path in LIST[string](logger.diagnostic_paths) do | |
| 183 | logger.clear(path, false) | |
| 184 | od | |
| 185 | ||
| 186 | logger.clear_suppression_regions() | |
| 187 | logger.clear_consumed_error() | |
| 188 | logger.clear_consumed_any() | |
| 189 | si | |
| 190 | ||
| 191 | _write(reply: COMPILE_REPLY) is | |
| 192 | _output.write(System.Text.Json.JsonSerializer.serialize[COMPILE_REPLY](reply, JSON_PROTOCOL.options)) | |
| 193 | _output.write("\n") | |
| 194 | _output.flush() | |
| 195 | si | |
| 196 | si | |
| 197 | ||
| 198 | // What a compile request needs before the server can act on it, and | |
| 199 | // the file name it compiles under. | |
| 200 | class COMPILE_REQUEST_CHECK is | |
| 201 | problem_with(request: COMPILE_REQUEST) -> string? static is | |
| 202 | if string.is_null_or_empty(request.name) then | |
| 203 | return "request has no name" | |
| 204 | fi | |
| 205 | ||
| 206 | if string.is_null_or_empty(request.output) then | |
| 207 | return "request has no output" | |
| 208 | fi | |
| 209 | ||
| 210 | if !request.source? /\ !request.text? then | |
| 211 | return "request has neither source nor text" | |
| 212 | fi | |
| 213 | ||
| 214 | if let source = request.source /\ !request.text? /\ !File.exists(source) then | |
| 215 | return "source file not found: {source}" | |
| 216 | fi | |
| 217 | ||
| 218 | if let references = request.references then | |
| 219 | for reference in references do | |
| 220 | if !File.exists(reference) then | |
| 221 | return "reference not found: {reference}" | |
| 222 | fi | |
| 223 | od | |
| 224 | fi | |
| 225 | ||
| 226 | return null | |
| 227 | si | |
| 228 | ||
| 229 | // Source carried inline is named after the submission, so its | |
| 230 | // diagnostics name a file a reader can tell apart from any other. | |
| 231 | source_path(request: COMPILE_REQUEST) -> string static => | |
| 232 | if request.text? then "{request.name}.ghul" else request.source ?? "{request.name}.ghul" fi | |
| 233 | si | |
| 234 | si |