microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc_doc_gen/src/generate_docs.rs

774lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::display::{CodeDisplay, Lookup, increase_header_level, parse_doc_for_summary};
8use crate::table_of_contents::table_of_contents;
9use qsc_ast::ast;
10use qsc_data_structures::language_features::LanguageFeatures;
11use qsc_data_structures::source::SourceMap;
12use qsc_data_structures::target::TargetCapabilityFlags;
13use qsc_frontend::compile::{self, Dependencies, PackageStore, compile};
14use qsc_frontend::resolve;
15use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Res, Visibility};
16use qsc_hir::{hir, ty};
17use rustc_hash::FxHashMap;
18use std::collections::BTreeMap;
19use std::fmt::Write;
20use std::fmt::{Display, Formatter, Result};
21use std::rc::Rc;
22use std::sync::Arc;
23
24// Name, Metadata, Content
25type Files = Vec<(Rc<str>, Rc<str>, Rc<str>)>;
26type FilesWithMetadata = Vec<(Rc<str>, Rc<Metadata>, Rc<str>)>;
27
28// Namespace -> metadata for items
29type ToC = FxHashMap<Rc<str>, Vec<Rc<Metadata>>>;
30
31struct Metadata {
32 uid: String,
33 title: String,
34 kind: MetadataKind,
35 package: PackageKind,
36 namespace: Rc<str>,
37 name: Rc<str>,
38 summary: String,
39 signature: String,
40}
41
42impl Metadata {
43 fn fully_qualified_name(&self) -> String {
44 let mut buf = if let PackageKind::AliasedPackage(ref package_alias) = self.package {
45 vec![format!("{package_alias}")]
46 } else {
47 vec![]
48 };
49
50 buf.push(self.namespace.to_string());
51 buf.push(self.name.to_string());
52 buf.join(".")
53 }
54
55 fn display_for_toc(&self) -> String {
56 format!(
57 "---
58uid: {}
59title: {}
60description: {}
61ms.date: {{TIMESTAMP}}
62ms.topic: landing-page
63---",
64 self.uid, self.title, self.summary,
65 )
66 }
67
68 fn display_for_item(&self) -> String {
69 let kind = match &self.kind {
70 MetadataKind::Function => "function",
71 MetadataKind::Operation => "operation",
72 MetadataKind::Udt => "udt",
73 MetadataKind::Export => "export",
74 MetadataKind::TableOfContents => "table of contents",
75 };
76 format!(
77 "---
78uid: {}
79title: {}
80description: \"Q# {}: {}\"
81ms.date: {{TIMESTAMP}}
82qsharp.kind: {}
83qsharp.package: {}
84qsharp.namespace: {}
85qsharp.name: {}
86qsharp.summary: \"{}\"
87---",
88 self.uid,
89 self.title,
90 self.title,
91 self.summary,
92 kind,
93 self.package,
94 self.namespace,
95 self.name,
96 self.summary
97 )
98 }
99}
100
101impl Display for Metadata {
102 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
103 if self.kind == MetadataKind::TableOfContents {
104 write!(f, "{}", self.display_for_toc())
105 } else {
106 write!(f, "{}", self.display_for_item())
107 }
108 }
109}
110
111#[derive(PartialOrd, Ord, Eq, PartialEq, Clone)]
112enum MetadataKind {
113 Function,
114 Operation,
115 Udt,
116 Export,
117 TableOfContents,
118}
119
120impl Display for MetadataKind {
121 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
122 let s = match &self {
123 MetadataKind::Function => "function",
124 MetadataKind::Operation => "operation",
125 MetadataKind::Udt => "user defined type",
126 MetadataKind::Export => "exported item",
127 MetadataKind::TableOfContents => "table of contents",
128 };
129 write!(f, "{s}")
130 }
131}
132
133#[derive(PartialOrd, Ord, Eq, PartialEq, Clone)]
134enum PackageKind {
135 UserCode,
136 AliasedPackage(String),
137 StandardLibrary,
138 Core,
139}
140
141impl Display for PackageKind {
142 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
143 let s = match &self {
144 PackageKind::UserCode => "__Main__",
145 PackageKind::AliasedPackage(alias) => alias,
146 PackageKind::StandardLibrary => "__Std__",
147 PackageKind::Core => "__Core__",
148 };
149 write!(f, "{s}")
150 }
151}
152
153/// Represents an immutable compilation state.
154#[derive(Debug)]
155struct Compilation {
156 /// Package store, containing the current package and all its dependencies.
157 package_store: PackageStore,
158 /// Current package id when provided.
159 current_package_id: Option<PackageId>,
160 /// Aliases for packages.
161 dependencies: FxHashMap<PackageId, Arc<str>>,
162}
163
164impl Compilation {
165 /// Creates a new `Compilation` by compiling standard library
166 /// and additional sources.
167 pub(crate) fn new(
168 additional_program: Option<(PackageStore, &Dependencies, SourceMap)>,
169 capabilities: Option<TargetCapabilityFlags>,
170 language_features: Option<LanguageFeatures>,
171 ) -> Self {
172 let actual_capabilities = capabilities.unwrap_or_default();
173 let actual_language_features = language_features.unwrap_or_default();
174
175 let mut current_package_id: Option<PackageId> = None;
176 let mut package_aliases: FxHashMap<PackageId, Arc<str>> = FxHashMap::default();
177
178 let package_store =
179 if let Some((mut package_store, dependencies, sources)) = additional_program {
180 let unit = compile(
181 &package_store,
182 dependencies,
183 sources,
184 actual_capabilities,
185 actual_language_features,
186 );
187 // We ignore errors here (unit.errors vector) and use whatever
188 // documentation we can produce. In future we may consider
189 // displaying the fact of error presence on documentation page.
190
191 for (package_id, package_alias) in dependencies {
192 if let Some(package_alias) = package_alias {
193 package_aliases.insert(*package_id, package_alias.clone());
194 }
195 }
196
197 current_package_id = Some(package_store.insert(unit));
198 package_store
199 } else {
200 let mut package_store = PackageStore::new(compile::core());
201 let std_unit = compile::std(&package_store, actual_capabilities);
202 package_store.insert(std_unit);
203 package_store
204 };
205
206 Self {
207 package_store,
208 current_package_id,
209 dependencies: package_aliases,
210 }
211 }
212}
213
214impl Lookup for Compilation {
215 fn get_ty(&self, _: ast::NodeId) -> Option<&ty::Ty> {
216 unimplemented!("Not needed for docs generation")
217 }
218
219 fn get_res(&self, _: ast::NodeId) -> Option<&resolve::Res> {
220 unimplemented!("Not needed for docs generation")
221 }
222
223 fn resolve_item_relative_to_user_package(
224 &self,
225 _: &hir::ItemId,
226 ) -> (&hir::Item, &hir::Package, hir::ItemId) {
227 unimplemented!("Not needed for docs generation")
228 }
229
230 /// Returns the hir `Item` node referred to by `res`.
231 /// `Res`s can resolve to external packages, and the references
232 /// are relative, so here we also need the
233 /// local `PackageId` that the `res` itself came from.
234 fn resolve_item_res(&self, res: &hir::Res) -> (&hir::Item, hir::ItemId) {
235 match res {
236 hir::Res::Item(item_id) => {
237 let (item, _, resolved_item_id) = self.resolve_item(item_id);
238 (item, resolved_item_id)
239 }
240 _ => panic!("expected to find item"),
241 }
242 }
243
244 /// Returns the hir `Item` node referred to by `item_id`.
245 /// `ItemId`s can refer to external packages, and the references
246 /// are relative, so here we also need the local `PackageId`
247 /// that the `ItemId` originates from.
248 fn resolve_item(&self, item_id: &hir::ItemId) -> (&hir::Item, &hir::Package, hir::ItemId) {
249 // If the `ItemId` contains a package id, use that.
250 // Lack of a package id means the item is in the
251 // same package as the one this `ItemId` reference
252 // came from. So use the local package id passed in.
253 let package = &self
254 .package_store
255 .get(item_id.package)
256 .expect("package should exist in store")
257 .package;
258 (
259 package
260 .items
261 .get(item_id.item)
262 .expect("item id should exist"),
263 package,
264 *item_id,
265 )
266 }
267}
268
269/// Determines the package kind for a given package in the compilation context
270fn determine_package_kind(package_id: PackageId, compilation: &Compilation) -> Option<PackageKind> {
271 let is_current_package = compilation.current_package_id == Some(package_id);
272
273 if package_id == PackageId::CORE {
274 // Core package is always included in the compilation.
275 Some(PackageKind::Core)
276 } else if package_id == 1.into() {
277 // Standard package is currently always included, but this isn't enforced by the compiler.
278 Some(PackageKind::StandardLibrary)
279 } else if is_current_package {
280 // This package could be user code if current package is specified.
281 Some(PackageKind::UserCode)
282 } else {
283 // This is a either a direct dependency of the user code or
284 // is not a package user can access (an indirect dependency).
285 compilation
286 .dependencies
287 .get(&package_id)
288 .map(|alias| PackageKind::AliasedPackage(alias.to_string()))
289 }
290}
291
292/// Processes all packages in a compilation and builds a table-of-contents structure
293fn build_toc_from_compilation(
294 compilation: &Compilation,
295 mut files: Option<&mut FilesWithMetadata>,
296) -> ToC {
297 let display = &CodeDisplay { compilation };
298
299 let mut toc: ToC = FxHashMap::default();
300
301 for (package_id, unit) in &compilation.package_store {
302 let Some(package_kind) = determine_package_kind(package_id, compilation) else {
303 continue;
304 };
305
306 let is_current_package = compilation.current_package_id == Some(package_id);
307 let package = &unit.package;
308
309 for (_, item) in &package.items {
310 if let Some((ns, metadata)) = generate_doc_for_item(
311 package,
312 package_kind.clone(),
313 is_current_package,
314 item,
315 display,
316 files.as_deref_mut().unwrap_or(&mut vec![]),
317 ) {
318 toc.entry(ns).or_default().push(metadata);
319 }
320 }
321 }
322
323 toc
324}
325
326/// Generates and returns documentation files for the standard library
327/// and additional sources (if specified.)
328#[must_use]
329pub fn generate_docs(
330 additional_sources: Option<(PackageStore, &Dependencies, SourceMap)>,
331 capabilities: Option<TargetCapabilityFlags>,
332 language_features: Option<LanguageFeatures>,
333) -> Files {
334 // Capabilities should default to all capabilities for documentation generation.
335 let capabilities = Some(capabilities.unwrap_or(TargetCapabilityFlags::all()));
336 let compilation = Compilation::new(additional_sources, capabilities, language_features);
337 let mut files: FilesWithMetadata = vec![];
338
339 let mut toc = build_toc_from_compilation(&compilation, Some(&mut files));
340
341 // Generate Overview files for each namespace
342 for (ns, items) in &mut toc {
343 generate_index_file(&mut files, ns, items);
344 }
345
346 generate_top_index(&mut files, &mut toc);
347
348 // We want to sort documentation files in a meaningful way.
349 // First, we want to put files for the current project, if it exists.
350 // Then we want to put explicit dependencies of the current project, if they exist.
351 // Then we want to add built-in std package. And finally built-in core package.
352 // Namespaces within packages should be sorted alphabetically and
353 // items with a namespace should be also sorted alphabetically with the index file appearing first.
354 // Also, items without any metadata (table of content) should come last.
355 files.sort_by_key(|file| {
356 let prefix = if file.0.ends_with("index.md") {
357 "0"
358 } else {
359 "1"
360 };
361 let name_key = format!("{}{}", prefix, file.1.name);
362 (file.1.package.clone(), file.1.namespace.clone(), name_key)
363 });
364
365 let mut result: Files = files
366 .into_iter()
367 .map(|(name, metadata, content)| (name, Rc::from(metadata.to_string().as_str()), content))
368 .collect();
369
370 generate_toc(&mut toc, &mut result);
371
372 result
373}
374
375fn generate_doc_for_item<'a>(
376 package: &'a Package,
377 package_kind: PackageKind,
378 include_internals: bool,
379 item: &'a Item,
380 display: &'a CodeDisplay,
381 files: &mut FilesWithMetadata,
382) -> Option<(Rc<str>, Rc<Metadata>)> {
383 let (true_package, true_item) = resolve_export(package, include_internals, item, display)?;
384
385 // Get namespace for item
386 let ns = get_namespace(package, item)?;
387
388 // Add file
389 let (metadata, content) = if matches!(item.kind, ItemKind::Export(_, _)) {
390 let true_ns = get_namespace(true_package, true_item)?;
391 generate_exported_file_content(
392 package_kind.clone(),
393 &ns,
394 item,
395 display,
396 &true_ns,
397 true_item,
398 )?
399 } else {
400 generate_file_content(package_kind, &ns, item, display)?
401 };
402 let file_name = Rc::from(format!("{ns}/{}.md", metadata.name).as_str());
403 let file_content = Rc::from(content.as_str());
404 let met = Rc::from(metadata);
405 files.push((file_name, met.clone(), file_content));
406
407 Some((ns.clone(), met))
408}
409
410fn generate_file_content(
411 package_kind: PackageKind,
412 ns: &Rc<str>,
413 item: &Item,
414 display: &CodeDisplay,
415) -> Option<(Metadata, String)> {
416 let metadata = get_metadata(package_kind, ns.clone(), item, display)?;
417
418 let doc = increase_header_level(&item.doc);
419 let title = &metadata.title;
420 let fqn = &metadata.fully_qualified_name();
421 let sig = &metadata.signature;
422
423 let content = format!(
424 "# {title}
425
426Fully qualified name: {fqn}
427
428```qsharp
429{sig}
430```
431"
432 );
433
434 let content = if doc.is_empty() {
435 content
436 } else {
437 format!("{content}\n{doc}\n")
438 };
439
440 Some((metadata, content))
441}
442
443#[allow(clippy::assigning_clones)]
444fn generate_exported_file_content(
445 package_kind: PackageKind,
446 ns: &Rc<str>,
447 item: &Item,
448 display: &CodeDisplay,
449 true_ns: &Rc<str>,
450 true_item: &Item,
451) -> Option<(Metadata, String)> {
452 let mut metadata = get_metadata(package_kind.clone(), ns.clone(), item, display)?;
453
454 let doc = increase_header_level(&item.doc);
455 let title = &metadata.title;
456 let fqn = &metadata.fully_qualified_name();
457
458 // Note: we are assuming the package kind does not change
459 let true_metadata = get_metadata(package_kind, true_ns.clone(), true_item, display)?;
460 let true_fqn = true_metadata.fully_qualified_name();
461
462 let summary = format!(
463 "This is an exported item. The actual definition is found here: [{true_fqn}](xref:Qdk.{true_fqn})"
464 );
465
466 metadata.summary = summary.clone();
467
468 let content = format!(
469 "# {title}
470
471Fully qualified name: {fqn}
472
473{summary}
474"
475 );
476
477 let content = if doc.is_empty() {
478 content
479 } else {
480 format!("{content}\n{doc}\n")
481 };
482
483 Some((metadata, content))
484}
485
486fn generate_index_file(files: &mut FilesWithMetadata, ns: &Rc<str>, items: &mut Vec<Rc<Metadata>>) {
487 if items.is_empty() {
488 return;
489 }
490
491 let short_name = if ns.starts_with("Microsoft.Quantum") {
492 ns.as_ref()
493 } else {
494 ns.split('.')
495 .next_back()
496 .expect("Namespaces should have at least one part.")
497 };
498
499 let package_kind = items[0].package.clone();
500 let metadata = Metadata {
501 uid: format!("Qdk.{ns}-toc"),
502 title: format!("{ns} namespace"),
503 kind: MetadataKind::TableOfContents,
504 package: package_kind,
505 namespace: ns.clone(),
506 name: "Overview".into(),
507 summary: format!("Table of contents for the Q# {short_name} namespace"),
508 signature: String::new(),
509 };
510
511 items.sort_by_key(|item| item.name.clone());
512 let content = items
513 .iter()
514 .map(|item| {
515 format!(
516 "| [{}](xref:Qdk.{}) | {} |",
517 item.name,
518 item.fully_qualified_name(),
519 item.summary.replace('|', "\\|")
520 )
521 })
522 .collect::<Vec<_>>()
523 .join("\n");
524
525 let content = format!(
526 "# {ns}
527
528The {ns} namespace contains the following items:
529
530| Name | Description |
531|------|-------------|
532{content}
533",
534 );
535
536 let rc_met = Rc::from(metadata);
537 items.insert(0, rc_met.clone());
538
539 let file_name = Rc::from(format!("{ns}/index.md").as_str());
540 let file_content = Rc::from(content.as_str());
541 files.push((file_name, rc_met, file_content));
542}
543
544fn generate_top_index(files: &mut FilesWithMetadata, toc: &mut ToC) {
545 let empty_ns: Rc<str> = Rc::from(String::new().as_str());
546 let metadata = Metadata {
547 uid: "Microsoft.Quantum.apiref-toc".to_string(),
548 title: "Q# standard libraries for the Microsoft Quantum Development Kit".to_string(),
549 kind: MetadataKind::TableOfContents,
550 package: PackageKind::StandardLibrary,
551 namespace: empty_ns.clone(),
552 name: "Overview".into(),
553 summary:
554 "Table of contents for the Q# standard libraries for Microsoft Quantum Development Kit"
555 .to_string(),
556 signature: String::new(),
557 };
558
559 let contents = table_of_contents();
560
561 files.push((Rc::from("index.md"), Rc::from(metadata), Rc::from(contents)));
562
563 toc.insert(empty_ns, vec![]);
564}
565
566/// Generates the Table of Contents file, toc.yml
567fn generate_toc(map: &mut ToC, files: &mut Files) {
568 let header = "
569# This file is automatically generated.
570# Please do not modify this file manually, or your changes will be lost when
571# documentation is rebuilt.";
572 let mut table = map
573 .iter_mut()
574 .map(|(namespace, items)| {
575 if namespace.is_empty() {
576 let content = "- items:
577 name: Overview
578 uid: Microsoft.Quantum.apiref-toc";
579 (namespace, content.to_string())
580 } else {
581 let items_str = items
582 .iter()
583 .map(|item| format!(" - {{name: {}, uid: {}}}", item.name, item.uid))
584 .collect::<Vec<String>>()
585 .join("\n");
586 let content =
587 format!("- items:\n{items_str}\n name: {namespace}\n uid: Qdk.{namespace}");
588 (namespace, content)
589 }
590 })
591 .collect::<Vec<_>>();
592
593 table.sort_unstable_by_key(|(n, _)| {
594 // Ensures that the Microsoft.Quantum.Unstable namespaces are listed last.
595 if n.starts_with("Microsoft.Quantum.Unstable") {
596 format!("1{n}")
597 } else {
598 format!("0{n}")
599 }
600 });
601 let table = table
602 .into_iter()
603 .map(|(_, c)| c)
604 .collect::<Vec<_>>()
605 .join("\n");
606 let content = format!("{header}\n{table}");
607 let content = content.as_str();
608
609 let file_name = Rc::from("toc.yml");
610 let file_metadata = Rc::from("");
611 let file_content = Rc::from(content);
612 files.push((file_name, file_metadata, file_content));
613}
614
615fn get_namespace(package: &Package, item: &Item) -> Option<Rc<str>> {
616 let local_id = item.parent?;
617 let parent = package
618 .items
619 .get(local_id)
620 .expect("Could not resolve parent item id");
621 let ItemKind::Namespace(name, _) = &parent.kind else {
622 return None;
623 };
624 if name.starts_with("QIR") {
625 None // We ignore "QIR" namespaces
626 } else {
627 let name = name.name();
628 if name.to_lowercase().starts_with("std.openqasm") {
629 None // We ignore openqasm namespaces
630 } else {
631 Some(name)
632 }
633 }
634}
635
636// Recursively resolves export items until it can find the root definition.
637// Returns the package and item of the root definition for an item.
638// If given the root definition, it will return the same item.
639fn resolve_export<'a>(
640 package: &'a Package,
641 include_internals: bool,
642 item: &'a Item,
643 display: &'a CodeDisplay,
644) -> Option<(&'a Package, &'a Item)> {
645 // Filter out items that are not visible or are namespaces
646 if !include_internals && (item.visibility == Visibility::Internal) {
647 return None;
648 }
649 if matches!(item.kind, ItemKind::Namespace(_, _)) {
650 return None;
651 }
652 if let ItemKind::Export(_, Res::Item(id)) = item.kind {
653 let (exported_item, exported_package, _) = display.compilation.resolve_item(&id);
654 return resolve_export(exported_package, include_internals, exported_item, display);
655 }
656
657 Some((package, item))
658}
659
660fn get_metadata(
661 package_kind: PackageKind,
662 ns: Rc<str>,
663 item: &Item,
664 display: &CodeDisplay,
665) -> Option<Metadata> {
666 let (name, signature, kind) = match &item.kind {
667 ItemKind::Callable(decl) => Some((
668 decl.name.name.clone(),
669 display.hir_callable_decl(decl).to_string(),
670 match &decl.kind {
671 CallableKind::Function => MetadataKind::Function,
672 CallableKind::Operation => MetadataKind::Operation,
673 },
674 )),
675 ItemKind::Ty(ident, udt) => Some((
676 ident.name.clone(),
677 display.hir_udt(udt).to_string(),
678 MetadataKind::Udt,
679 )),
680 ItemKind::Namespace(_, _) => None,
681 ItemKind::Export(name, _) => Some((
682 name.name.clone(),
683 // If we want to show docs for exports, we could do that here.
684 String::new(),
685 MetadataKind::Export,
686 )),
687 }?;
688
689 let summary = parse_doc_for_summary(&item.doc)
690 .replace("\r\n", " ")
691 .replace('\n', " ");
692
693 Some(Metadata {
694 uid: format!("Qdk.{ns}.{name}"),
695 title: format!("{name} {kind}"),
696 kind,
697 package: package_kind,
698 namespace: ns,
699 name,
700 summary,
701 signature,
702 })
703}
704
705/// Generates summary documentation organized by namespace.
706/// Returns a map of namespace -> metadata items for easier testing and manipulation.
707fn generate_summaries_map() -> BTreeMap<String, Vec<Rc<Metadata>>> {
708 let compilation = Compilation::new(None, None, None);
709
710 // Use the shared logic to build ToC structure
711 let toc = build_toc_from_compilation(&compilation, None);
712
713 // Convert ToC to BTreeMap, filtering out table of contents entries
714 let mut result = BTreeMap::new();
715
716 for (ns, items) in toc {
717 let mut summaries = Vec::new();
718
719 for item in items {
720 // Skip table of contents entries
721 if item.kind == MetadataKind::TableOfContents {
722 continue;
723 }
724
725 summaries.push(item);
726 }
727
728 if !summaries.is_empty() {
729 // Sort items within namespace
730 summaries.sort_by_key(|item| item.name.clone());
731 result.insert(ns.to_string(), summaries);
732 }
733 }
734
735 result
736}
737/// Converts a Metadata item to its markdown representation
738fn metadata_to_markdown(item: &Metadata) -> String {
739 let mut result = format!("## {}\n\n", item.name);
740 let _ = write!(result, "```qsharp\n{}\n```\n\n", item.signature);
741 if !item.summary.is_empty() {
742 let _ = write!(result, "{}\n\n", item.summary);
743 }
744 result
745}
746
747/// Generates markdown summary for a single namespace
748fn generate_namespace_summary(namespace: &str, items: &[Rc<Metadata>]) -> String {
749 let mut result = format!("# {namespace}\n\n");
750
751 for item in items {
752 result.push_str(&metadata_to_markdown(item));
753 }
754
755 result
756}
757
758/// Generates summary documentation organized by namespace.
759/// Returns a single markdown string with namespace headers and minimal item documentation
760/// containing just function signatures and summaries for efficient consumption by language models.
761#[must_use]
762pub fn generate_summaries() -> String {
763 let summaries_map = generate_summaries_map();
764
765 // Generate markdown output organized by namespace
766 let mut result = String::new();
767
768 // Sort namespaces for consistent output (BTreeMap already sorts keys)
769 for (ns, items) in &summaries_map {
770 result.push_str(&generate_namespace_summary(ns, items));
771 }
772
773 result
774}
775