Appearance
| 1 | namespace Analysis is | |
| 2 | use System.Exception; | |
| 3 | use IO.Std; | |
| 4 | ||
| 5 | use Collections.Iterable; | |
| 6 | ||
| 7 | use Pair = Collections.KeyValuePair; | |
| 8 | ||
| 9 | use Ghul.Pipes; | |
| 10 | ||
| 11 | use IoC; | |
| 12 | use Logging; | |
| 13 | use Source; | |
| 14 | use Compiler; | |
| 15 | ||
| 16 | class COMMAND_DESPATCHER( | |
| 17 | _reader: IO.TextReader, | |
| 18 | _writer: IO.TextWriter, | |
| 19 | _timers: TIMERS, | |
| 20 | _log: IO.TextWriter, | |
| 21 | _watchdog: WATCHDOG, | |
| 22 | _want_stats_report: bool | |
| 23 | ) is | |
| 24 | _command_map: Collections.MAP[System.Type,CommandHandler]; | |
| 25 | _last_report_time: System.DateTime; | |
| 26 | ||
| 27 | _listening: bool; | |
| 28 | ||
| 29 | init(..) is | |
| 30 | _last_report_time = System.DateTime.now; | |
| 31 | _command_map = Collections.MAP[System.Type,CommandHandler](); | |
| 32 | si | |
| 33 | ||
| 34 | // The compiler's own version, sourced from the entry assembly's | |
| 35 | // AssemblyInformationalVersionAttribute (same source as the driver's | |
| 36 | // startup log line). Exposed in the LISTEN frame so a client can | |
| 37 | // reject a version older than the minimum it supports; a bad | |
| 38 | // version-negotiation error is a lot clearer than a mysterious hang. | |
| 39 | current_compiler_version() -> string? static is | |
| 40 | let attribute = | |
| 41 | System.Reflection.Assembly.get_entry_assembly()! | |
| 42 | .get_custom_attributes(typeof System.Reflection.AssemblyInformationalVersionAttribute, false) | |
| 43 | |> map(a => cast System.Reflection.AssemblyInformationalVersionAttribute?(a)!) | |
| 44 | |> first(); | |
| 45 | ||
| 46 | if attribute? then | |
| 47 | return attribute.informational_version; | |
| 48 | fi | |
| 49 | ||
| 50 | return null; | |
| 51 | si | |
| 52 | ||
| 53 | // Register a handler keyed by the request variant type. A deserialized | |
| 54 | // request routes to its handler by runtime type; the variant's | |
| 55 | // unqualified class name (e.g. `HOVER`, `FORMAT_RANGE`) labels the | |
| 56 | // per-command timer in the stats report. | |
| 57 | add_handler(request_variant: System.Type, command_handler: CommandHandler) is | |
| 58 | assert !_command_map.contains_key(request_variant) else "replacing command handler for {request_variant.name}"; | |
| 59 | ||
| 60 | _command_map[request_variant] = command_handler; | |
| 61 | si | |
| 62 | ||
| 63 | poll() -> bool is | |
| 64 | if !_listening then | |
| 65 | let listen_capabilities = Collections.LIST[string](); | |
| 66 | // Opting into the incremental body re-walk via | |
| 67 | // `--incremental-analysis` is honoured. The flag itself is | |
| 68 | // still off by default; this capability tells a client it is | |
| 69 | // safe to pass. | |
| 70 | listen_capabilities.add("incremental-analysis"); | |
| 71 | ||
| 72 | Protocol.JSON_PROTOCOL.write_response(_writer, Protocol.Response.LISTEN(listen_capabilities, current_compiler_version())); | |
| 73 | ||
| 74 | _listening = true; | |
| 75 | fi | |
| 76 | ||
| 77 | let request: Protocol.Request? mut = null; | |
| 78 | ||
| 79 | try | |
| 80 | request = Protocol.JSON_PROTOCOL.read_request(_reader); | |
| 81 | catch ex: Exception | |
| 82 | // A frame the analyser couldn't deserialize is a protocol | |
| 83 | // mismatch, not analyser instability - typically a client | |
| 84 | // sending a request variant the analyser doesn't know | |
| 85 | // (older analyser + newer client). Answer with a discrete | |
| 86 | // error frame so the client can surface it, and keep the | |
| 87 | // process alive to serve subsequent requests. | |
| 88 | _log.write_line("ghūl: could not parse request: {ex.get_type()} {ex.message}"); | |
| 89 | ||
| 90 | Protocol.JSON_PROTOCOL.write_response( | |
| 91 | _writer, | |
| 92 | Protocol.Response.ERROR("parse", "{ex.get_type().name}: {ex.message}", "") | |
| 93 | ); | |
| 94 | ||
| 95 | _watchdog.on_operation_complete(_writer); | |
| 96 | ||
| 97 | return true; | |
| 98 | yrt | |
| 99 | ||
| 100 | if !request? then | |
| 101 | _log.write_line("ghūl: reader is at end"); | |
| 102 | _log.flush(); | |
| 103 | ||
| 104 | return false; | |
| 105 | fi | |
| 106 | ||
| 107 | let request_variant = request.get_type(); | |
| 108 | let command_name = request_variant.name; | |
| 109 | ||
| 110 | if _command_map.contains_key(request_variant) then | |
| 111 | let handler = _command_map[request_variant]; | |
| 112 | ||
| 113 | _timers.start(command_name); | |
| 114 | handler.handle(request, _writer); | |
| 115 | _timers.finish(command_name); | |
| 116 | ||
| 117 | _watchdog.on_operation_complete(_writer); | |
| 118 | ||
| 119 | if _want_stats_report then | |
| 120 | let elapsed_since_last_report = System.DateTime.now.subtract(_last_report_time); | |
| 121 | ||
| 122 | if elapsed_since_last_report.total_seconds > 60.0D then | |
| 123 | _last_report_time = System.DateTime.now; | |
| 124 | ||
| 125 | _log.write(_timers); | |
| 126 | fi | |
| 127 | fi | |
| 128 | ||
| 129 | return true; | |
| 130 | else | |
| 131 | // Registered variants with no handler are also a protocol | |
| 132 | // mismatch - the request kind exists on the wire but this | |
| 133 | // build doesn't route it anywhere. Same treatment as an | |
| 134 | // unparseable frame: emit an error and keep going. | |
| 135 | _log.write_line("ghūl: no handler found for command: '{command_name}'"); | |
| 136 | ||
| 137 | Protocol.JSON_PROTOCOL.write_response( | |
| 138 | _writer, | |
| 139 | Protocol.Response.ERROR("unknown_command", "no handler registered for request kind {command_name}", command_name) | |
| 140 | ); | |
| 141 | ||
| 142 | _watchdog.on_operation_complete(_writer); | |
| 143 | ||
| 144 | return true; | |
| 145 | fi | |
| 146 | si | |
| 147 | si | |
| 148 | si |