1use 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#[derive(Clone, Copy, Debug)]
80pub struct MemberDoc {
81 pub name: &'static str,
83 pub docs: &'static str,
85}
86
87#[derive(Clone, Copy, Debug)]
89pub struct ExampleSpec {
90 pub title: &'static str,
92 pub description: &'static str,
94 pub source: &'static str,
96 pub output: &'static [&'static str],
98 pub ticks: u32,
100 pub presentation: &'static str,
102}
103
104#[derive(Clone, Copy, Debug)]
107pub struct BuiltinDefinition {
108 pub name: &'static str,
110 pub category: &'static str,
112 pub kind: &'static str,
114 pub declaration: &'static str,
116 pub docs: &'static str,
118 pub status: &'static str,
120 pub members: &'static [MemberDoc],
122 pub examples: &'static [ExampleSpec],
124 pub rust_path: &'static str,
126 pub representation: &'static str,
128}
129
130#[derive(Clone, Debug, Serialize)]
132pub struct BuiltinMember {
133 pub name: String,
135 pub kind: &'static str,
137 pub signature: String,
139 pub ty: String,
141 pub default: Option<String>,
143 pub description: String,
145}
146
147#[derive(Clone, Debug, Serialize)]
149pub struct BuiltinExample {
150 pub title: &'static str,
152 pub description: &'static str,
154 pub source: &'static str,
156 pub output: &'static [&'static str],
158 pub ticks: u32,
160 pub presentation: &'static str,
162}
163
164#[derive(Clone, Debug, Serialize)]
166pub struct Builtin {
167 pub name: &'static str,
169 pub category: &'static str,
171 pub kind: &'static str,
173 pub signature: String,
175 pub declaration: &'static str,
177 pub example: &'static str,
179 pub example_output: &'static [&'static str],
181 pub example_ticks: u32,
183 pub notes: Vec<String>,
185 pub summary: String,
187 pub description: Vec<String>,
189 pub members: Vec<BuiltinMember>,
191 pub examples: Vec<BuiltinExample>,
193 pub documentation: String,
195 pub status: &'static str,
197 pub rust_documentation: &'static str,
199 pub representation: &'static str,
201}
202
203pub 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
288fn 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 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(¶meter.ty);
488 result.push(BuiltinMember {
489 signature: format!("{}: {ty}", parameter.name),
490 description: describe(¶meter.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 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}