Skip to main content

doge_compiler/
builtins.rs

1//! The compiler-side view of the always-in-scope builtins (`len`, `str`, `int`,
2//! `float`, `bytes`, `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: "bytes",
92        runtime_fn: "to_bytes",
93        arities: &[1],
94        shape: BuiltinShape::Fallible,
95        hint: "bytes(thing)",
96    },
97    BuiltinFn {
98        name: "dec",
99        runtime_fn: "to_decimal",
100        arities: &[1],
101        shape: BuiltinShape::Fallible,
102        hint: "dec(thing)",
103    },
104    BuiltinFn {
105        name: "range",
106        runtime_fn: "range",
107        arities: &[1, 2],
108        shape: BuiltinShape::Range,
109        hint: "range(n) or range(a, b)",
110    },
111    BuiltinFn {
112        name: "gib",
113        runtime_fn: "gib",
114        arities: &[0, 1],
115        shape: BuiltinShape::Prompt,
116        hint: "gib() or gib(\"prompt\")",
117    },
118];
119
120/// The builtin named `name`, if there is one.
121pub fn builtin(name: &str) -> Option<&'static BuiltinFn> {
122    BUILTINS.iter().find(|b| b.name == name)
123}
124
125/// Whether `name` is a builtin — always in scope, never redefinable.
126pub fn is_builtin(name: &str) -> bool {
127    builtin(name).is_some()
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn known_names_resolve_and_unknown_do_not() {
136        assert!(is_builtin("len"));
137        assert!(is_builtin("range"));
138        assert!(!is_builtin("nope"));
139        assert!(builtin("str").is_some());
140    }
141
142    #[test]
143    fn arity_phrase_matches_diagnostic_wording() {
144        assert_eq!(builtin("len").unwrap().arity_phrase(), "1 argument");
145        assert_eq!(builtin("range").unwrap().arity_phrase(), "1 or 2 arguments");
146    }
147
148    #[test]
149    fn accepts_reflects_declared_arities() {
150        let range = builtin("range").unwrap();
151        assert!(range.accepts(1));
152        assert!(range.accepts(2));
153        assert!(!range.accepts(3));
154        assert!(!range.accepts(0));
155
156        let len = builtin("len").unwrap();
157        assert!(len.accepts(1));
158        assert!(!len.accepts(2));
159    }
160}