Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Reflection.Metadata.ILOpCode | |
| 3 | use IO.Std | |
| 4 | ||
| 5 | use System.Text.StringBuilder | |
| 6 | ||
| 7 | use Logging | |
| 8 | use Trees | |
| 9 | use Source | |
| 10 | ||
| 11 | use IR | |
| 12 | use IR.Values | |
| 13 | ||
| 14 | use Ghul.Pipes | |
| 15 | ||
| 16 | // Holds a generator function whose state-machine class hasn't | |
| 17 | // been emitted yet — its MoveNext block was filled in while | |
| 18 | // walking the function's body, but the wrapping `.class` lives | |
| 19 | // outside the enclosing user class in IL. Flushed by | |
| 20 | // `emit_pending_state_machines` at the tail of the enclosing | |
| 21 | // class / namespace visit. | |
| 22 | class PENDING_STATE_MACHINE is | |
| 23 | state_machine: Semantic.Symbols.STATE_MACHINE | |
| 24 | move_next_block: IR.Values.BLOCK | |
| 25 | ||
| 26 | init(state_machine: Semantic.Symbols.STATE_MACHINE, move_next_block: IR.Values.BLOCK) is | |
| 27 | self.state_machine = state_machine | |
| 28 | self.move_next_block = move_next_block | |
| 29 | si | |
| 30 | si | |
| 31 | ||
| 32 | // Async sibling of PENDING_STATE_MACHINE. Flushed by | |
| 33 | // `emit_pending_state_machines` alongside the generator list. | |
| 34 | class PENDING_ASYNC_STATE_MACHINE is | |
| 35 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE | |
| 36 | move_next_block: IR.Values.BLOCK | |
| 37 | success_label: IR.LABEL | |
| 38 | end_label: IR.LABEL | |
| 39 | ||
| 40 | init( | |
| 41 | async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE, | |
| 42 | move_next_block: IR.Values.BLOCK, | |
| 43 | success_label: IR.LABEL, | |
| 44 | end_label: IR.LABEL | |
| 45 | ) is | |
| 46 | self.async_state_machine = async_state_machine | |
| 47 | self.move_next_block = move_next_block | |
| 48 | self.success_label = success_label | |
| 49 | self.end_label = end_label | |
| 50 | si | |
| 51 | si | |
| 52 | ||
| 53 | // One entry in a dispatch placeholder — the pair an | |
| 54 | // AWAIT_SUSPEND contributes when it registers with its | |
| 55 | // enclosing protected region. `state` is the suspension state | |
| 56 | // number; `cold_resume_label` is the IR.LABEL inside the same | |
| 57 | // region's `.try` body that GET_RESULT routes through. | |
| 58 | class ASYNC_DISPATCH_ENTRY is | |
| 59 | state: int | |
| 60 | cold_resume_label: IR.LABEL | |
| 61 | ||
| 62 | init(state: int, cold_resume_label: IR.LABEL) is | |
| 63 | self.state = state | |
| 64 | self.cold_resume_label = cold_resume_label | |
| 65 | si | |
| 66 | si | |
| 67 | ||
| 68 | // IL-emission frame for a `val ... lav` block currently in | |
| 69 | // scope. Returns inside the body whose val_block_target is this | |
| 70 | // block push their value and `br` to `end_label`; the natural | |
| 71 | // fall-through emits the tail value and falls into the same | |
| 72 | // label. All paths converge at end_label with exactly one value | |
| 73 | // on the evaluation stack (or empty for a void block). No value | |
| 74 | // ever lives in a CLR local or frame field at a suspension | |
| 75 | // point, so async-await and generator-yield compose without | |
| 76 | // bespoke handling. | |
| 77 | // | |
| 78 | // result_type is the resolved block value type from compile- | |
| 79 | // expressions; void means no value-push. is_void is cached to | |
| 80 | // skip the lookup at every return emission. enclosing_try_count | |
| 81 | // is the number of `.try` entries on the loop-label stack at | |
| 82 | // val-block entry — a return whose current try count exceeds | |
| 83 | // this opened a try INSIDE the val-block body and needs `leave` | |
| 84 | // instead of `br` to exit the protected region. value_block is | |
| 85 | // the captured IR BLOCK used in capture mode; pre(VAL_BLOCK) | |
| 86 | // eagerly allocates the cross-try TEMP on it before walking | |
| 87 | // the body, so the `.locals init` lands at the head of value_ | |
| 88 | // block's IL stream rather than inside an inner `.try {}`. | |
| 89 | class VAL_BLOCK_IL is | |
| 90 | block: Trees.Expressions.VAL_BLOCK | |
| 91 | end_label: IR.LABEL | |
| 92 | result_type: Semantic.Types.Type? | |
| 93 | is_void: bool | |
| 94 | enclosing_try_count: int | |
| 95 | value_block: IR.Values.BLOCK? | |
| 96 | ||
| 97 | // Non-null when generate-il is spilling this val-block to a | |
| 98 | // frame field (body contains a state-machine suspend). Val- | |
| 99 | // targeted returns then `stfld $spill_field` instead of | |
| 100 | // leaving the value on the stack, so all paths converge at | |
| 101 | // end_label with the value in the field and an empty stack. | |
| 102 | spill_field: Semantic.Symbols.Field? public | |
| 103 | ||
| 104 | // Capture-mode cross-try state. The TEMP and label must be | |
| 105 | // allocated BEFORE the body walk, so the declaration is | |
| 106 | // encoded ahead of the load at the post-body join site | |
| 107 | // rather than somewhere inside the body. VAL_BLOCK.pre | |
| 108 | // pre-allocates both eagerly when value_block has a | |
| 109 | // non-void result type and is not spilling. | |
| 110 | // | |
| 111 | // A val-targeted `return E` inside an inner `.try` can't | |
| 112 | // `br end_label` (invalid IL across a protected region) | |
| 113 | // and can't carry its value across `leave` (empty-stack | |
| 114 | // precondition). Mirrors the function-return-from-try | |
| 115 | // pattern: stash the value into `cross_try_temp`, `leave | |
| 116 | // cross_try_join_label`, and at the join `ldloc` the temp | |
| 117 | // back onto the stack so it falls through into end_label. | |
| 118 | // `cross_try_used` flags whether the join block needs to | |
| 119 | // be emitted at all — otherwise the unconditional `br | |
| 120 | // end_label; join: ldloc; end_label` would push | |
| 121 | // uninitialised data onto the stack on the natural path's | |
| 122 | // divergent variants. | |
| 123 | cross_try_temp: IR.TEMP? public | |
| 124 | cross_try_join_label: IR.LABEL? public | |
| 125 | cross_try_used: bool public | |
| 126 | ||
| 127 | init( | |
| 128 | block: Trees.Expressions.VAL_BLOCK, | |
| 129 | end_label: IR.LABEL, | |
| 130 | result_type: Semantic.Types.Type?, | |
| 131 | is_void: bool, | |
| 132 | enclosing_try_count: int, | |
| 133 | value_block: IR.Values.BLOCK? | |
| 134 | ) is | |
| 135 | self.block = block | |
| 136 | self.end_label = end_label | |
| 137 | self.result_type = result_type | |
| 138 | self.is_void = is_void | |
| 139 | self.enclosing_try_count = enclosing_try_count | |
| 140 | self.value_block = value_block | |
| 141 | si | |
| 142 | si | |
| 143 | ||
| 144 | // Helper for the four composites whose AST value sits in an | |
| 145 | // `IR.Values.BLOCK` and whose body may suspend (`val ... lav`, | |
| 146 | // a `Statements.LIST` in expression position, `if` / `case` in | |
| 147 | // expression position). The natural emission captures body IL | |
| 148 | // inside the value BLOCK; a consumer later replays it. When the | |
| 149 | // captured IL contains a state-machine suspend, the replay traps | |
| 150 | // the suspend's `leave` / state register inside the consumer's | |
| 151 | // stack setup — invalid IL plus state-loss across MoveNext | |
| 152 | // re-entry. | |
| 153 | // | |
| 154 | // Spill mode breaks the capture: the body emits inline in outer | |
| 155 | // current_block alongside the dispatcher, each value-producing | |
| 156 | // path lands in an anonymous frame field via `ldarg.0; v; stfld`, | |
| 157 | // and the value BLOCK is rewritten to hold just a `ldfld` of the | |
| 158 | // field. Consumers see a clean load. | |
| 159 | // | |
| 160 | // One spiller per composite. Which mode it is in follows from | |
| 161 | // whether compile-expressions recorded a frame field for the | |
| 162 | // node, which is where the spill-vs-capture decision is made. | |
| 163 | // `enter` | |
| 164 | // opens the BLOCK for direct emission when capturing; `emit | |
| 165 | // _value` emits each tail/branch value (spill or forward); | |
| 166 | // `leave` either rewrites the BLOCK's contents to a field load | |
| 167 | // (spill) or closes it with `leave_block` (capture). VAL_BLOCK | |
| 168 | // also reads `spill_field` to make val-targeted returns store | |
| 169 | // into the same field. | |
| 170 | class COMPOSITE_VALUE_SPILLER is | |
| 171 | _gen: GENERATE_IL | |
| 172 | _value_block: IR.Values.BLOCK? | |
| 173 | _frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE? | |
| 174 | _spill_field: Semantic.Symbols.Field? | |
| 175 | ||
| 176 | init( | |
| 177 | gen: GENERATE_IL, | |
| 178 | node: Trees.Node, | |
| 179 | value: IR.Values.Value?, | |
| 180 | frame: Semantic.Symbols.STATE_MACHINE_FRAME_BASE? | |
| 181 | ) is | |
| 182 | _gen = gen | |
| 183 | ||
| 184 | if !value? \/ !isa IR.Values.BLOCK(value) then | |
| 185 | return | |
| 186 | fi | |
| 187 | ||
| 188 | _value_block = cast IR.Values.BLOCK(value) | |
| 189 | ||
| 190 | if !frame? then | |
| 191 | return | |
| 192 | fi | |
| 193 | ||
| 194 | // Compile-expressions makes the spill-vs-capture decision | |
| 195 | // and allocates the field, because a frame field first | |
| 196 | // declared here would be numbered by nothing. An entry | |
| 197 | // means spill; none means capture. | |
| 198 | let spill_field = gen.composite_spill_state.get(node) | |
| 199 | ||
| 200 | if !spill_field? then | |
| 201 | return | |
| 202 | fi | |
| 203 | ||
| 204 | _frame = frame | |
| 205 | _spill_field = spill_field | |
| 206 | si | |
| 207 | ||
| 208 | is_spilling: bool => _spill_field? | |
| 209 | ||
| 210 | spill_field: Semantic.Symbols.Field? => _spill_field | |
| 211 | ||
| 212 | enter() is | |
| 213 | if !_value_block? \/ _spill_field? then | |
| 214 | return | |
| 215 | fi | |
| 216 | _gen.enter_block(_value_block) | |
| 217 | si | |
| 218 | ||
| 219 | emit_value(value: IR.Values.Value) is | |
| 220 | let spill = _spill_field | |
| 221 | ||
| 222 | if spill? then | |
| 223 | _gen.add(_gen._build_frame_field_store(_frame!, spill, value)) | |
| 224 | else | |
| 225 | _gen.add(value) | |
| 226 | fi | |
| 227 | si | |
| 228 | ||
| 229 | leave() is | |
| 230 | if _spill_field? then | |
| 231 | _value_block!.add(_gen._build_frame_field_load(_frame!, _spill_field)) | |
| 232 | elif _value_block? then | |
| 233 | _gen.leave_block() | |
| 234 | fi | |
| 235 | si | |
| 236 | si | |
| 237 | ||
| 238 | // Placeholder block sitting at the top of one protected region's | |
| 239 | // body (MoveNext's outer try, or any inner let-use / user `.try`). | |
| 240 | // While the region's body walks, AWAIT_SUSPENDs / YIELDs that | |
| 241 | // fire inside it append their state-label pair via `register`. | |
| 242 | // Region close populates `block` with `ldloc V_state; ldc.i4 N; | |
| 243 | // beq cold_N` for each entry — keeping the dispatch branches | |
| 244 | // inside the same `.try` as the targets they reach — and hands | |
| 245 | // those states to the enclosing holder, retargeted to | |
| 246 | // `top_label`: from outside a protected region, control may only | |
| 247 | // enter at its first instruction, so the enclosing dispatch | |
| 248 | // routes a resuming MoveNext straight past any statements between | |
| 249 | // the regions (hoisted-variable initializers, loop iterators) | |
| 250 | // that must not re-run on re-entry. | |
| 251 | // | |
| 252 | // `state_local_il` is the IL spelling of the CLR local cached at | |
| 253 | // MoveNext entry — `'.async_state'` for async state machines, | |
| 254 | // `'.gen_state'` for generators. Both nest under the same | |
| 255 | // dispatch-holder machinery; only the local name differs. | |
| 256 | class ASYNC_DISPATCH_HOLDER is | |
| 257 | block: IR.Values.BLOCK | |
| 258 | state_local_il: string | |
| 259 | top_label: IR.LABEL | |
| 260 | is_state_machine_entry: bool | |
| 261 | _entries: Collections.LIST[ASYNC_DISPATCH_ENTRY] | |
| 262 | ||
| 263 | init( | |
| 264 | block: IR.Values.BLOCK, | |
| 265 | state_local_il: string, | |
| 266 | top_label: IR.LABEL, | |
| 267 | is_state_machine_entry: bool | |
| 268 | ) is | |
| 269 | self.block = block | |
| 270 | self.state_local_il = state_local_il | |
| 271 | self.top_label = top_label | |
| 272 | self.is_state_machine_entry = is_state_machine_entry | |
| 273 | _entries = Collections.LIST[ASYNC_DISPATCH_ENTRY]() | |
| 274 | si | |
| 275 | ||
| 276 | register(state: int, cold_resume_label: IR.LABEL) is | |
| 277 | _entries.add(ASYNC_DISPATCH_ENTRY(state, cold_resume_label)) | |
| 278 | si | |
| 279 | ||
| 280 | entries: Collections.Iterable[ASYNC_DISPATCH_ENTRY] => _entries | |
| 281 | si | |
| 282 | ||
| 283 | class GENERATE_IL: ScopedVisitor is | |
| 284 | _logger: Logger | |
| 285 | _symbol_table: Semantic.SYMBOL_TABLE | |
| 286 | _symbol_loader: Semantic.SYMBOL_LOADER | |
| 287 | _innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup | |
| 288 | _function_caller: Semantic.FUNCTION_CALLER | |
| 289 | _type_caster: Semantic.TYPE_CASTER | |
| 290 | _overload_resolver: Semantic.OVERLOAD_RESOLVER | |
| 291 | _symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS | |
| 292 | _build_flags: Compiler.GLOBAL_BUILD_FLAGS | |
| 293 | ||
| 294 | // Read by COMPOSITE_VALUE_SPILLER: an entry is compile- | |
| 295 | // expressions telling this composite to spill. | |
| 296 | composite_spill_state: COMPOSITE_SPILL_STORE | |
| 297 | ||
| 298 | // Read for a literal leaf's value-equality test; see | |
| 299 | // VARIABLE_LEFT_STATE. | |
| 300 | _variable_left_state: VARIABLE_LEFT_STATE_STORE | |
| 301 | ||
| 302 | _context: CONTEXT | |
| 303 | _brancher: BRANCHER | |
| 304 | _boxer: VALUE_BOXER | |
| 305 | _loops: LOOP_LABEL_STACK | |
| 306 | _block_context: BlockContext | |
| 307 | ||
| 308 | // Asked which return types complete without a state machine; | |
| 309 | // depends only on the innate lookup, so it is built here rather | |
| 310 | // than threaded through the constructor. | |
| 311 | _task_conversion: Semantic.TASK_CONVERSION | |
| 312 | ||
| 313 | // The `.try` block each catch clause currently being walked is | |
| 314 | // attached to. A catch is visited on its own, after the block | |
| 315 | // it guards has closed, so it has no other route to the extent | |
| 316 | // its region needs. | |
| 317 | _try_extents: Collections.STACK[Values.TRY_EXTENT] | |
| 318 | ||
| 319 | _il_output_depth: int | |
| 320 | ||
| 321 | // Set by an `@entry` pragma, and cleared by the next function the | |
| 322 | // walk enters - the one the pragma annotates. | |
| 323 | ||
| 324 | _in_catch_variable: bool | |
| 325 | ||
| 326 | _indent: int | |
| 327 | _depth: int | |
| 328 | _run_on: bool | |
| 329 | _indent_needed: bool | |
| 330 | ||
| 331 | _interpolation_handler: Semantic.Types.Type? | |
| 332 | _constructor: Semantic.Symbols.Function | |
| 333 | _append_literal: Semantic.Symbols.Function | |
| 334 | _append_formatted_generic: Semantic.Symbols.Function | |
| 335 | _interpolated_value_classifier: Semantic.INTERPOLATED_VALUE_CLASSIFIER | |
| 336 | _display_function: Semantic.Symbols.Function? | |
| 337 | _append_formatted_generic_alignment: Semantic.Symbols.Function | |
| 338 | _append_formatted_generic_format: Semantic.Symbols.Function | |
| 339 | _append_formatted_generic_alignment_format: Semantic.Symbols.Function | |
| 340 | _to_string_and_clear: Semantic.Symbols.Function | |
| 341 | _dispose: Semantic.Symbols.Function | |
| 342 | ||
| 343 | // Generators whose state-machine class IL emission has been | |
| 344 | // deferred until the enclosing class/namespace closes. The | |
| 345 | // outer function method is emitted inline; the state-machine | |
| 346 | // class needs to land as a SIBLING of the user's class, not | |
| 347 | // inside it (CIL has no nested-class-in-method form). Mirrors | |
| 348 | // the existing closure-FRAME emission pattern (see | |
| 349 | // gen_closures called from visit(`class)). | |
| 350 | _pending_state_machines: Collections.LIST[PENDING_STATE_MACHINE] | |
| 351 | _pending_async_state_machines: Collections.LIST[PENDING_ASYNC_STATE_MACHINE] | |
| 352 | ||
| 353 | // Per-async-function emission context, set/cleared by | |
| 354 | // `_pre_async_function`. Visit handlers for AWAIT / RETURN | |
| 355 | // check `current_function == _current_async_state_machine | |
| 356 | // .function` so a nested closure's own visit doesn't fire | |
| 357 | // the outer function's async paths. | |
| 358 | _current_async_state_machine: Semantic.Symbols.ASYNC_STATE_MACHINE? | |
| 359 | _current_async_success_label: IR.LABEL? | |
| 360 | _current_async_end_label: IR.LABEL? | |
| 361 | ||
| 362 | // Leave target for yields inside a `.try` body. CIL forbids | |
| 363 | // `ret` from inside a protected region; yields that suspend | |
| 364 | // from within an enclosing `.try` `leave` to this label, which | |
| 365 | // sits outside any try and just emits `ldc.i4.1; ret`. | |
| 366 | // Lazy: yield's emission sets this on first use; the generator | |
| 367 | // trailer emits the label + ret only if it was actually used. | |
| 368 | _current_generator_yield_return_label: IR.LABEL? | |
| 369 | ||
| 370 | // Stack of dispatch placeholders for the currently-open | |
| 371 | // protected regions (outermost is the MoveNext body itself). | |
| 372 | // Pushed when entering a `.try` body, popped on close. Each | |
| 373 | // AWAIT_SUSPEND that fires while a holder is active registers | |
| 374 | // its (state, cold_resume_label) pair with the top holder. | |
| 375 | // At close-of-region the holder's BLOCK is populated with | |
| 376 | // `ldloc V_state; ldc.i4 N; beq cold_resume_N` for each | |
| 377 | // registered await — so the `beq` and the target label live | |
| 378 | // in the same `.try`, never crossing a region boundary — and | |
| 379 | // the states are propagated to the enclosing holder against | |
| 380 | // this region's entry, so the MoveNext-entry dispatch reaches | |
| 381 | // resume points nested in `.try`s without re-running the code | |
| 382 | // between the regions. | |
| 383 | _async_dispatch_stack: Collections.LIST[ASYNC_DISPATCH_HOLDER] | |
| 384 | ||
| 385 | // Stack of `val ... lav` IL frames, parallel to the | |
| 386 | // val_block_target stamped on RETURN nodes by compile- | |
| 387 | // expressions. Each entry holds the block's end_label, the | |
| 388 | // resolved result_type, the open-`.try` count cached at | |
| 389 | // val-block entry, and (in capture mode) the eagerly- | |
| 390 | // allocated cross-try TEMP and join label. visit(RETURN) | |
| 391 | // reads the top via the node's val_block_target identity | |
| 392 | // and dispatches to one of three shapes: spill mode stfld's | |
| 393 | // into the frame field then leave/br end_label; capture | |
| 394 | // mode with no inner try pushes the value and `br`s | |
| 395 | // end_label; capture mode crossing an inner try stashes the | |
| 396 | // value into the TEMP and `leave`s the join label, where | |
| 397 | // a post-body `ldloc` joins it back onto end_label's stack. | |
| 398 | _val_block_il_stack: Collections.LIST[VAL_BLOCK_IL] | |
| 399 | ||
| 400 | current_block: Values.BLOCK => _block_context.current_block | |
| 401 | ||
| 402 | // The interpolation-handler and disposal symbols are | |
| 403 | // materialized by ensure_runtime_symbols_are_materialized, | |
| 404 | // since the runtime types are not loaded yet at construction. | |
| 405 | @suppress("field-definite-assignment") | |
| 406 | init( | |
| 407 | logger: Logger, | |
| 408 | symbol_table: Semantic.SYMBOL_TABLE, | |
| 409 | namespaces: Semantic.NAMESPACES, | |
| 410 | symbol_loader: Semantic.SYMBOL_LOADER, | |
| 411 | innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup, | |
| 412 | function_caller: Semantic.FUNCTION_CALLER, | |
| 413 | type_caster: Semantic.TYPE_CASTER, | |
| 414 | overload_resolver: Semantic.OVERLOAD_RESOLVER, | |
| 415 | symbol_use_locations: Semantic.SYMBOL_USE_LOCATIONS, | |
| 416 | context: CONTEXT, | |
| 417 | block_context: BlockContext, | |
| 418 | brancher: BRANCHER, | |
| 419 | boxer: VALUE_BOXER, | |
| 420 | build_flags: Compiler.GLOBAL_BUILD_FLAGS, | |
| 421 | composite_spill_state: COMPOSITE_SPILL_STORE, | |
| 422 | variable_left_state: VARIABLE_LEFT_STATE_STORE | |
| 423 | ) | |
| 424 | is | |
| 425 | super.init(logger, symbol_table, namespaces) | |
| 426 | ||
| 427 | _logger = logger | |
| 428 | _symbol_table = symbol_table | |
| 429 | _symbol_loader = symbol_loader | |
| 430 | _innate_symbol_lookup = innate_symbol_lookup | |
| 431 | _task_conversion = Semantic.TASK_CONVERSION(innate_symbol_lookup) | |
| 432 | _function_caller = function_caller | |
| 433 | _type_caster = type_caster | |
| 434 | _overload_resolver = overload_resolver | |
| 435 | _symbol_use_locations = symbol_use_locations | |
| 436 | _context = context | |
| 437 | _block_context = block_context | |
| 438 | _brancher = brancher | |
| 439 | _boxer = boxer | |
| 440 | _build_flags = build_flags | |
| 441 | self.composite_spill_state = composite_spill_state | |
| 442 | _variable_left_state = variable_left_state | |
| 443 | ||
| 444 | _loops = LOOP_LABEL_STACK() | |
| 445 | _try_extents = Collections.STACK[Values.TRY_EXTENT]() | |
| 446 | _pending_state_machines = Collections.LIST[PENDING_STATE_MACHINE]() | |
| 447 | _pending_async_state_machines = Collections.LIST[PENDING_ASYNC_STATE_MACHINE]() | |
| 448 | _async_dispatch_stack = Collections.LIST[ASYNC_DISPATCH_HOLDER]() | |
| 449 | _val_block_il_stack = Collections.LIST[VAL_BLOCK_IL]() | |
| 450 | si | |
| 451 | ||
| 452 | ensure_runtime_symbols_are_materialized() is | |
| 453 | if _interpolation_handler? then | |
| 454 | return | |
| 455 | fi | |
| 456 | ||
| 457 | let string_type = _innate_symbol_lookup.get_string_type() | |
| 458 | let int_type = _innate_symbol_lookup.get_int_type() | |
| 459 | ||
| 460 | ||
| 461 | // Materialize IDisposable.Dispose() | |
| 462 | let idisposable_type = _innate_symbol_lookup.get_idisposable_type() | |
| 463 | ||
| 464 | // there is only one overload of dispose: | |
| 465 | _dispose = cast Semantic.Symbols.FUNCTION_GROUP?(idisposable_type.find_member("dispose"))!.functions[0] | |
| 466 | ||
| 467 | _interpolated_value_classifier = Semantic.INTERPOLATED_VALUE_CLASSIFIER(_innate_symbol_lookup) | |
| 468 | _display_function = _interpolated_value_classifier.display_function | |
| 469 | ||
| 470 | // Materialize the various methods of the string interpolation handler: | |
| 471 | let interpolation_handler = _innate_symbol_lookup.get_interpolated_string_handler_type() | |
| 472 | _interpolation_handler = interpolation_handler | |
| 473 | ||
| 474 | // the constructor overload we want is the only one with 2 arguments: | |
| 475 | _constructor = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("init"))!.functions |> | |
| 476 | filter(f => f.arguments.count == 2) |> | |
| 477 | only() | |
| 478 | ||
| 479 | // there is only one overload of append_literal: | |
| 480 | _append_literal = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_literal"))!.functions[0] | |
| 481 | ||
| 482 | // the first overload of append_formatted we want is the only one that is both generic and with 1 argument: | |
| 483 | _append_formatted_generic = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 484 | filter(f => f.is_generic /\ f.arguments.count == 1) |> | |
| 485 | only() | |
| 486 | ||
| 487 | // there are two overloads of append_formatted with 2 | |
| 488 | // arguments, we want the one where the second argument is | |
| 489 | // an int: | |
| 490 | _append_formatted_generic_alignment = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 491 | filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(int_type) == Semantic.Types.MATCH.SAME) |> | |
| 492 | only() | |
| 493 | ||
| 494 | // there are two overloads of append_formatted with 2 | |
| 495 | // arguments, we want the one where the second argument is | |
| 496 | // a string: | |
| 497 | // (BCL declares the `format` parameter as `string?`, so SAME no longer matches under directional compare; | |
| 498 | // != DIFFERENT accepts both `string` and `string?` shapes.) | |
| 499 | _append_formatted_generic_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 500 | filter(f => f.is_generic /\ f.arguments.count == 2 /\ f.arguments[1].type!.compare(string_type) != Semantic.Types.MATCH.DIFFERENT) |> | |
| 501 | only() | |
| 502 | ||
| 503 | // there is only one overload of append_formatted with 3 arguments: | |
| 504 | _append_formatted_generic_alignment_format = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("append_formatted"))!.functions |> | |
| 505 | filter(f => f.is_generic /\ f.arguments.count == 3) |> | |
| 506 | only() | |
| 507 | ||
| 508 | // there is only one overload of to_string_and_clear: | |
| 509 | _to_string_and_clear = cast Semantic.Symbols.FUNCTION_GROUP?(interpolation_handler.find_member("to_string_and_clear"))!.functions[0] | |
| 510 | si | |
| 511 | ||
| 512 | apply(root: Trees.Node) is | |
| 513 | if _logger.any_errors then | |
| 514 | return | |
| 515 | fi | |
| 516 | ||
| 517 | LABEL.set_pass("_") | |
| 518 | ||
| 519 | root.walk(self) | |
| 520 | si | |
| 521 | ||
| 522 | // Mirrors compile_expressions.ghul's enter_node/leave_node. | |
| 523 | // pre(Statements.LIST) takes over the statement walk, so the | |
| 524 | // location bracketing has to be re-applied by hand around | |
| 525 | // each statement. | |
| 526 | enter_node(node: Trees.Node) is | |
| 527 | if !_build_flags.want_debug then | |
| 528 | return | |
| 529 | fi | |
| 530 | IoC.CONTAINER.instance.location_stack.push(node.debug_location) | |
| 531 | si | |
| 532 | ||
| 533 | leave_node(node: Trees.Node) is | |
| 534 | if !_build_flags.want_debug then | |
| 535 | return | |
| 536 | fi | |
| 537 | IoC.CONTAINER.instance.location_stack.pop() | |
| 538 | si | |
| 539 | ||
| 540 | enter_block(block: Values.BLOCK) is | |
| 541 | _block_context.enter_block(block) | |
| 542 | si | |
| 543 | ||
| 544 | enter_block() is | |
| 545 | _block_context.enter_block() | |
| 546 | si | |
| 547 | ||
| 548 | leave_block() is | |
| 549 | _block_context.leave_block() | |
| 550 | si | |
| 551 | ||
| 552 | get_brancher_for_block() -> BLOCK_BRANCHER => | |
| 553 | _brancher.get_for(_block_context.current_block) | |
| 554 | ||
| 555 | add(value: Values.Value) is | |
| 556 | current_block.add(value) | |
| 557 | si | |
| 558 | si | |
| 559 | ||
| 560 | ||
| 561 | // A loop has no `middle`: only the try shape branches through one, | |
| 562 | // and only the try constructor makes it. | |
| 563 | @suppress("field-definite-assignment") | |
| 564 | class LOOP_LABELS is | |
| 565 | is_try: bool | |
| 566 | is_in_finally: bool public | |
| 567 | is_loop: bool => !is_try | |
| 568 | ||
| 569 | name: string? | |
| 570 | start: LABEL | |
| 571 | middle: LABEL | |
| 572 | end: LABEL | |
| 573 | ||
| 574 | return_needed: TEMP? | |
| 575 | return_value: TEMP? | |
| 576 | ||
| 577 | // Loop-as-expression state. `node` correlates the entry with | |
| 578 | // the AST loop, so a `break` whose target compile-expressions | |
| 579 | // resolved by symbol finds this entry without a name search. | |
| 580 | // The rest mirror the val-block's cross-try handling: a | |
| 581 | // value-carrying exit from inside a `.try` cannot `br` with a | |
| 582 | // value on the stack (`leave` requires it empty), so it stashes | |
| 583 | // into cross_try_temp and `leave`s to cross_try_join, which | |
| 584 | // reloads before result_label. | |
| 585 | node: Trees.Statements.Statement? public | |
| 586 | wants_value: bool public | |
| 587 | result_type: Semantic.Types.Type? public | |
| 588 | result_label: LABEL? public | |
| 589 | cross_try_temp: TEMP? public | |
| 590 | cross_try_join: LABEL? public | |
| 591 | cross_try_used: bool public | |
| 592 | spill_field: Semantic.Symbols.Field? public | |
| 593 | enclosing_try_count: int public | |
| 594 | ||
| 595 | init(name: string?) is | |
| 596 | is_try = false | |
| 597 | self.name = name | |
| 598 | start = LABEL() | |
| 599 | end = LABEL() | |
| 600 | si | |
| 601 | ||
| 602 | init(return_needed: TEMP, return_value: TEMP?) is | |
| 603 | is_try = true | |
| 604 | self.return_needed = return_needed | |
| 605 | self.return_value = return_value | |
| 606 | start = LABEL() | |
| 607 | middle = LABEL() | |
| 608 | end = LABEL() | |
| 609 | si | |
| 610 | ||
| 611 | matches(name: string) -> bool => self.name? /\ self.name! =~ name | |
| 612 | si | |
| 613 | ||
| 614 | class LOOP_LABEL_STACK is | |
| 615 | _next_name: string? | |
| 616 | ||
| 617 | loops: Collections.MutableList[LOOP_LABELS] | |
| 618 | is_in_loop: bool => get_current_loop() != null | |
| 619 | is_in_try: bool => get_current_try() != null | |
| 620 | ||
| 621 | get_current_try() -> LOOP_LABELS? is | |
| 622 | let index: int mut = loops.count - 1 | |
| 623 | ||
| 624 | while index >= 0 do | |
| 625 | let result = loops[index] | |
| 626 | ||
| 627 | if result.is_try then | |
| 628 | return result | |
| 629 | fi | |
| 630 | ||
| 631 | index = index - 1 | |
| 632 | od | |
| 633 | return null | |
| 634 | si | |
| 635 | ||
| 636 | // Number of `is_try` entries currently on the stack. Cached | |
| 637 | // at val-block entry; a return inside the body whose count | |
| 638 | // exceeds the cached value opened a try INSIDE the block, | |
| 639 | // so `br end_label` would cross the try's boundary. | |
| 640 | open_try_count: int is | |
| 641 | let count mut = 0 | |
| 642 | for l in loops do | |
| 643 | if l.is_try then | |
| 644 | count = count + 1 | |
| 645 | fi | |
| 646 | od | |
| 647 | return count | |
| 648 | si | |
| 649 | ||
| 650 | get_current_loop() -> LOOP_LABELS? is | |
| 651 | let index: int mut = loops.count - 1 | |
| 652 | ||
| 653 | while index >= 0 do | |
| 654 | let result = loops[index] | |
| 655 | ||
| 656 | if result.is_loop then | |
| 657 | return result | |
| 658 | fi | |
| 659 | ||
| 660 | index = index - 1 | |
| 661 | od | |
| 662 | return null | |
| 663 | si | |
| 664 | ||
| 665 | init() is | |
| 666 | loops = Collections.LIST[LOOP_LABELS]() | |
| 667 | si | |
| 668 | ||
| 669 | next_name(name: string) is | |
| 670 | _next_name = name | |
| 671 | si | |
| 672 | ||
| 673 | find(name: string) -> LOOP_LABELS? is | |
| 674 | let index: int mut = loops.count - 1 | |
| 675 | ||
| 676 | while index >= 0 do | |
| 677 | let result = loops[index] | |
| 678 | ||
| 679 | if result.matches(name) then | |
| 680 | return result | |
| 681 | fi | |
| 682 | ||
| 683 | index = index - 1 | |
| 684 | od | |
| 685 | return null | |
| 686 | si | |
| 687 | ||
| 688 | enter_try(return_needed: TEMP, return_value: TEMP?) -> LOOP_LABELS is | |
| 689 | let result = LOOP_LABELS(return_needed, return_value) | |
| 690 | ||
| 691 | loops.add(result) | |
| 692 | ||
| 693 | return result | |
| 694 | si | |
| 695 | ||
| 696 | enter_loop(node: Trees.Statements.Statement?) -> LOOP_LABELS is | |
| 697 | let result = LOOP_LABELS(_next_name) | |
| 698 | ||
| 699 | result.node = node | |
| 700 | result.enclosing_try_count = open_try_count | |
| 701 | ||
| 702 | _next_name = null | |
| 703 | ||
| 704 | loops.add(result) | |
| 705 | ||
| 706 | return result | |
| 707 | si | |
| 708 | ||
| 709 | find_by_node(node: Trees.Statements.Statement) -> LOOP_LABELS? is | |
| 710 | let index: int mut = loops.count - 1 | |
| 711 | ||
| 712 | while index >= 0 do | |
| 713 | let candidate = loops[index] | |
| 714 | ||
| 715 | if candidate.is_loop /\ candidate.node == node then | |
| 716 | return candidate | |
| 717 | fi | |
| 718 | ||
| 719 | index = index - 1 | |
| 720 | od | |
| 721 | return null | |
| 722 | si | |
| 723 | ||
| 724 | // The innermost enclosing loop that consumes a value — where a | |
| 725 | // valued `break` delivers its result. Intermediate non-expression | |
| 726 | // loops are exited through. | |
| 727 | find_value_loop() -> LOOP_LABELS? is | |
| 728 | let index: int mut = loops.count - 1 | |
| 729 | ||
| 730 | while index >= 0 do | |
| 731 | let candidate = loops[index] | |
| 732 | ||
| 733 | if candidate.is_loop /\ candidate.wants_value then | |
| 734 | return candidate | |
| 735 | fi | |
| 736 | ||
| 737 | index = index - 1 | |
| 738 | od | |
| 739 | return null | |
| 740 | si | |
| 741 | ||
| 742 | leave_loop() is | |
| 743 | loops.remove_at(loops.count - 1) | |
| 744 | si | |
| 745 | si | |
| 746 | si |