Skip to main content

konjure_lang/
catalog.rs

1//! Rust-owned standard-library declarations and executable reference documentation.
2//!
3//! [`prelude`] generates the compiler prelude and its docs from the same declarations.
4//! [`functions`] owns global native signatures and [`crate::methods`] owns receiver
5//! methods used by both the checker and the reference. Primitive descriptions come
6//! directly from [`crate::Value`] doc comments.
7//! The website renders [`catalog`]; it does not maintain a parallel language schema.
8
9use crate::{Type, ast::Item, parse, program::type_text};
10use serde::Serialize;
11
12pub mod functions;
13mod numbers;
14pub mod prelude;
15pub mod values;
16pub use prelude::PRELUDE;
17
18const ALGEBRAIC_DEFINITIONS: &[BuiltinDefinition] = &[
19    BuiltinDefinition {
20        name: "Opt",
21        category: "Values",
22        kind: "enum",
23        declaration: "enum Opt[T] { Some(T), None }",
24        docs: "An optional value that records presence explicitly.\n\nMatch both Some and None before using an optional result.",
25        status: "runtime",
26        members: &[],
27        examples: &[ExampleSpec {
28            title: "Match an optional value",
29            description: "Both cases make absence explicit.",
30            source: "let value: Opt[f64] = Some(4);\nmatch value { Some(number) => print(number), None => print(\"none\"), };",
31            output: &["4"],
32            ticks: 0,
33            presentation: "snippet",
34        }],
35        rust_path: "/sdk/rust/konjure_lang/value/struct.EnumValue.html",
36        representation: "Value::Enum with the concrete Opt[T] type and checked case payload.",
37    },
38    BuiltinDefinition {
39        name: "Res",
40        category: "Values",
41        kind: "enum",
42        declaration: "enum Res[T, E] { Ok(T), Err(E) }",
43        docs: "A recoverable success or error result.\n\nUse ? to propagate Err to a compatible Res-returning boundary, or match both cases locally.",
44        status: "runtime",
45        members: &[],
46        examples: &[ExampleSpec {
47            title: "Match a result",
48            description: "A result carries either its value or its error payload.",
49            source: "let value: Res[f64, DataError] = Ok(4);\nmatch value { Ok(number) => print(number), Err(error) => print(error), };",
50            output: &["4"],
51            ticks: 0,
52            presentation: "snippet",
53        }],
54        rust_path: "/sdk/rust/konjure_lang/value/struct.EnumValue.html",
55        representation: "Value::Enum with the concrete Res[T, E] type and checked case payload.",
56    },
57    BuiltinDefinition {
58        name: "DataError",
59        category: "Values",
60        kind: "enum",
61        declaration: "enum DataError { ... }",
62        docs: "A stable recoverable error emitted by checked numeric, byte, and tensor operations.\n\nEach case name and description comes from the Rust SDK data-error registry.",
63        status: "runtime",
64        members: &[],
65        examples: &[ExampleSpec {
66            title: "Handle invalid UTF-8",
67            description: "Decoding reports InvalidUtf8 instead of replacing malformed bytes.",
68            source: "match bytes([255:u8]).decode() { Ok(value) => print(value), Err(error) => print(error), };",
69            output: &["InvalidUtf8"],
70            ticks: 0,
71            presentation: "snippet",
72        }],
73        rust_path: "/sdk/rust/konjure_sdk/data/enum.DataError.html",
74        representation: "konjure_sdk::data::DataError, carried in Res error payloads.",
75    },
76];
77
78/// Documentation attached to a field, parameter, method or function result.
79#[derive(Clone, Copy, Debug)]
80pub struct MemberDoc {
81    /// Declaration member name; `return` describes a function result.
82    pub name: &'static str,
83    /// Meaning, units and constraints. The type and default come from the declaration.
84    pub docs: &'static str,
85}
86
87/// A source-owned example and its independent expected result.
88#[derive(Clone, Copy, Debug)]
89pub struct ExampleSpec {
90    /// Short task demonstrated by the program.
91    pub title: &'static str,
92    /// What to observe or change when running the program.
93    pub description: &'static str,
94    /// Complete Konjure source with no hidden setup or external modules.
95    pub source: &'static str,
96    /// Exact log lines after initialization and the requested simulation ticks.
97    pub output: &'static [&'static str],
98    /// Fixed simulation ticks before checking the output.
99    pub ticks: u32,
100    /// Website presentation: `snippet`, `console`, or `scene`.
101    pub presentation: &'static str,
102}
103
104/// One documented Rust registration. Macros emit this alongside the declaration
105/// consumed by the compiler; doc comments feed both rustdoc and the website.
106#[derive(Clone, Copy, Debug)]
107pub struct BuiltinDefinition {
108    /// Public language name.
109    pub name: &'static str,
110    /// Purpose-oriented group in the reference index.
111    pub category: &'static str,
112    /// Declaration kind: type, enum, trait, func, constant, context or module.
113    pub kind: &'static str,
114    /// Authoritative language declaration, including all supported overloads.
115    pub declaration: &'static str,
116    /// Rust doc comments. The first paragraph is the summary; later paragraphs explain behavior.
117    pub docs: &'static str,
118    /// Implemented runtime or host support, distinct from a declaration.
119    pub status: &'static str,
120    /// Member descriptions; signatures and defaults are derived from the declaration.
121    pub members: &'static [MemberDoc],
122    /// Runnable programs verified on native Rust and WASM.
123    pub examples: &'static [ExampleSpec],
124    /// Site-local rustdoc link to the corresponding Rust definition.
125    pub rust_path: &'static str,
126    /// How the language concept is represented or executed by Rust.
127    pub representation: &'static str,
128}
129
130/// A compiler-derived field, method, parameter or result with source documentation.
131#[derive(Clone, Debug, Serialize)]
132pub struct BuiltinMember {
133    /// Field or parameter name; `return` for results.
134    pub name: String,
135    /// `field`, `method`, `parameter` or `return`.
136    pub kind: &'static str,
137    /// Language spelling, preserving declaration order.
138    pub signature: String,
139    /// Type spelling for fields, parameters and results.
140    pub ty: String,
141    /// Source expression used when a field is omitted.
142    pub default: Option<String>,
143    /// Behavior and units from the Rust registration's member docs.
144    pub description: String,
145}
146
147/// An executable, documented example exported to a host UI.
148#[derive(Clone, Debug, Serialize)]
149pub struct BuiltinExample {
150    /// Short purpose, suitable for an example heading.
151    pub title: &'static str,
152    /// An observable result or edit to try.
153    pub description: &'static str,
154    /// Standalone language program.
155    pub source: &'static str,
156    /// Expected output checked independently of generated artifacts.
157    pub output: &'static [&'static str],
158    /// Simulation ticks required for the expected output.
159    pub ticks: u32,
160    /// `snippet`, `console`, or `scene`.
161    pub presentation: &'static str,
162}
163
164/// One standard-library entry shared by the website and language services.
165#[derive(Clone, Debug, Serialize)]
166pub struct Builtin {
167    /// Public language name.
168    pub name: &'static str,
169    /// Purpose-oriented documentation group.
170    pub category: &'static str,
171    /// Declaration category used by the editor.
172    pub kind: &'static str,
173    /// Complete signature derived from the authoritative declaration.
174    pub signature: String,
175    /// Source declaration or language spelling of an intrinsic.
176    pub declaration: &'static str,
177    /// First example, retained for existing catalog consumers. See [`Self::examples`].
178    pub example: &'static str,
179    /// Expected output of [`Self::example`].
180    pub example_output: &'static [&'static str],
181    /// Ticks for [`Self::example`].
182    pub example_ticks: u32,
183    /// Behavioral paragraphs from Rust doc comments, retained for catalog consumers.
184    pub notes: Vec<String>,
185    /// First paragraph of the definition's Rust docs.
186    pub summary: String,
187    /// Detailed behavior, units, ownership and failure conditions.
188    pub description: Vec<String>,
189    /// Documented fields, methods, parameters and results.
190    pub members: Vec<BuiltinMember>,
191    /// All checked programs for this builtin.
192    pub examples: Vec<BuiltinExample>,
193    /// Project-root-relative reference page.
194    pub documentation: String,
195    /// Actual runtime or adapter support.
196    pub status: &'static str,
197    /// Link to the Rust source definition's rustdoc.
198    pub rust_documentation: &'static str,
199    /// Rust storage or execution model, without implying a direct struct alias.
200    pub representation: &'static str,
201}
202
203/// Return the standard library from Rust declarations, doc comments and checked examples.
204/// This metadata is also exported unchanged by the WebAssembly SDK.
205pub fn catalog() -> Vec<Builtin> {
206    let mut entries: Vec<_> = prelude::definitions()
207        .into_iter()
208        .chain(functions::definitions())
209        .chain(values::definitions())
210        .chain(numbers::definitions())
211        .chain(ALGEBRAIC_DEFINITIONS.iter().copied())
212        .map(|definition| {
213            let mut paragraphs = paragraphs(definition.docs).into_iter();
214            let summary = paragraphs.next().expect("a builtin needs a summary");
215            let description: Vec<_> = paragraphs.collect();
216            let first = definition
217                .examples
218                .first()
219                .expect("a builtin needs an example");
220            Builtin {
221                name: definition.name,
222                category: definition.category,
223                kind: definition.kind,
224                signature: definition
225                    .declaration
226                    .split_whitespace()
227                    .collect::<Vec<_>>()
228                    .join(" "),
229                declaration: definition.declaration,
230                example: first.source,
231                example_output: first.output,
232                example_ticks: first.ticks,
233                notes: description.clone(),
234                summary,
235                description,
236                members: members(&definition),
237                examples: definition
238                    .examples
239                    .iter()
240                    .map(|example| BuiltinExample {
241                        title: example.title,
242                        description: example.description,
243                        source: example.source,
244                        output: example.output,
245                        ticks: example.ticks,
246                        presentation: example.presentation,
247                    })
248                    .collect(),
249                documentation: documentation_path(definition.name, definition.kind),
250                status: definition.status,
251                rust_documentation: definition.rust_path,
252                representation: definition.representation,
253            }
254        })
255        .collect();
256    for entry in &mut entries {
257        entry.members.extend(native_method_members(entry.name));
258        entry.members.extend(native_conversion_member(entry.name));
259    }
260    entries
261}
262
263fn native_conversion_member(name: &str) -> Vec<BuiltinMember> {
264    let signature = match name {
265        "Tensor" => "convert[U]() -> Res[Tensor[U], DataError]",
266        "Number" => "convert[U]() -> Res[U, DataError]",
267        _ if crate::data::dtype(&Type::named(name)).is_some() => {
268            "convert[U]() -> Res[U, DataError]"
269        }
270        _ => return vec![],
271    };
272    vec![BuiltinMember {
273        name: "convert".into(),
274        kind: "method",
275        signature: signature.into(),
276        ty: signature
277            .rsplit_once(" -> ")
278            .expect("method signature has result")
279            .1
280            .into(),
281        default: None,
282        description:
283            "Converts to explicit numeric dtype U or returns DataError when exact conversion fails."
284                .into(),
285    }]
286}
287
288/// Native methods shown beside the type which owns their receiver. The checked
289/// function signatures come from [`crate::methods`], rather than a duplicated
290/// documentation declaration.
291fn native_method_members(name: &str) -> Vec<BuiltinMember> {
292    let receiver = match name {
293        "Number" => Type::named("f64"),
294        "Str" | "String" => Type::named("Str"),
295        "Bin" => Type::named("Bin"),
296        "List" => Type::List(Box::new(Type::named("T"))),
297        "Tensor" => Type::Tensor(Box::new(Type::named("T"))),
298        _ => return vec![],
299    };
300    [
301        "len",
302        "utf8",
303        "decode",
304        "append",
305        "get",
306        "shape",
307        "reshape",
308        "transpose",
309        "sum",
310        "matmul",
311        "sin",
312        "cos",
313        "sqrt",
314        "abs",
315        "floor",
316        "ceil",
317        "min",
318        "max",
319        "clamp",
320        "pow",
321    ]
322    .into_iter()
323    .flat_map(|method| {
324        crate::methods::signatures(&receiver, method, None)
325            .into_iter()
326            .map(move |signature| (method, signature))
327    })
328    .map(|(method, signature)| {
329        let Type::Function {
330            parameters,
331            returns,
332        } = signature
333        else {
334            unreachable!("native method signatures are functions");
335        };
336        let parameter_text = parameters
337            .iter()
338            .map(type_text)
339            .collect::<Vec<_>>()
340            .join(", ");
341        BuiltinMember {
342            name: method.into(),
343            kind: "method",
344            signature: format!("{method}({parameter_text}) -> {}", type_text(&returns)),
345            ty: type_text(&returns),
346            default: None,
347            description: native_method_description(method).into(),
348        }
349    })
350    .collect()
351}
352
353fn native_method_description(name: &str) -> &'static str {
354    match name {
355        "len" => "Returns the receiver's element, byte, or Unicode scalar count.",
356        "utf8" => "Encodes text as immutable UTF-8 bytes.",
357        "decode" => "Decodes UTF-8 bytes or returns DataError for malformed input.",
358        "append" => "Returns a new list with one value appended.",
359        "get" => "Returns Some(value) for a valid index and None otherwise.",
360        "shape" => "Returns one nonnegative extent for each tensor axis.",
361        "reshape" => "Returns a reshaped tensor or DataError when dimensions do not fit.",
362        "transpose" => "Returns a reordered tensor or DataError for an invalid axis permutation.",
363        "sum" => "Returns the tensor sum or DataError when the operation cannot be represented.",
364        "matmul" => "Returns a matrix product or DataError when tensor shapes are incompatible.",
365        "sin" | "cos" | "sqrt" | "abs" | "floor" | "ceil" | "min" | "max" | "clamp" | "pow" => {
366            "Returns the numeric result or DataError when the operation cannot be represented."
367        }
368        _ => unreachable!("native method docs match the signature table"),
369    }
370}
371
372fn paragraphs(docs: &str) -> Vec<String> {
373    docs.lines()
374        .map(str::trim)
375        .collect::<Vec<_>>()
376        .join("\n")
377        .split("\n\n")
378        .map(|paragraph| paragraph.split_whitespace().collect::<Vec<_>>().join(" "))
379        .filter(|paragraph| !paragraph.is_empty())
380        .collect()
381}
382
383fn documentation_path(name: &str, kind: &str) -> String {
384    let slug = if name == "entity" && kind == "context" {
385        "entity-context".into()
386    } else if name == "tensor" && kind == "func" {
387        "tensor-function".into()
388    } else {
389        name.to_lowercase()
390    };
391    format!("/docs/language/builtins/{slug}/")
392}
393
394fn members(definition: &BuiltinDefinition) -> Vec<BuiltinMember> {
395    if definition.kind == "enum" {
396        return enum_members(definition.name);
397    }
398    let describe = |name: &str| {
399        definition
400            .members
401            .iter()
402            .find(|member| member.name == name)
403            .map(|member| paragraphs(member.docs).join(" "))
404            .unwrap_or_default()
405    };
406    if !matches!(definition.kind, "type" | "trait" | "func") {
407        return vec![];
408    }
409    if definition.kind == "type" && !definition.declaration.trim_start().starts_with("type ") {
410        return vec![];
411    }
412    // Intrinsic generic selectors aren't authorable generic functions. Erase only the
413    // selector on each function name so the ordinary parser can read parameter types.
414    let source = if definition.kind == "func" {
415        let methods = definition
416            .declaration
417            .lines()
418            .filter(|line| !line.trim().is_empty())
419            .map(|line| {
420                let line = line.trim().trim_end_matches(';');
421                let parameters = line.find('(').expect("function signature has parameters");
422                let head = line[..parameters].split('[').next().unwrap();
423                format!("{head}{};", &line[parameters..])
424            })
425            .collect::<Vec<_>>()
426            .join("\n");
427        format!("trait Signature {{ {methods} }}")
428    } else {
429        definition.declaration.to_owned()
430    };
431    let items = parse(&source, "builtin").expect("builtin declaration must parse");
432    let mut result = vec![];
433    for item in items {
434        match item {
435            Item::Class(class) => {
436                for field in class.fields {
437                    let ty = type_text(&field.ty);
438                    result.push(BuiltinMember {
439                        signature: format!("{}: {ty}", field.name),
440                        description: describe(&field.name),
441                        name: field.name,
442                        kind: "field",
443                        ty,
444                        default: field
445                            .default
446                            .map(|value| source[value.span.start..value.span.end].to_owned()),
447                    });
448                }
449            }
450            Item::Trait(contract) => {
451                for method in contract.methods {
452                    if definition.kind == "trait" {
453                        result.push(BuiltinMember {
454                            name: method.name.clone(),
455                            kind: "method",
456                            signature: crate::program::function_text(&method),
457                            ty: String::new(),
458                            default: None,
459                            description: describe(&method.name),
460                        });
461                    } else {
462                        for parameter in method.parameters {
463                            let ty = type_text(&parameter.ty);
464                            result.push(BuiltinMember {
465                                signature: format!("{}: {ty}", parameter.name),
466                                description: describe(&parameter.name),
467                                name: parameter.name,
468                                kind: "parameter",
469                                ty,
470                                default: None,
471                            });
472                        }
473                        let ty = type_text(&method.returns);
474                        result.push(BuiltinMember {
475                            name: "return".into(),
476                            kind: "return",
477                            signature: format!("-> {ty}"),
478                            ty,
479                            default: None,
480                            description: describe("return"),
481                        });
482                    }
483                }
484            }
485            _ => unreachable!("catalog declarations are types or trait signatures"),
486        }
487    }
488    // Shared parameters/results of overloads are documented once.
489    let mut seen = std::collections::BTreeSet::new();
490    result.retain(|member| seen.insert((member.name.clone(), member.signature.clone())));
491    result
492}
493
494fn enum_members(name: &str) -> Vec<BuiltinMember> {
495    let case = |name: &str, signature: String, description: String| BuiltinMember {
496        name: name.into(),
497        kind: "variant",
498        ty: String::new(),
499        signature,
500        default: None,
501        description,
502    };
503    match name {
504        "Opt" => vec![
505            case(
506                "Some",
507                "Some(T)".into(),
508                "Contains a present value of T.".into(),
509            ),
510            case(
511                "None",
512                "None".into(),
513                "Records that no value is present.".into(),
514            ),
515        ],
516        "Res" => vec![
517            case(
518                "Ok",
519                "Ok(T)".into(),
520                "Contains a successful value of T.".into(),
521            ),
522            case(
523                "Err",
524                "Err(E)".into(),
525                "Contains a recoverable error of E.".into(),
526            ),
527        ],
528        "DataError" => konjure_sdk::data::DataError::VARIANTS
529            .iter()
530            .map(|descriptor| {
531                case(
532                    descriptor.name,
533                    descriptor.name.into(),
534                    descriptor.docs.into(),
535                )
536            })
537            .collect(),
538        _ => vec![],
539    }
540}