Skip to main content

doge_compiler/
builtins.rs

1//! The compiler-side view of the always-in-scope builtins (`len`, `str`, `int`,
2//! `float`, `range`): the runtime function each call wires to, the argument
3//! counts it accepts, and how its call is emitted. Mirrors the runtime `builtins`
4//! (like [`crate::stdlib`] mirrors the runtime `stdlib`) — a builtin here must
5//! have a matching function there. This one table is the single source the
6//! checker (name-in-scope, name-clash) and codegen (call emission, value
7//! dispatcher) both read.
8
9/// How a builtin call is emitted and dispatched.
10#[derive(Clone, Copy)]
11pub enum BuiltinShape {
12    /// Returns `Result` at runtime, so the emitted call threads `?`/labeled-break
13    /// and the value-dispatcher arm forwards the `Result` as-is.
14    Fallible,
15    /// Cannot fail: the emitted call is used directly, and the value-dispatcher
16    /// arm wraps it in `Ok`.
17    Infallible,
18    /// `range`: one argument (`0..n`) or two (`a..b`), each a fallible `range`
19    /// call with the runtime's two-argument shape.
20    Range,
21    /// `gib`: no argument (read a line) or one (a prompt printed first), a fallible
22    /// call that maps to the runtime's `Option<&Value>` prompt shape.
23    Prompt,
24}
25
26/// One builtin: its name, the `doge-runtime` function a call emits, the argument
27/// counts it accepts, its emission shape, and the call-shape hint for arity
28/// diagnostics.
29pub struct BuiltinFn {
30    pub name: &'static str,
31    pub runtime_fn: &'static str,
32    pub arities: &'static [usize],
33    pub shape: BuiltinShape,
34    pub hint: &'static str,
35}
36
37impl BuiltinFn {
38    /// Whether a call with `argc` arguments has a valid arity for this builtin.
39    pub fn accepts(&self, argc: usize) -> bool {
40        self.arities.contains(&argc)
41    }
42
43    /// The accepted-argument phrase for an arity diagnostic, e.g. `1 argument` or
44    /// `1 or 2 arguments`.
45    pub fn arity_phrase(&self) -> String {
46        let counts = self
47            .arities
48            .iter()
49            .map(|n| n.to_string())
50            .collect::<Vec<_>>()
51            .join(" or ");
52        let noun = if self.arities == [1] {
53            "argument"
54        } else {
55            "arguments"
56        };
57        format!("{counts} {noun}")
58    }
59}
60
61pub const BUILTINS: &[BuiltinFn] = &[
62    BuiltinFn {
63        name: "len",
64        runtime_fn: "len",
65        arities: &[1],
66        shape: BuiltinShape::Fallible,
67        hint: "len(thing)",
68    },
69    BuiltinFn {
70        name: "str",
71        runtime_fn: "to_str",
72        arities: &[1],
73        shape: BuiltinShape::Infallible,
74        hint: "str(thing)",
75    },
76    BuiltinFn {
77        name: "int",
78        runtime_fn: "to_int",
79        arities: &[1],
80        shape: BuiltinShape::Fallible,
81        hint: "int(thing)",
82    },
83    BuiltinFn {
84        name: "float",
85        runtime_fn: "to_float",
86        arities: &[1],
87        shape: BuiltinShape::Fallible,
88        hint: "float(thing)",
89    },
90    BuiltinFn {
91        name: "range",
92        runtime_fn: "range",
93        arities: &[1, 2],
94        shape: BuiltinShape::Range,
95        hint: "range(n) or range(a, b)",
96    },
97    BuiltinFn {
98        name: "gib",
99        runtime_fn: "gib",
100        arities: &[0, 1],
101        shape: BuiltinShape::Prompt,
102        hint: "gib() or gib(\"prompt\")",
103    },
104];
105
106/// The builtin named `name`, if there is one.
107pub fn builtin(name: &str) -> Option<&'static BuiltinFn> {
108    BUILTINS.iter().find(|b| b.name == name)
109}
110
111/// Whether `name` is a builtin — always in scope, never redefinable.
112pub fn is_builtin(name: &str) -> bool {
113    builtin(name).is_some()
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn known_names_resolve_and_unknown_do_not() {
122        assert!(is_builtin("len"));
123        assert!(is_builtin("range"));
124        assert!(!is_builtin("nope"));
125        assert!(builtin("str").is_some());
126    }
127
128    #[test]
129    fn arity_phrase_matches_diagnostic_wording() {
130        assert_eq!(builtin("len").unwrap().arity_phrase(), "1 argument");
131        assert_eq!(builtin("range").unwrap().arity_phrase(), "1 or 2 arguments");
132    }
133
134    #[test]
135    fn accepts_reflects_declared_arities() {
136        let range = builtin("range").unwrap();
137        assert!(range.accepts(1));
138        assert!(range.accepts(2));
139        assert!(!range.accepts(3));
140        assert!(!range.accepts(0));
141
142        let len = builtin("len").unwrap();
143        assert!(len.accepts(1));
144        assert!(!len.accepts(2));
145    }
146}