Appearance
| 1 | namespace Syntax.Process.Printer is | |
| 2 | use Collections | |
| 3 | ||
| 4 | use Trees | |
| 5 | use Source | |
| 6 | ||
| 7 | use Lexical.TRIVIA | |
| 8 | ||
| 9 | ||
| 10 | // Definition formatting: function literals and declarations, classes, structs, partial and | |
| 11 | // impl blocks, unions and variants. | |
| 12 | partial FORMATTER is | |
| 13 | ||
| 14 | // --- faithfulness fix: a lambda's argument list must be parenthesised | |
| 15 | // unless it is a single argument with an inferred return type — | |
| 16 | // the only paren-free lambda form. A bare empty list (`=> body`) | |
| 17 | // or a bare multi-argument list does not reparse as a lambda, and | |
| 18 | // an explicit return type needs the parens or `arg: T -> U` | |
| 19 | // reparses as an argument of function type `T -> U`. --- | |
| 20 | ||
| 21 | visit(function: Expressions.FUNCTION) is | |
| 22 | note(function.location) | |
| 23 | ||
| 24 | let return_annotation = | |
| 25 | if isa TypeExpressions.INFER(function.type_expression) then | |
| 26 | _annotation(a => a.return_type(function.location)) | |
| 27 | else | |
| 28 | null | |
| 29 | fi | |
| 30 | ||
| 31 | let explicit_return = | |
| 32 | (!isa TypeExpressions.INFER(function.type_expression) /\ !_strip("return", function.location, "type", function.type_expression.location, function.type_expression.location)) \/ return_annotation? | |
| 33 | ||
| 34 | if let function.nested_name? then | |
| 35 | nested_name.accept(self) | |
| 36 | fi | |
| 37 | ||
| 38 | // The paren-free form is available only for a sole argument that is | |
| 39 | // a bare name. A destructure group renders as its own parenthesised | |
| 40 | // pattern, so dropping the argument list's parens would leave that | |
| 41 | // pattern standing as the list - `((a, b)) => ...`, one destructured | |
| 42 | // argument, reparsing as `(a, b) => ...`, which is two. An argument | |
| 43 | // with a written type needs them too: `word: string => ...` does not | |
| 44 | // parse as a lambda at all. | |
| 45 | let sole_argument_needs_parens = | |
| 46 | function.arguments.count == 1 /\ | |
| 47 | (function.arguments | |
| 48 | |> Ghul.Pipes.any( | |
| 49 | a => | |
| 50 | if let only = cast Expressions.VARIABLE?(a) then | |
| 51 | (only.is_synthesized /\ only.left?) \/ | |
| 52 | !isa TypeExpressions.INFER(only.type_expression) \/ | |
| 53 | _annotation(a => a.parameter_type(only.location))? | |
| 54 | else | |
| 55 | _annotation(x => x.parameter_type(a.location))? | |
| 56 | fi | |
| 57 | )) | |
| 58 | ||
| 59 | // A named nested function always writes its argument list in | |
| 60 | // parens: the paren-free lambda form has nowhere to put the name. | |
| 61 | let parenthesise = | |
| 62 | function.nested_name? \/ | |
| 63 | explicit_return \/ | |
| 64 | function.arguments.count != 1 \/ | |
| 65 | sole_argument_needs_parens | |
| 66 | ||
| 67 | if parenthesise then | |
| 68 | write("(") | |
| 69 | fi | |
| 70 | ||
| 71 | let first mut = true | |
| 72 | for a in function.arguments do | |
| 73 | if !first then | |
| 74 | write(", ") | |
| 75 | fi | |
| 76 | ||
| 77 | a.accept(self) | |
| 78 | ||
| 79 | // A parameter written as a bare name parses as an | |
| 80 | // identifier rather than a variable; its inferred type | |
| 81 | // is written after it the same way. | |
| 82 | if isa Expressions.IDENTIFIER(a) then | |
| 83 | if let annotation = _annotation(x => x.parameter_type(a.location)) then | |
| 84 | write(": {annotation}") | |
| 85 | fi | |
| 86 | fi | |
| 87 | ||
| 88 | first = false | |
| 89 | od | |
| 90 | ||
| 91 | if parenthesise then | |
| 92 | write(")") | |
| 93 | fi | |
| 94 | ||
| 95 | if let annotation = return_annotation then | |
| 96 | write(" -> {annotation}") | |
| 97 | elif explicit_return then | |
| 98 | write(" -> ") | |
| 99 | function.type_expression.accept(self) | |
| 100 | fi | |
| 101 | ||
| 102 | // The marker follows the return type: `(n: int) -> int rec`. | |
| 103 | // `rec` names the literal from inside its own body. Dropping it | |
| 104 | // leaves every self-reference in the body unresolved. A named | |
| 105 | // nested function is reached by its name instead, so its own | |
| 106 | // recursion marker is never written. | |
| 107 | if function.is_recursive /\ !function.nested_name? then | |
| 108 | write(" rec") | |
| 109 | fi | |
| 110 | ||
| 111 | write(" ") | |
| 112 | function.body.accept(self) | |
| 113 | si | |
| 114 | ||
| 115 | // --- faithfulness fix: the inherited visits render a pragma's | |
| 116 | // subject and drop the pragma itself, so `--format-in-place` | |
| 117 | // silently deletes every attribute and every conditional- | |
| 118 | // compilation guard in the file. A dropped `@IF.debug()` also | |
| 119 | // unguards the statement it was written on. --- | |
| 120 | ||
| 121 | visit(pragma: Pragmas.PRAGMA) is | |
| 122 | note(pragma.location) | |
| 123 | ||
| 124 | write("@") | |
| 125 | pragma.name.accept(self) | |
| 126 | write("(") | |
| 127 | ||
| 128 | let first mut = true | |
| 129 | ||
| 130 | for argument in pragma.arguments do | |
| 131 | if !first then | |
| 132 | write(", ") | |
| 133 | fi | |
| 134 | argument.accept(self) | |
| 135 | first = false | |
| 136 | od | |
| 137 | ||
| 138 | if let pragma.named_arguments? then | |
| 139 | for named_argument in named_arguments do | |
| 140 | if !first then | |
| 141 | write(", ") | |
| 142 | fi | |
| 143 | named_argument.name.accept(self) | |
| 144 | write(" = ") | |
| 145 | named_argument.value.accept(self) | |
| 146 | first = false | |
| 147 | od | |
| 148 | fi | |
| 149 | ||
| 150 | write(")") | |
| 151 | si | |
| 152 | ||
| 153 | // An attribute on a definition sits on its own line above it. | |
| 154 | visit(pragma: Definitions.PRAGMA) is | |
| 155 | note(pragma.location) | |
| 156 | pragma.pragma.accept(self) | |
| 157 | write_line() | |
| 158 | pragma.definition.accept(self) | |
| 159 | si | |
| 160 | ||
| 161 | // A pragma on a statement stays on the statement's own line. | |
| 162 | visit(pragma: Statements.PRAGMA) is | |
| 163 | note(pragma.location) | |
| 164 | pragma.pragma.accept(self) | |
| 165 | ||
| 166 | if let pragma.statement? then | |
| 167 | write(" ") | |
| 168 | statement.accept(self) | |
| 169 | fi | |
| 170 | si | |
| 171 | ||
| 172 | // --- faithfulness fix: an unresolved AMBIGUOUS_EXPRESSION (a parse- | |
| 173 | // time `x[y]` that could be a generic application or an index) | |
| 174 | // has the same surface syntax either way. The plain printer emits | |
| 175 | // a debug `(ambiguous ... or ...)` form, which is not valid ghūl. --- | |
| 176 | ||
| 177 | visit(ambiguous: Expressions.AMBIGUOUS_EXPRESSION) is | |
| 178 | note(ambiguous.location) | |
| 179 | ||
| 180 | if let ambiguous.left? then | |
| 181 | left.accept(self) | |
| 182 | write(".") | |
| 183 | fi | |
| 184 | ||
| 185 | ambiguous.identifier.accept(self) | |
| 186 | write("[") | |
| 187 | ambiguous.type_arguments.accept(self) | |
| 188 | write("]") | |
| 189 | si | |
| 190 | ||
| 191 | // --- faithfulness fix: STRUCT printed as `struct`, not `trait`; | |
| 192 | // CLASS / STRUCT carry an optional primary-constructor header | |
| 193 | // `(params)` between the type-argument list and the ancestor | |
| 194 | // list, and a body-less primary-ctor form `class X(p: T);` is | |
| 195 | // emitted as the `;` shorthand rather than `is si`. --- | |
| 196 | ||
| 197 | visit(`class: Definitions.CLASS) is | |
| 198 | emit_classy("class", `class) | |
| 199 | si | |
| 200 | ||
| 201 | visit(`struct: Definitions.STRUCT) is | |
| 202 | emit_classy("struct", `struct) | |
| 203 | si | |
| 204 | ||
| 205 | // The inherited visit writes a trait's modifiers straight after its | |
| 206 | // name, so `trait T open` comes back as `trait Topen`. emit_classy | |
| 207 | // separates them, and handles the ancestor clause the same way. | |
| 208 | visit(`trait: Definitions.TRAIT) is | |
| 209 | emit_classy("trait", `trait) | |
| 210 | si | |
| 211 | ||
| 212 | // `partial Target[args] is … si` — no ancestor clause, so emit_classy | |
| 213 | // (which only writes `: …` when ancestors are present) prints it faithfully. | |
| 214 | visit(`partial: Definitions.PARTIAL) is | |
| 215 | emit_classy("partial", `partial) | |
| 216 | si | |
| 217 | ||
| 218 | // `impl Interface for Target[args] is … si` — the interface leads and the | |
| 219 | // target follows `for`, the reverse of the `Target: Interface` shape | |
| 220 | // emit_classy assumes, so it needs its own emitter. | |
| 221 | visit(`impl: Definitions.IMPL) is | |
| 222 | note(`impl.location) | |
| 223 | ||
| 224 | write("impl ") | |
| 225 | ||
| 226 | if `impl.ancestors? then | |
| 227 | `impl.ancestors.accept(self) | |
| 228 | fi | |
| 229 | ||
| 230 | write(" for ") | |
| 231 | `impl.name.accept(self) | |
| 232 | ||
| 233 | if `impl.arguments? then | |
| 234 | write("[") | |
| 235 | `impl.arguments!.accept(self) | |
| 236 | write("]") | |
| 237 | fi | |
| 238 | ||
| 239 | if !`impl.modifiers.is_empty then | |
| 240 | write(" ") | |
| 241 | emit_modifiers(`impl.modifiers) | |
| 242 | fi | |
| 243 | ||
| 244 | write_line(" is") | |
| 245 | indent() | |
| 246 | `impl.body.accept(self) | |
| 247 | flush_leading(`impl.body.location.end) | |
| 248 | outdent() | |
| 249 | write_line("si") | |
| 250 | si | |
| 251 | ||
| 252 | emit_classy(keyword: string, classy: Definitions.Classy) is | |
| 253 | note(classy.location) | |
| 254 | ||
| 255 | write(keyword) | |
| 256 | write(" ") | |
| 257 | classy.name.accept(self) | |
| 258 | ||
| 259 | if classy.arguments? then | |
| 260 | write("[") | |
| 261 | classy.arguments!.accept(self) | |
| 262 | write("]") | |
| 263 | fi | |
| 264 | ||
| 265 | if classy.primary_params? then | |
| 266 | let primary_params = classy.primary_params | |
| 267 | write("(") | |
| 268 | emit_parameter_list(primary_params) | |
| 269 | write(")") | |
| 270 | fi | |
| 271 | ||
| 272 | if classy.ancestors? then | |
| 273 | write(": ") | |
| 274 | classy.ancestors!.accept(self) | |
| 275 | fi | |
| 276 | ||
| 277 | if !classy.modifiers.is_empty then | |
| 278 | write(" ") | |
| 279 | emit_modifiers(classy.modifiers) | |
| 280 | fi | |
| 281 | ||
| 282 | if classy.primary_params? /\ _body_is_empty(classy.body) then | |
| 283 | write_terminator() | |
| 284 | return | |
| 285 | fi | |
| 286 | ||
| 287 | write_line(" is") | |
| 288 | indent() | |
| 289 | classy.body.accept(self) | |
| 290 | ||
| 291 | // Up to the class's own end, not its body's: a blank line or a | |
| 292 | // comment written after the last definition sits between the | |
| 293 | // two, and flushing only to the body's end leaves it in the | |
| 294 | // queue to surface after the `si` - which reads as one more | |
| 295 | // blank line between this class and the next, and as another | |
| 296 | // on every reformat. | |
| 297 | flush_leading(classy.location.end) | |
| 298 | ||
| 299 | outdent() | |
| 300 | write_line("si") | |
| 301 | si | |
| 302 | ||
| 303 | _body_is_empty(body: Definitions.LIST) -> bool is | |
| 304 | for d in body do | |
| 305 | return false | |
| 306 | od | |
| 307 | return true | |
| 308 | si | |
| 309 | ||
| 310 | // --- faithfulness fix: SUPER_CALL is a body declaration only | |
| 311 | // reachable inside a primary-constructor class/struct body. | |
| 312 | // The base printer's StrictVisitor base throws on unhandled | |
| 313 | // node types — provide an explicit visit. --- | |
| 314 | ||
| 315 | visit(super_call: Definitions.SUPER_CALL) is | |
| 316 | note(super_call.location) | |
| 317 | write("super(") | |
| 318 | let first mut = true | |
| 319 | for a in super_call.args do | |
| 320 | if !first then | |
| 321 | write(", ") | |
| 322 | fi | |
| 323 | a.accept(self) | |
| 324 | first = false | |
| 325 | od | |
| 326 | write(")") | |
| 327 | write_terminator() | |
| 328 | si | |
| 329 | ||
| 330 | // --- faithfulness fix: the base printer's UNION visit ignores | |
| 331 | // the primary-constructor header (the parens after the union | |
| 332 | // name) and so drops it on round-trip. Emit it. --- | |
| 333 | ||
| 334 | visit(`union: Definitions.UNION) is | |
| 335 | note(`union.location) | |
| 336 | ||
| 337 | write("union ") | |
| 338 | `union.name.accept(self) | |
| 339 | ||
| 340 | if `union.arguments? then | |
| 341 | write("[") | |
| 342 | `union.arguments!.accept(self) | |
| 343 | write("]") | |
| 344 | fi | |
| 345 | ||
| 346 | if `union.primary_params? then | |
| 347 | let primary_params = `union.primary_params | |
| 348 | write("(") | |
| 349 | emit_parameter_list(primary_params) | |
| 350 | write(")") | |
| 351 | fi | |
| 352 | ||
| 353 | if `union.ancestors? /\ `union.ancestors.count > 0 then | |
| 354 | write(": ") | |
| 355 | `union.ancestors!.accept(self) | |
| 356 | fi | |
| 357 | ||
| 358 | if !`union.modifiers.is_empty then | |
| 359 | write(" ") | |
| 360 | emit_modifiers(`union.modifiers) | |
| 361 | fi | |
| 362 | ||
| 363 | write_line(" is") | |
| 364 | indent() | |
| 365 | ||
| 366 | let previous_has_primary = _union_has_primary_params | |
| 367 | _union_has_primary_params = `union.primary_params? | |
| 368 | ||
| 369 | try | |
| 370 | `union.body.accept(self) | |
| 371 | finally | |
| 372 | _union_has_primary_params = previous_has_primary | |
| 373 | yrt | |
| 374 | ||
| 375 | flush_leading(`union.body.location.end) | |
| 376 | outdent() | |
| 377 | write_line("si") | |
| 378 | si | |
| 379 | ||
| 380 | // --- faithfulness fix: a typed-union VARIANT is parsed with a | |
| 381 | // synthetic `public field` modifier list and an always-empty | |
| 382 | // body block, so the inherited visit prints | |
| 383 | // `RED public field is\nsi`. A variant has no user-written | |
| 384 | // body and the modifiers are not source-visible — emit the | |
| 385 | // `name(fields)? default? ;` form the user actually wrote. --- | |
| 386 | ||
| 387 | visit(variant: Definitions.VARIANT) is | |
| 388 | note(variant.location) | |
| 389 | ||
| 390 | variant.name.accept(self) | |
| 391 | ||
| 392 | if variant.fields.count > 0 /\ !_variant_fields_are_implicit_splice(variant) then | |
| 393 | write("(") | |
| 394 | variant.fields.accept(self) | |
| 395 | write(")") | |
| 396 | fi | |
| 397 | ||
| 398 | if variant.is_default then | |
| 399 | write(" default") | |
| 400 | fi | |
| 401 | ||
| 402 | write_terminator() | |
| 403 | si | |
| 404 | ||
| 405 | // True when the variant's only field is a single `..` splice and | |
| 406 | // we are inside a primary-header union — the splice is then | |
| 407 | // implied by the bare `NAME;` form and the parens add noise. | |
| 408 | _variant_fields_are_implicit_splice(variant: Definitions.VARIANT) -> bool is | |
| 409 | if !_union_has_primary_params then | |
| 410 | return false | |
| 411 | fi | |
| 412 | ||
| 413 | if variant.fields.count != 1 then | |
| 414 | return false | |
| 415 | fi | |
| 416 | ||
| 417 | for v in variant.fields do | |
| 418 | return v.is_splice | |
| 419 | od | |
| 420 | ||
| 421 | return false | |
| 422 | si | |
| 423 | si | |
| 424 | si |