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 _ => 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 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(¶meter.ty);
464 result.push(BuiltinMember {
465 signature: format!("{}: {ty}", parameter.name),
466 description: describe(¶meter.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 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}