microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
billti/copilot

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

compiler/qsc_doc_gen/src/display.rs

768lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use qsc_ast::ast;
5use qsc_frontend::resolve;
6use qsc_hir::{
7 hir::{self, PackageId},
8 ty::{self, GenericParam},
9};
10use regex_lite::Regex;
11use std::{
12 fmt::{Display, Formatter, Result},
13 rc::Rc,
14};
15
16/// Trait describing a struct capable of resolving various ids found in the AST and HIR.
17pub trait Lookup {
18 /// Looks up the type of a node in user code
19 fn get_ty(&self, expr_id: ast::NodeId) -> Option<&ty::Ty>;
20
21 /// Looks up the resolution of a node in user code
22 fn get_res(&self, id: ast::NodeId) -> Option<&resolve::Res>;
23
24 /// Returns the hir `Item` node referred to by `item_id`,
25 /// along with the `Package` and `PackageId` for the package
26 /// that it was found in.
27 fn resolve_item_relative_to_user_package(
28 &self,
29 item_id: &hir::ItemId,
30 ) -> (&hir::Item, &hir::Package, hir::ItemId);
31
32 /// Returns the hir `Item` node referred to by `res`.
33 /// `Res`s can resolve to external packages, and the references
34 /// are relative, so here we also need the
35 /// local `PackageId` that the `res` itself came from.
36 fn resolve_item_res(
37 &self,
38 local_package_id: PackageId,
39 res: &hir::Res,
40 ) -> (&hir::Item, hir::ItemId);
41
42 /// Returns the hir `Item` node referred to by `item_id`.
43 /// `ItemId`s can refer to external packages, and the references
44 /// are relative, so here we also need the local `PackageId`
45 /// that the `ItemId` originates from.
46 fn resolve_item(
47 &self,
48 local_package_id: PackageId,
49 item_id: &hir::ItemId,
50 ) -> (&hir::Item, &hir::Package, hir::ItemId);
51}
52
53pub struct CodeDisplay<'a> {
54 pub compilation: &'a dyn Lookup,
55}
56
57#[allow(clippy::unused_self)]
58impl<'a> CodeDisplay<'a> {
59 #[must_use]
60 pub fn hir_callable_decl(&self, decl: &'a hir::CallableDecl) -> impl Display + '_ {
61 HirCallableDecl { decl }
62 }
63
64 #[must_use]
65 pub fn ast_callable_decl(&self, decl: &'a ast::CallableDecl) -> impl Display + '_ {
66 AstCallableDecl {
67 lookup: self.compilation,
68 decl,
69 }
70 }
71
72 #[must_use]
73 pub fn name_ty_id(&self, name: &'a str, ty_id: ast::NodeId) -> impl Display + '_ {
74 NameTyId {
75 lookup: self.compilation,
76 name,
77 ty_id,
78 }
79 }
80
81 #[must_use]
82 pub fn ident_ty(&self, ident: &'a ast::Ident, ty: &'a ast::Ty) -> impl Display + '_ {
83 IdentTy { ident, ty }
84 }
85
86 #[must_use]
87 pub fn ident_ty_def(&self, ident: &'a ast::Ident, def: &'a ast::TyDef) -> impl Display + 'a {
88 IdentTyDef { ident, def }
89 }
90
91 #[must_use]
92 pub fn struct_decl(&self, decl: &'a ast::StructDecl) -> impl Display + 'a {
93 StructDecl { decl }
94 }
95
96 #[must_use]
97 pub fn hir_udt_field(&self, field: &'a ty::UdtField) -> impl Display + '_ {
98 HirUdtField { field }
99 }
100
101 #[must_use]
102 pub fn hir_udt(&self, udt: &'a ty::Udt) -> impl Display + '_ {
103 HirUdt::new(udt)
104 }
105
106 #[must_use]
107 pub fn hir_pat(&self, pat: &'a hir::Pat) -> impl Display + '_ {
108 HirPat { pat }
109 }
110
111 #[must_use]
112 pub fn get_param_offset(&self, decl: &hir::CallableDecl) -> u32 {
113 HirCallableDecl { decl }.get_param_offset()
114 }
115
116 // The rest of the display implementations are not made public b/c they're not used,
117 // but there's no reason they couldn't be
118}
119
120// Display impls for each syntax/hir element we may encounter
121
122struct IdentTy<'a> {
123 ident: &'a ast::Ident,
124 ty: &'a ast::Ty,
125}
126
127impl<'a> Display for IdentTy<'a> {
128 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
129 write!(f, "{} : {}", self.ident.name, AstTy { ty: self.ty },)
130 }
131}
132
133struct NameTyId<'a> {
134 lookup: &'a dyn Lookup,
135 name: &'a str,
136 ty_id: ast::NodeId,
137}
138
139impl<'a> Display for NameTyId<'a> {
140 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
141 write!(
142 f,
143 "{} : {}",
144 self.name,
145 TyId {
146 lookup: self.lookup,
147 ty_id: self.ty_id,
148 },
149 )
150 }
151}
152
153struct HirCallableDecl<'a> {
154 decl: &'a hir::CallableDecl,
155}
156
157impl HirCallableDecl<'_> {
158 fn get_param_offset(&self) -> u32 {
159 let offset = match self.decl.kind {
160 hir::CallableKind::Function => "function".len(),
161 hir::CallableKind::Operation => "operation".len(),
162 } + 1 // this is for the space between keyword and name
163 + self.decl.name.name.len()
164 + display_type_params(&self.decl.generics).len();
165
166 u32::try_from(offset)
167 .expect("failed to cast usize to u32 while calculating parameter offset")
168 }
169}
170
171impl Display for HirCallableDecl<'_> {
172 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
173 let kind = match self.decl.kind {
174 hir::CallableKind::Function => "function",
175 hir::CallableKind::Operation => "operation",
176 };
177
178 write!(f, "{} {}", kind, self.decl.name.name)?;
179 let type_params = display_type_params(&self.decl.generics);
180 write!(f, "{type_params}")?;
181 let input = HirPat {
182 pat: &self.decl.input,
183 };
184 if matches!(self.decl.input.kind, hir::PatKind::Tuple(_)) {
185 write!(f, "{input}")?;
186 } else {
187 write!(f, "({input})")?;
188 }
189 write!(
190 f,
191 " : {}{}",
192 self.decl.output.display(),
193 FunctorSetValue {
194 functors: self.decl.functors,
195 },
196 )
197 }
198}
199
200struct AstCallableDecl<'a> {
201 lookup: &'a dyn Lookup,
202 decl: &'a ast::CallableDecl,
203}
204
205impl<'a> Display for AstCallableDecl<'a> {
206 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
207 let kind = match self.decl.kind {
208 ast::CallableKind::Function => "function",
209 ast::CallableKind::Operation => "operation",
210 };
211
212 let functors = ast_callable_functors(self.decl);
213 let functors = FunctorSetValue { functors };
214
215 write!(f, "{} {}", kind, self.decl.name.name)?;
216 if !self.decl.generics.is_empty() {
217 let type_params = self
218 .decl
219 .generics
220 .iter()
221 .map(|p| p.name.clone())
222 .collect::<Vec<_>>()
223 .join(", ");
224 write!(f, "<{type_params}>")?;
225 }
226 let input = AstPat {
227 pat: &self.decl.input,
228 lookup: self.lookup,
229 };
230 if matches!(*self.decl.input.kind, ast::PatKind::Tuple(_)) {
231 write!(f, "{input}")?;
232 } else {
233 write!(f, "({input})")?;
234 }
235 write!(
236 f,
237 " : {}{}",
238 AstTy {
239 ty: &self.decl.output
240 },
241 functors,
242 )
243 }
244}
245
246struct HirPat<'a> {
247 pat: &'a hir::Pat,
248}
249
250impl<'a> Display for HirPat<'a> {
251 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
252 match &self.pat.kind {
253 hir::PatKind::Bind(name) => write!(f, "{} : {}", name.name, self.pat.ty.display()),
254 hir::PatKind::Discard => write!(f, "_ : {}", self.pat.ty.display()),
255 hir::PatKind::Tuple(items) => {
256 let mut elements = items.iter();
257 if let Some(elem) = elements.next() {
258 write!(f, "({}", HirPat { pat: elem })?;
259 for elem in elements {
260 write!(f, ", {}", HirPat { pat: elem })?;
261 }
262 write!(f, ")")
263 } else {
264 write!(f, "()")
265 }
266 }
267 hir::PatKind::Err => write!(f, "?"),
268 }
269 }
270}
271
272struct AstPat<'a> {
273 lookup: &'a dyn Lookup,
274 pat: &'a ast::Pat,
275}
276
277impl<'a> Display for AstPat<'a> {
278 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
279 match &*self.pat.kind {
280 ast::PatKind::Bind(ident, anno) => match anno {
281 Some(ty) => write!(f, "{}", IdentTy { ident, ty }),
282 None => write!(
283 f,
284 "{}",
285 NameTyId {
286 lookup: self.lookup,
287 name: &ident.name,
288 ty_id: self.pat.id
289 }
290 ),
291 },
292 ast::PatKind::Discard(anno) => match anno {
293 Some(ty) => write!(f, "{}", AstTy { ty }),
294 None => write!(
295 f,
296 "_ : {}",
297 TyId {
298 lookup: self.lookup,
299 ty_id: self.pat.id,
300 }
301 ),
302 },
303 ast::PatKind::Elided => write!(f, "..."),
304 ast::PatKind::Paren(item) => write!(
305 f,
306 "{}",
307 AstPat {
308 lookup: self.lookup,
309 pat: item,
310 }
311 ),
312 ast::PatKind::Tuple(items) => {
313 let mut elements = items.iter();
314 if let Some(elem) = elements.next() {
315 write!(
316 f,
317 "({}",
318 AstPat {
319 lookup: self.lookup,
320 pat: elem,
321 }
322 )?;
323 for elem in elements {
324 write!(
325 f,
326 ", {}",
327 AstPat {
328 lookup: self.lookup,
329 pat: elem,
330 }
331 )?;
332 }
333 write!(f, ")")
334 } else {
335 write!(f, "()")
336 }
337 }
338 ast::PatKind::Err => write!(f, "?"),
339 }
340 }
341}
342
343struct IdentTyDef<'a> {
344 ident: &'a ast::Ident,
345 def: &'a ast::TyDef,
346}
347
348impl Display for IdentTyDef<'_> {
349 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
350 if let Some(fields) = as_struct(self.def) {
351 write!(f, "struct {} ", self.ident.name)?;
352 fmt_brace_seq(f, &fields, |item| IdentTy {
353 ident: &item.name,
354 ty: &item.ty,
355 })
356 } else {
357 write!(
358 f,
359 "newtype {} = {}",
360 self.ident.name,
361 TyDef { def: self.def }
362 )
363 }
364 }
365}
366
367struct StructDecl<'a> {
368 decl: &'a ast::StructDecl,
369}
370
371impl Display for StructDecl<'_> {
372 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
373 write!(f, "struct {} ", self.decl.name.name)?;
374 fmt_brace_seq(f, &self.decl.fields, |item| IdentTy {
375 ident: &item.name,
376 ty: &item.ty,
377 })
378 }
379}
380
381struct HirUdt<'a> {
382 udt: &'a ty::Udt,
383 is_struct: bool,
384}
385
386impl<'a> HirUdt<'a> {
387 fn new(udt: &'a ty::Udt) -> Self {
388 HirUdt {
389 udt,
390 is_struct: udt.is_struct(),
391 }
392 }
393}
394
395impl<'a> Display for HirUdt<'a> {
396 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
397 if self.is_struct {
398 match &self.udt.definition.kind {
399 ty::UdtDefKind::Tuple(fields) => {
400 write!(f, "struct {} ", self.udt.name)?;
401 fmt_brace_seq(f, fields, UdtDef::new)?;
402 }
403 ty::UdtDefKind::Field(_) => {}
404 }
405 Ok(())
406 } else {
407 let udt_def = UdtDef::new(&self.udt.definition);
408 write!(f, "newtype {} = {}", self.udt.name, udt_def)
409 }
410 }
411}
412
413struct UdtDef<'a> {
414 name: Option<Rc<str>>,
415 kind: UdtDefKind<'a>,
416}
417
418enum UdtDefKind<'a> {
419 SingleTy(&'a ty::Ty),
420 TupleTy(Vec<UdtDef<'a>>),
421}
422
423impl<'a> UdtDef<'a> {
424 pub fn new(def: &'a ty::UdtDef) -> Self {
425 match &def.kind {
426 ty::UdtDefKind::Field(field) => UdtDef {
427 name: field.name.clone(),
428 kind: UdtDefKind::SingleTy(&field.ty),
429 },
430 ty::UdtDefKind::Tuple(defs) => UdtDef {
431 name: None,
432 kind: UdtDefKind::TupleTy(defs.iter().map(UdtDef::new).collect()),
433 },
434 }
435 }
436}
437
438impl Display for UdtDef<'_> {
439 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
440 if let Some(name) = &self.name {
441 write!(f, "{name} : ")?;
442 }
443
444 match &self.kind {
445 UdtDefKind::SingleTy(ty) => {
446 write!(f, "{}", ty.display())
447 }
448 UdtDefKind::TupleTy(defs) => fmt_tuple(f, defs, |def| def),
449 }
450 }
451}
452
453struct HirUdtField<'a> {
454 field: &'a ty::UdtField,
455}
456
457impl Display for HirUdtField<'_> {
458 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
459 if let Some(name) = &self.field.name {
460 write!(f, "{name} : ")?;
461 }
462 write!(f, "{}", self.field.ty.display())
463 }
464}
465
466struct FunctorSetValue {
467 functors: ty::FunctorSetValue,
468}
469
470impl Display for FunctorSetValue {
471 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
472 if let ty::FunctorSetValue::Empty = self.functors {
473 Ok(())
474 } else {
475 write!(f, " is {}", self.functors)
476 }
477 }
478}
479
480struct TyId<'a> {
481 lookup: &'a dyn Lookup,
482 ty_id: ast::NodeId,
483}
484
485impl<'a> Display for TyId<'a> {
486 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
487 if let Some(ty) = self.lookup.get_ty(self.ty_id) {
488 write!(f, "{}", ty.display())
489 } else {
490 write!(f, "?")
491 }
492 }
493}
494
495struct AstTy<'a> {
496 ty: &'a ast::Ty,
497}
498
499impl<'a> Display for AstTy<'a> {
500 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
501 match self.ty.kind.as_ref() {
502 ast::TyKind::Array(ty) => write!(f, "{}[]", AstTy { ty }),
503 ast::TyKind::Arrow(kind, input, output, functors) => {
504 let arrow = match kind {
505 ast::CallableKind::Function => "->",
506 ast::CallableKind::Operation => "=>",
507 };
508 write!(
509 f,
510 "({} {} {}{})",
511 AstTy { ty: input },
512 arrow,
513 AstTy { ty: output },
514 FunctorExpr { functors }
515 )
516 }
517 ast::TyKind::Hole => write!(f, "_"),
518 ast::TyKind::Paren(ty) => write!(f, "{}", AstTy { ty }),
519 ast::TyKind::Path(path) => write!(f, "{}", AstPath { path }),
520 ast::TyKind::Param(id) => write!(f, "{}", id.name),
521 ast::TyKind::Tuple(tys) => fmt_tuple(f, tys, |ty| AstTy { ty }),
522 ast::TyKind::Err => write!(f, "?"),
523 }
524 }
525}
526
527struct FunctorExpr<'a> {
528 functors: &'a Option<Box<ast::FunctorExpr>>,
529}
530
531impl<'a> Display for FunctorExpr<'a> {
532 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
533 match self.functors {
534 Some(functors) => {
535 let functors = eval_functor_expr(functors);
536 write!(f, "{}", FunctorSetValue { functors })
537 }
538 None => Ok(()),
539 }
540 }
541}
542
543struct AstPath<'a> {
544 path: &'a ast::Path,
545}
546
547impl<'a> Display for AstPath<'a> {
548 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
549 match self.path.segments.as_ref() {
550 Some(parts) => write!(f, "{parts}.{}", self.path.name.name),
551 None => write!(f, "{}", self.path.name.name),
552 }
553 }
554}
555
556struct TyDef<'a> {
557 def: &'a ast::TyDef,
558}
559
560impl<'a> Display for TyDef<'a> {
561 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
562 match self.def.kind.as_ref() {
563 ast::TyDefKind::Field(name, ty) => match name {
564 Some(name) => write!(f, "{} : {}", name.name, AstTy { ty }),
565 None => write!(f, "{}", AstTy { ty }),
566 },
567 ast::TyDefKind::Paren(def) => write!(f, "{}", TyDef { def }),
568 ast::TyDefKind::Tuple(tys) => fmt_tuple(f, tys, |def| TyDef { def }),
569 ast::TyDefKind::Err => write!(f, "?"),
570 }
571 }
572}
573
574fn fmt_tuple<'a, I, O>(
575 formatter: &mut Formatter,
576 items: &'a [I],
577 map: impl Fn(&'a I) -> O,
578) -> Result
579where
580 O: Display,
581{
582 let mut elements = items.iter();
583 if let Some(elem) = elements.next() {
584 write!(formatter, "({}", map(elem))?;
585 if elements.len() == 0 {
586 write!(formatter, ",)")?;
587 } else {
588 for elem in elements {
589 write!(formatter, ", {}", map(elem))?;
590 }
591 write!(formatter, ")")?;
592 }
593 } else {
594 write!(formatter, "Unit")?;
595 }
596 Ok(())
597}
598
599fn fmt_brace_seq<'a, I, O>(
600 formatter: &mut Formatter<'_>,
601 items: &'a [I],
602 map: impl Fn(&'a I) -> O,
603) -> Result
604where
605 O: Display,
606{
607 write!(formatter, "{{ ")?;
608 if let Some((last, most)) = items.split_last() {
609 for item in most {
610 write!(formatter, "{}, ", map(item))?;
611 }
612 write!(formatter, "{} ", map(last))?;
613 }
614 write!(formatter, "}}")
615}
616
617fn display_type_params(generics: &[GenericParam]) -> String {
618 let type_params = generics
619 .iter()
620 .filter_map(|generic| match generic {
621 GenericParam::Ty(name) => Some(name.name.clone()),
622 GenericParam::Functor(_) => None,
623 })
624 .collect::<Vec<_>>()
625 .join(", ");
626 if type_params.is_empty() {
627 type_params
628 } else {
629 format!("<{type_params}>")
630 }
631}
632
633//
634// helpers that don't manipulate any strings
635//
636
637fn ast_callable_functors(callable: &ast::CallableDecl) -> ty::FunctorSetValue {
638 let mut functors = callable
639 .functors
640 .as_ref()
641 .map_or(ty::FunctorSetValue::Empty, |f| {
642 eval_functor_expr(f.as_ref())
643 });
644
645 if let ast::CallableBody::Specs(specs) = callable.body.as_ref() {
646 for spec in specs {
647 let spec_functors = match spec.spec {
648 ast::Spec::Body => ty::FunctorSetValue::Empty,
649 ast::Spec::Adj => ty::FunctorSetValue::Adj,
650 ast::Spec::Ctl => ty::FunctorSetValue::Ctl,
651 ast::Spec::CtlAdj => ty::FunctorSetValue::CtlAdj,
652 };
653 functors = functors.union(&spec_functors);
654 }
655 }
656
657 functors
658}
659
660fn eval_functor_expr(expr: &ast::FunctorExpr) -> ty::FunctorSetValue {
661 match expr.kind.as_ref() {
662 ast::FunctorExprKind::BinOp(op, lhs, rhs) => {
663 let lhs_functors = eval_functor_expr(lhs);
664 let rhs_functors = eval_functor_expr(rhs);
665 match op {
666 ast::SetOp::Union => lhs_functors.union(&rhs_functors),
667 ast::SetOp::Intersect => lhs_functors.intersect(&rhs_functors),
668 }
669 }
670 ast::FunctorExprKind::Lit(ast::Functor::Adj) => ty::FunctorSetValue::Adj,
671 ast::FunctorExprKind::Lit(ast::Functor::Ctl) => ty::FunctorSetValue::Ctl,
672 ast::FunctorExprKind::Paren(inner) => eval_functor_expr(inner),
673 }
674}
675
676fn as_struct(ty_def: &ast::TyDef) -> Option<Vec<ast::FieldDef>> {
677 match ty_def.kind.as_ref() {
678 ast::TyDefKind::Paren(inner) => as_struct(inner),
679 ast::TyDefKind::Tuple(fields) => {
680 let mut converted_fields = Vec::new();
681 for field in fields {
682 let field = remove_parens(field);
683 match field.kind.as_ref() {
684 ast::TyDefKind::Field(Some(name), field_ty) => {
685 converted_fields.push(ast::FieldDef {
686 id: field.id,
687 span: field.span,
688 name: name.clone(),
689 ty: field_ty.clone(),
690 });
691 }
692 _ => return None,
693 }
694 }
695 Some(converted_fields)
696 }
697 ast::TyDefKind::Err | ast::TyDefKind::Field(..) => None,
698 }
699}
700
701fn remove_parens(ty_def: &ast::TyDef) -> &ast::TyDef {
702 match ty_def.kind.as_ref() {
703 ast::TyDefKind::Paren(inner) => remove_parens(inner.as_ref()),
704 _ => ty_def,
705 }
706}
707
708//
709// parsing functions for working with doc comments
710//
711
712/// Takes a doc string from Q# and increases all of the markdown header levels by one level.
713/// i.e. `# Summary` becomes `## Summary`
714#[must_use]
715pub fn increase_header_level(doc: &str) -> String {
716 let re = Regex::new(r"(?mi)^(#+)( [\s\S]+?)$").expect("Invalid regex");
717 re.replace_all(doc, "$1#$2").to_string()
718}
719
720/// Takes a doc string from Q# and returns the contents of the `# Summary` section. If no
721/// such section can be found, returns the original doc string.
722#[must_use]
723pub fn parse_doc_for_summary(doc: &str) -> String {
724 let re = Regex::new(r"(?mi)(?:^# Summary$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex");
725 match re.captures(doc) {
726 Some(captures) => {
727 let capture = captures
728 .get(1)
729 .expect("Didn't find the capture for the given regex");
730 capture.as_str()
731 }
732 None => doc,
733 }
734 .trim()
735 .to_string()
736}
737
738/// Takes a doc string from a Q# callable and the name of a parameter of
739/// that callable. Returns the description of that parameter found in the
740/// doc string. If no description is found, returns the empty string.
741#[must_use]
742pub fn parse_doc_for_param(doc: &str, param: &str) -> String {
743 let re = Regex::new(r"(?mi)(?:^# Input$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex");
744 let input = match re.captures(doc) {
745 Some(captures) => {
746 let capture = captures
747 .get(1)
748 .expect("Didn't find the capture for the given regex");
749 capture.as_str()
750 }
751 None => return String::new(),
752 }
753 .trim();
754
755 let re = Regex::new(format!(r"(?mi)(?:^## {param}$)([\s\S]*?)(?:(^(#|##) .*)|\z)").as_str())
756 .expect("Invalid regex");
757 match re.captures(input) {
758 Some(captures) => {
759 let capture = captures
760 .get(1)
761 .expect("Didn't find the capture for the given regex");
762 capture.as_str()
763 }
764 None => return String::new(),
765 }
766 .trim()
767 .to_string()
768}