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        "Mesh" => Type::named("Mesh"),
299        _ => return vec![],
300    };
301    [
302        "len",
303        "utf8",
304        "decode",
305        "append",
306        "get",
307        "shape",
308        "reshape",
309        "transpose",
310        "sum",
311        "matmul",
312        "sin",
313        "cos",
314        "sqrt",
315        "abs",
316        "floor",
317        "ceil",
318        "min",
319        "max",
320        "clamp",
321        "pow",
322        "sphere",
323        "torus",
324        "cone",
325        "grid",
326        "lathe",
327        "suzanne",
328        "translated",
329        "scaled",
330        "rotated",
331        "mapped",
332        "decode_json",
333    ]
334    .into_iter()
335    .flat_map(|method| {
336        crate::methods::signatures(&receiver, method, None)
337            .into_iter()
338            .map(move |signature| (method, signature))
339    })
340    .map(|(method, signature)| {
341        let Type::Function {
342            parameters,
343            returns,
344        } = signature
345        else {
346            unreachable!("native method signatures are functions");
347        };
348        let parameter_text = parameters
349            .iter()
350            .map(type_text)
351            .collect::<Vec<_>>()
352            .join(", ");
353        BuiltinMember {
354            name: method.into(),
355            kind: "method",
356            signature: format!("{method}({parameter_text}) -> {}", type_text(&returns)),
357            ty: type_text(&returns),
358            default: None,
359            description: native_method_description(method).into(),
360        }
361    })
362    .collect()
363}
364
365fn native_method_description(name: &str) -> &'static str {
366    match name {
367        "len" => "Returns the receiver's element, byte, or Unicode scalar count.",
368        "utf8" => "Encodes text as immutable UTF-8 bytes.",
369        "decode" => "Decodes UTF-8 bytes or returns DataError for malformed input.",
370        "append" => "Returns a new list with one value appended.",
371        "get" => "Returns Some(value) for a valid index and None otherwise.",
372        "shape" => "Returns one nonnegative extent for each tensor axis.",
373        "reshape" => "Returns a reshaped tensor or DataError when dimensions do not fit.",
374        "transpose" => "Returns a reordered tensor or DataError for an invalid axis permutation.",
375        "sum" => "Returns the tensor sum or DataError when the operation cannot be represented.",
376        "matmul" => "Returns a matrix product or DataError when tensor shapes are incompatible.",
377        "sin" | "cos" | "sqrt" | "abs" | "floor" | "ceil" | "min" | "max" | "clamp" | "pow" => {
378            "Returns the numeric result or DataError when the operation cannot be represented."
379        }
380        "sphere" | "torus" | "cone" | "grid" | "lathe" | "suzanne" => {
381            "Returns a bounded procedural Mesh; dimensions, integer resolution, and topology are validated before buffers are retained."
382        }
383        "translated" | "scaled" | "rotated" => {
384            "Returns a transformed Mesh with finite coordinates and correctly transformed normals; negative scale reverses winding."
385        }
386        "mapped" => {
387            "Calls the checked mapper once per vertex and returns a Mesh with derived smooth normals; mapper errors reject the whole result."
388        }
389        "decode_json" => {
390            "Decodes bounded immutable Mesh JSON string chunks without paths, fetching, or code execution; malformed or oversized payloads return DataError."
391        }
392        _ => unreachable!("native method docs match the signature table"),
393    }
394}
395
396fn paragraphs(docs: &str) -> Vec<String> {
397    docs.lines()
398        .map(str::trim)
399        .collect::<Vec<_>>()
400        .join("\n")
401        .split("\n\n")
402        .map(|paragraph| paragraph.split_whitespace().collect::<Vec<_>>().join(" "))
403        .filter(|paragraph| !paragraph.is_empty())
404        .collect()
405}
406
407fn documentation_path(name: &str, kind: &str) -> String {
408    let slug = if name == "entity" && kind == "context" {
409        "entity-context".into()
410    } else if name == "tensor" && kind == "func" {
411        "tensor-function".into()
412    } else {
413        name.to_lowercase()
414    };
415    format!("/docs/language/builtins/{slug}/")
416}
417
418fn members(definition: &BuiltinDefinition) -> Vec<BuiltinMember> {
419    if definition.kind == "enum" {
420        return enum_members(definition.name);
421    }
422    let describe = |name: &str| {
423        definition
424            .members
425            .iter()
426            .find(|member| member.name == name)
427            .map(|member| paragraphs(member.docs).join(" "))
428            .unwrap_or_default()
429    };
430    if !matches!(definition.kind, "type" | "trait" | "func") {
431        return vec![];
432    }
433    if definition.kind == "type" && !definition.declaration.trim_start().starts_with("type ") {
434        return vec![];
435    }
436    // Intrinsic generic selectors aren't authorable generic functions. Erase only the
437    // selector on each function name so the ordinary parser can read parameter types.
438    let source = if definition.kind == "func" {
439        let methods = definition
440            .declaration
441            .lines()
442            .filter(|line| !line.trim().is_empty())
443            .map(|line| {
444                let line = line.trim().trim_end_matches(';');
445                let parameters = line.find('(').expect("function signature has parameters");
446                let head = line[..parameters].split('[').next().unwrap();
447                format!("{head}{};", &line[parameters..])
448            })
449            .collect::<Vec<_>>()
450            .join("\n");
451        format!("trait Signature {{ {methods} }}")
452    } else {
453        definition.declaration.to_owned()
454    };
455    let items = parse(&source, "builtin").expect("builtin declaration must parse");
456    let mut result = vec![];
457    for item in items {
458        match item {
459            Item::Class(class) => {
460                for field in class.fields {
461                    let ty = type_text(&field.ty);
462                    result.push(BuiltinMember {
463                        signature: format!("{}: {ty}", field.name),
464                        description: describe(&field.name),
465                        name: field.name,
466                        kind: "field",
467                        ty,
468                        default: field
469                            .default
470                            .map(|value| source[value.span.start..value.span.end].to_owned()),
471                    });
472                }
473            }
474            Item::Trait(contract) => {
475                for method in contract.methods {
476                    if definition.kind == "trait" {
477                        result.push(BuiltinMember {
478                            name: method.name.clone(),
479                            kind: "method",
480                            signature: crate::program::function_text(&method),
481                            ty: String::new(),
482                            default: None,
483                            description: describe(&method.name),
484                        });
485                    } else {
486                        for parameter in method.parameters {
487                            let ty = type_text(&parameter.ty);
488                            result.push(BuiltinMember {
489                                signature: format!("{}: {ty}", parameter.name),
490                                description: describe(&parameter.name),
491                                name: parameter.name,
492                                kind: "parameter",
493                                ty,
494                                default: None,
495                            });
496                        }
497                        let ty = type_text(&method.returns);
498                        result.push(BuiltinMember {
499                            name: "return".into(),
500                            kind: "return",
501                            signature: format!("-> {ty}"),
502                            ty,
503                            default: None,
504                            description: describe("return"),
505                        });
506                    }
507                }
508            }
509            _ => unreachable!("catalog declarations are types or trait signatures"),
510        }
511    }
512    // Shared parameters/results of overloads are documented once.
513    let mut seen = std::collections::BTreeSet::new();
514    result.retain(|member| seen.insert((member.name.clone(), member.signature.clone())));
515    result
516}
517
518fn enum_members(name: &str) -> Vec<BuiltinMember> {
519    let case = |name: &str, signature: String, description: String| BuiltinMember {
520        name: name.into(),
521        kind: "variant",
522        ty: String::new(),
523        signature,
524        default: None,
525        description,
526    };
527    match name {
528        "Opt" => vec![
529            case(
530                "Some",
531                "Some(T)".into(),
532                "Contains a present value of T.".into(),
533            ),
534            case(
535                "None",
536                "None".into(),
537                "Records that no value is present.".into(),
538            ),
539        ],
540        "Res" => vec![
541            case(
542                "Ok",
543                "Ok(T)".into(),
544                "Contains a successful value of T.".into(),
545            ),
546            case(
547                "Err",
548                "Err(E)".into(),
549                "Contains a recoverable error of E.".into(),
550            ),
551        ],
552        "DataError" => konjure_sdk::data::DataError::VARIANTS
553            .iter()
554            .map(|descriptor| {
555                case(
556                    descriptor.name,
557                    descriptor.name.into(),
558                    descriptor.docs.into(),
559                )
560            })
561            .collect(),
562        _ => vec![],
563    }
564}