Skip to main content

ptx_parser/unparser/
function.rs

1use crate::unparser::common::push_register;
2use crate::{
3    lexer::PtxToken,
4    r#type::{function::*, variable::ParameterDirective},
5    unparser::*,
6};
7
8fn push_register_components(tokens: &mut Vec<PtxToken>, name: &str) {
9    if let Some(stripped) = name.strip_prefix('%') {
10        let mut parts = stripped.split('.');
11        if let Some(first) = parts.next() {
12            let register_name = format!("%{first}");
13            push_register(tokens, &register_name);
14        }
15        for part in parts {
16            if part.is_empty() {
17                continue;
18            }
19            push_directive(tokens, part);
20        }
21    } else {
22        push_identifier(tokens, name);
23    }
24}
25
26fn unparse_param(tokens: &mut Vec<PtxToken>, param: &ParameterDirective, spaced: bool) {
27    match param {
28        ParameterDirective::Parameter {
29            align,
30            ty,
31            ptr,
32            space,
33            name,
34            array,
35            ..
36        } => {
37            push_directive(tokens, "param");
38            push_space(tokens, spaced);
39            ty.unparse_tokens_mode(tokens, spaced);
40            if *ptr {
41                push_directive(tokens, "ptr");
42            }
43            if let Some(address_space) = space {
44                address_space.unparse_tokens_mode(tokens, spaced);
45            }
46            if let Some(value) = align {
47                push_directive(tokens, "align");
48                push_space(tokens, spaced);
49                push_decimal(tokens, *value);
50            }
51            push_space(tokens, spaced);
52            push_identifier(tokens, &name.val);
53            for extent in array {
54                tokens.push(PtxToken::LBracket);
55                if let Some(value) = extent {
56                    push_decimal(tokens, *value);
57                }
58                tokens.push(PtxToken::RBracket);
59            }
60        }
61        ParameterDirective::Register { ty, name, .. } => {
62            push_directive(tokens, "reg");
63            push_space(tokens, spaced);
64            ty.unparse_tokens_mode(tokens, spaced);
65            push_space(tokens, spaced);
66            push_register_components(tokens, &name.val);
67        }
68    }
69}
70
71fn unparse_param_list(tokens: &mut Vec<PtxToken>, params: &[ParameterDirective], spaced: bool) {
72    for (idx, param) in params.iter().enumerate() {
73        if idx > 0 {
74            tokens.push(PtxToken::Comma);
75            push_space(tokens, spaced);
76        }
77        unparse_param(tokens, param, spaced);
78    }
79}
80
81fn unparse_section_line(
82    tokens: &mut Vec<PtxToken>,
83    line: &StatementSectionDirectiveLine,
84    spaced: bool,
85) {
86    match line {
87        StatementSectionDirectiveLine::B8 { values, .. } => {
88            push_directive(tokens, "b8");
89            for (idx, value) in values.iter().enumerate() {
90                if idx > 0 {
91                    tokens.push(PtxToken::Comma);
92                    push_space(tokens, spaced);
93                }
94                push_space(tokens, spaced);
95                push_signed_decimal_i64(tokens, *value as i64);
96            }
97            push_newline(tokens, spaced);
98        }
99        StatementSectionDirectiveLine::B16 { values, .. } => {
100            push_directive(tokens, "b16");
101            for (idx, value) in values.iter().enumerate() {
102                if idx > 0 {
103                    tokens.push(PtxToken::Comma);
104                    push_space(tokens, spaced);
105                }
106                push_space(tokens, spaced);
107                push_signed_decimal_i64(tokens, *value as i64);
108            }
109            push_newline(tokens, spaced);
110        }
111        StatementSectionDirectiveLine::B32Immediate { values, .. } => {
112            push_directive(tokens, "b32");
113            for (idx, value) in values.iter().enumerate() {
114                if idx > 0 {
115                    tokens.push(PtxToken::Comma);
116                    push_space(tokens, spaced);
117                }
118                push_space(tokens, spaced);
119                push_signed_decimal_i64(tokens, *value);
120            }
121            push_newline(tokens, spaced);
122        }
123        StatementSectionDirectiveLine::B64Immediate { values, .. } => {
124            push_directive(tokens, "b64");
125            for (idx, value) in values.iter().enumerate() {
126                if idx > 0 {
127                    tokens.push(PtxToken::Comma);
128                    push_space(tokens, spaced);
129                }
130                push_space(tokens, spaced);
131                push_signed_decimal_i128(tokens, *value);
132            }
133            push_newline(tokens, spaced);
134        }
135        StatementSectionDirectiveLine::B32Label { labels, .. } => {
136            push_directive(tokens, "b32");
137            push_space(tokens, spaced);
138            push_identifier(tokens, &labels.val);
139            push_newline(tokens, spaced);
140        }
141        StatementSectionDirectiveLine::B64Label { labels, .. } => {
142            push_directive(tokens, "b64");
143            push_space(tokens, spaced);
144            push_identifier(tokens, &labels.val);
145            push_newline(tokens, spaced);
146        }
147        StatementSectionDirectiveLine::B32LabelPlusImm { entries, .. } => {
148            push_directive(tokens, "b32");
149            let (label, offset) = entries;
150            push_space(tokens, spaced);
151            push_identifier(tokens, &label.val);
152            if *offset >= 0 {
153                tokens.push(PtxToken::Plus);
154                push_decimal(tokens, *offset);
155            } else {
156                tokens.push(PtxToken::Minus);
157                let magnitude = (*offset as i128).abs();
158                push_decimal(tokens, magnitude);
159            }
160            push_newline(tokens, spaced);
161        }
162        StatementSectionDirectiveLine::B64LabelPlusImm { entries, .. } => {
163            push_directive(tokens, "b64");
164            let (label, offset) = entries;
165            push_space(tokens, spaced);
166            push_identifier(tokens, &label.val);
167            if *offset >= 0 {
168                tokens.push(PtxToken::Plus);
169                push_decimal(tokens, *offset);
170            } else {
171                tokens.push(PtxToken::Minus);
172                let magnitude = (*offset as i128).abs();
173                push_decimal(tokens, magnitude);
174            }
175            push_newline(tokens, spaced);
176        }
177        StatementSectionDirectiveLine::B32LabelDiff { entries, .. } => {
178            push_directive(tokens, "b32");
179            let (left, right) = entries;
180            push_space(tokens, spaced);
181            push_identifier(tokens, &left.val);
182            tokens.push(PtxToken::Minus);
183            push_space(tokens, spaced);
184            push_identifier(tokens, &right.val);
185            push_newline(tokens, spaced);
186        }
187        StatementSectionDirectiveLine::B64LabelDiff { entries, .. } => {
188            push_directive(tokens, "b64");
189            let (left, right) = entries;
190            push_space(tokens, spaced);
191            push_identifier(tokens, &left.val);
192            tokens.push(PtxToken::Minus);
193            push_space(tokens, spaced);
194            push_identifier(tokens, &right.val);
195            push_newline(tokens, spaced);
196        }
197    }
198}
199
200fn push_signed_decimal_i64(tokens: &mut Vec<PtxToken>, value: i64) {
201    if value < 0 {
202        tokens.push(PtxToken::Minus);
203        push_decimal(tokens, (-value) as i128);
204    } else {
205        push_decimal(tokens, value);
206    }
207}
208
209fn push_signed_decimal_i128(tokens: &mut Vec<PtxToken>, value: i128) {
210    if value < 0 {
211        tokens.push(PtxToken::Minus);
212        push_decimal(tokens, -value);
213    } else {
214        push_decimal(tokens, value);
215    }
216}
217
218impl PtxUnparser for RegisterDirective {
219    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
220        self.unparse_tokens_mode(tokens, false);
221    }
222
223    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
224        push_directive(tokens, "reg");
225        push_space(tokens, spaced);
226        self.ty.unparse_tokens_mode(tokens, spaced);
227        for (idx, target) in self.registers.iter().enumerate() {
228            if idx > 0 {
229                tokens.push(PtxToken::Comma);
230                push_space(tokens, spaced);
231            } else {
232                push_space(tokens, spaced);
233            }
234            push_register_components(tokens, &target.name.val);
235            if let Some(range) = target.range {
236                tokens.push(PtxToken::LAngle);
237                push_decimal(tokens, range);
238                tokens.push(PtxToken::RAngle);
239            }
240        }
241        tokens.push(PtxToken::Semicolon);
242        push_newline(tokens, spaced);
243    }
244}
245
246impl PtxUnparser for StatementDirective {
247    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
248        self.unparse_tokens_mode(tokens, false);
249    }
250
251    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
252        match self {
253            StatementDirective::Reg {
254                directive: register,
255                ..
256            } => register.unparse_tokens_mode(tokens, spaced),
257            StatementDirective::Local {
258                directive: variable,
259                ..
260            } => {
261                push_directive(tokens, "local");
262                push_space(tokens, spaced);
263                variable.unparse_tokens_mode(tokens, spaced);
264            }
265            StatementDirective::Param {
266                directive: variable,
267                ..
268            } => {
269                push_directive(tokens, "param");
270                push_space(tokens, spaced);
271                variable.unparse_tokens_mode(tokens, spaced);
272            }
273            StatementDirective::Shared {
274                directive: variable,
275                ..
276            } => {
277                push_directive(tokens, "shared");
278                push_space(tokens, spaced);
279                variable.unparse_tokens_mode(tokens, spaced);
280            }
281            StatementDirective::Pragma {
282                directive: pragma, ..
283            } => {
284                push_directive(tokens, "pragma");
285                push_space(tokens, spaced);
286                let text = match &pragma.kind {
287                    PragmaDirectiveKind::Nounroll => "nounroll".to_string(),
288                    PragmaDirectiveKind::EnableSmemSpilling => "enable_smem_spilling".to_string(),
289                    PragmaDirectiveKind::UsedBytesMask { mask } => {
290                        format!("used_bytes_mask {}", mask)
291                    }
292                    PragmaDirectiveKind::Frequency { value } => {
293                        format!("frequency {}", value)
294                    }
295                    PragmaDirectiveKind::Raw(text) => text.clone(),
296                };
297                tokens.push(PtxToken::StringLiteral(text));
298                tokens.push(PtxToken::Semicolon);
299                push_newline(tokens, spaced);
300            }
301            StatementDirective::BranchTargets { directive, .. } => {
302                push_directive(tokens, "branchtargets");
303                push_space(tokens, spaced);
304                for (idx, label) in directive.labels.iter().enumerate() {
305                    if idx > 0 {
306                        tokens.push(PtxToken::Comma);
307                        push_space(tokens, spaced);
308                    }
309                    push_token_from_str(tokens, &label.val);
310                }
311                tokens.push(PtxToken::Semicolon);
312                push_newline(tokens, spaced);
313            }
314            StatementDirective::CallTargets { directive, .. } => {
315                push_directive(tokens, "calltargets");
316                push_space(tokens, spaced);
317                for (idx, target) in directive.targets.iter().enumerate() {
318                    if idx > 0 {
319                        tokens.push(PtxToken::Comma);
320                        push_space(tokens, spaced);
321                    }
322                    push_token_from_str(tokens, &target.val);
323                }
324                tokens.push(PtxToken::Semicolon);
325                push_newline(tokens, spaced);
326            }
327            StatementDirective::Loc { directive: loc, .. } => {
328                push_directive(tokens, "loc");
329                push_space(tokens, spaced);
330                push_decimal(tokens, loc.file_index);
331                push_space(tokens, spaced);
332                push_decimal(tokens, loc.line);
333                push_space(tokens, spaced);
334                push_decimal(tokens, loc.column);
335                if let Some(inline) = &loc.inlined_at {
336                    tokens.push(PtxToken::Comma);
337                    push_space(tokens, spaced);
338                    push_identifier(tokens, "inlined_at");
339                    push_space(tokens, spaced);
340                    push_decimal(tokens, inline.file_index);
341                    push_space(tokens, spaced);
342                    push_decimal(tokens, inline.line);
343                    push_space(tokens, spaced);
344                    push_decimal(tokens, inline.column);
345                    tokens.push(PtxToken::Comma);
346                    push_space(tokens, spaced);
347                    push_identifier(tokens, &inline.function_name.val);
348                    push_space(tokens, spaced);
349                    push_identifier(tokens, &inline.label.val);
350                    if let Some(offset) = inline.label_offset {
351                        if offset >= 0 {
352                            tokens.push(PtxToken::Plus);
353                        } else {
354                            tokens.push(PtxToken::Minus);
355                        }
356                        push_decimal(tokens, offset.abs());
357                    }
358                }
359                push_newline(tokens, spaced);
360            }
361            StatementDirective::Dwarf {
362                directive: dwarf, ..
363            } => {
364                dwarf.unparse_tokens_mode(tokens, spaced);
365                push_newline(tokens, spaced);
366            }
367            StatementDirective::Section {
368                directive: section, ..
369            } => {
370                section.unparse_tokens_mode(tokens, spaced);
371            }
372            StatementDirective::CallPrototype { directive, .. } => {
373                push_directive(tokens, "callprototype");
374                push_space(tokens, spaced);
375                if let Some(ret) = &directive.return_param {
376                    unparse_param(tokens, ret, spaced);
377                } else {
378                    push_identifier(tokens, "_");
379                }
380                tokens.push(PtxToken::LParen);
381                unparse_param_list(tokens, &directive.params, spaced);
382                tokens.push(PtxToken::RParen);
383                if directive.noreturn {
384                    push_space(tokens, spaced);
385                    push_directive(tokens, "noreturn");
386                }
387                if let Some(value) = directive.abi_preserve {
388                    push_space(tokens, spaced);
389                    push_directive(tokens, "abi_preserve");
390                    push_space(tokens, spaced);
391                    push_decimal(tokens, value);
392                }
393                if let Some(value) = directive.abi_preserve_control {
394                    push_space(tokens, spaced);
395                    push_directive(tokens, "abi_preserve_control");
396                    push_space(tokens, spaced);
397                    push_decimal(tokens, value);
398                }
399                tokens.push(PtxToken::Semicolon);
400                push_newline(tokens, spaced);
401            }
402        }
403    }
404}
405
406impl PtxUnparser for SectionDirective {
407    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
408        self.unparse_tokens_mode(tokens, false);
409    }
410
411    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
412        push_directive(tokens, "section");
413        push_space(tokens, spaced);
414        push_token_from_str(tokens, &self.name);
415        push_space(tokens, spaced);
416        tokens.push(PtxToken::LBrace);
417        push_newline(tokens, spaced);
418        for entry in &self.entries {
419            match entry {
420                SectionEntry::Label { label, .. } => {
421                    push_identifier(tokens, &label.val);
422                    tokens.push(PtxToken::Colon);
423                    push_newline(tokens, spaced);
424                }
425                SectionEntry::Directive(line) => {
426                    unparse_section_line(tokens, line, spaced);
427                }
428            }
429        }
430        tokens.push(PtxToken::RBrace);
431        push_newline(tokens, spaced);
432    }
433}
434
435impl PtxUnparser for FunctionStatement {
436    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
437        self.unparse_tokens_mode(tokens, false);
438    }
439
440    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
441        match self {
442            FunctionStatement::Label { label, .. } => {
443                push_identifier(tokens, &label.val);
444                tokens.push(PtxToken::Colon);
445                push_newline(tokens, spaced);
446            }
447            FunctionStatement::Instruction { instruction, .. } => {
448                instruction.unparse_tokens_mode(tokens, spaced)
449            }
450            FunctionStatement::Directive { directive, .. } => {
451                directive.unparse_tokens_mode(tokens, spaced)
452            }
453            FunctionStatement::Block {
454                statements: block, ..
455            } => {
456                tokens.push(PtxToken::LBrace);
457                for statement in block {
458                    statement.unparse_tokens_mode(tokens, spaced);
459                }
460                tokens.push(PtxToken::RBrace);
461                push_newline(tokens, spaced);
462            }
463        }
464    }
465}
466
467impl PtxUnparser for FunctionBody {
468    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
469        self.unparse_tokens_mode(tokens, false);
470    }
471
472    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
473        tokens.push(PtxToken::LBrace);
474        push_newline(tokens, spaced);
475        for statement in &self.statements {
476            statement.unparse_tokens_mode(tokens, spaced);
477        }
478        tokens.push(PtxToken::RBrace);
479        push_newline(tokens, spaced);
480    }
481}
482
483impl PtxUnparser for FunctionDim {
484    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
485        self.unparse_tokens_mode(tokens, false);
486    }
487
488    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
489        match self {
490            FunctionDim::X { x, .. } => {
491                push_decimal(tokens, *x);
492            }
493            FunctionDim::XY { x, y, .. } => {
494                push_decimal(tokens, *x);
495                tokens.push(PtxToken::Comma);
496                push_space(tokens, spaced);
497                push_decimal(tokens, *y);
498            }
499            FunctionDim::XYZ { x, y, z, .. } => {
500                push_decimal(tokens, *x);
501                tokens.push(PtxToken::Comma);
502                push_space(tokens, spaced);
503                push_decimal(tokens, *y);
504                tokens.push(PtxToken::Comma);
505                push_space(tokens, spaced);
506                push_decimal(tokens, *z);
507            }
508        }
509    }
510}
511
512impl PtxUnparser for EntryFunctionHeaderDirective {
513    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
514        self.unparse_tokens_mode(tokens, false);
515    }
516
517    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
518        match self {
519            EntryFunctionHeaderDirective::MaxNReg { value, .. } => {
520                push_directive(tokens, "maxnreg");
521                push_space(tokens, spaced);
522                push_decimal(tokens, *value);
523            }
524            EntryFunctionHeaderDirective::MaxNTid { dim, .. } => {
525                push_directive(tokens, "maxntid");
526                push_space(tokens, spaced);
527                dim.unparse_tokens_mode(tokens, spaced);
528            }
529            EntryFunctionHeaderDirective::ReqNTid { dim, .. } => {
530                push_directive(tokens, "reqntid");
531                push_space(tokens, spaced);
532                dim.unparse_tokens_mode(tokens, spaced);
533            }
534            EntryFunctionHeaderDirective::MinNCtaPerSm { value, .. } => {
535                push_directive(tokens, "minnctapersm");
536                push_space(tokens, spaced);
537                push_decimal(tokens, *value);
538            }
539            EntryFunctionHeaderDirective::MaxNCtaPerSm { value, .. } => {
540                push_directive(tokens, "maxnctapersm");
541                push_space(tokens, spaced);
542                push_decimal(tokens, *value);
543            }
544            EntryFunctionHeaderDirective::Pragma {
545                args: arguments, ..
546            } => {
547                push_directive(tokens, "pragma");
548                push_space(tokens, spaced);
549                for argument in arguments {
550                    tokens.push(PtxToken::StringLiteral(argument.clone()));
551                    push_space(tokens, spaced);
552                }
553                if spaced {
554                    if let Some(last) = tokens.last() {
555                        if matches!(last, PtxToken::Space) {
556                            tokens.pop();
557                        }
558                    }
559                }
560            }
561            EntryFunctionHeaderDirective::ReqNctaPerCluster { dim, .. } => {
562                push_directive(tokens, "reqnctapercluster");
563                push_space(tokens, spaced);
564                dim.unparse_tokens_mode(tokens, spaced);
565            }
566            EntryFunctionHeaderDirective::ExplicitCluster { .. } => {
567                push_directive(tokens, "explicitcluster");
568            }
569            EntryFunctionHeaderDirective::MaxClusterRank { value, .. } => {
570                push_directive(tokens, "maxclusterrank");
571                push_space(tokens, spaced);
572                push_decimal(tokens, *value);
573            }
574            EntryFunctionHeaderDirective::BlocksAreClusters { .. } => {
575                push_directive(tokens, "blocksareclusters")
576            }
577        }
578    }
579}
580
581impl PtxUnparser for FuncFunctionHeaderDirective {
582    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
583        self.unparse_tokens_mode(tokens, false);
584    }
585
586    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
587        match self {
588            FuncFunctionHeaderDirective::NoReturn { .. } => push_directive(tokens, "noreturn"),
589            FuncFunctionHeaderDirective::Pragma {
590                args: arguments, ..
591            } => {
592                push_directive(tokens, "pragma");
593                push_space(tokens, spaced);
594                for argument in arguments {
595                    tokens.push(PtxToken::StringLiteral(argument.clone()));
596                    push_space(tokens, spaced);
597                }
598                if spaced {
599                    if let Some(PtxToken::Space) = tokens.last() {
600                        tokens.pop();
601                    }
602                }
603            }
604            FuncFunctionHeaderDirective::AbiPreserve { value, .. } => {
605                push_directive(tokens, "abi_preserve");
606                push_space(tokens, spaced);
607                push_decimal(tokens, *value);
608            }
609            FuncFunctionHeaderDirective::AbiPreserveControl { value, .. } => {
610                push_directive(tokens, "abi_preserve_control");
611                push_space(tokens, spaced);
612                push_decimal(tokens, *value);
613            }
614        }
615    }
616}
617
618impl PtxUnparser for AliasFunctionDirective {
619    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
620        self.unparse_tokens_mode(tokens, false);
621    }
622
623    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
624        push_directive(tokens, "alias");
625        push_space(tokens, spaced);
626        push_identifier(tokens, &self.alias.val);
627        tokens.push(PtxToken::Comma);
628        push_space(tokens, spaced);
629        push_identifier(tokens, &self.target.val);
630        tokens.push(PtxToken::Semicolon);
631        push_newline(tokens, spaced);
632    }
633}
634
635impl PtxUnparser for EntryFunctionDirective {
636    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
637        self.unparse_tokens_mode(tokens, false);
638    }
639
640    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
641        for directive in &self.directives {
642            directive.unparse_tokens_mode(tokens, spaced);
643            if spaced {
644                push_space(tokens, spaced);
645            }
646        }
647        push_directive(tokens, "entry");
648        push_space(tokens, spaced);
649        push_identifier(tokens, &self.name.val);
650        tokens.push(PtxToken::LParen);
651        unparse_param_list(tokens, &self.params, spaced);
652        tokens.push(PtxToken::RParen);
653        match &self.body {
654            Some(body) => body.unparse_tokens_mode(tokens, spaced),
655            None => {
656                tokens.push(PtxToken::Semicolon);
657                push_newline(tokens, spaced);
658            }
659        }
660    }
661}
662
663impl PtxUnparser for FuncFunctionDirective {
664    fn unparse_tokens(&self, tokens: &mut Vec<PtxToken>) {
665        self.unparse_tokens_mode(tokens, false);
666    }
667
668    fn unparse_tokens_mode(&self, tokens: &mut Vec<PtxToken>, spaced: bool) {
669        for attribute in &self.attributes {
670            attribute.unparse_tokens_mode(tokens, spaced);
671            if spaced {
672                push_space(tokens, spaced);
673            }
674        }
675        for directive in &self.directives {
676            directive.unparse_tokens_mode(tokens, spaced);
677            if spaced {
678                push_space(tokens, spaced);
679            }
680        }
681        push_directive(tokens, "func");
682        if let Some(ret) = &self.return_param {
683            push_space(tokens, spaced);
684            tokens.push(PtxToken::LParen);
685            unparse_param(tokens, ret, spaced);
686            tokens.push(PtxToken::RParen);
687        }
688        push_space(tokens, spaced);
689        push_identifier(tokens, &self.name.val);
690        tokens.push(PtxToken::LParen);
691        unparse_param_list(tokens, &self.params, spaced);
692        tokens.push(PtxToken::RParen);
693        match &self.body {
694            Some(body) => body.unparse_tokens_mode(tokens, spaced),
695            None => {
696                tokens.push(PtxToken::Semicolon);
697                push_newline(tokens, spaced);
698            }
699        }
700    }
701}