Skip to main content

candela/compiler/
expr.rs

1use super::type_system::TypeExpr;
2pub use crate::rt::Span;
3use smol_strc::SmolStr;
4use smol_strc::ToSmolStr;
5use std::{hint::unreachable_unchecked, rc::Rc};
6
7/// Separator between a type name and a method name in a mangled method symbol.
8///
9/// `impl` methods lower to per-type-unique free functions named `Type#method`.
10/// `#` is not a legal character in a candela identifier (which is
11/// `[a-zA-Z_][a-zA-Z0-9_]*`) and is never produced by the lexer, so a mangled
12/// name can never collide with a user-written free function or with a method of
13/// another type: `Point#len` and `Str#len` are distinct symbols, and both are
14/// distinct from a free `fn len`.
15pub const METHOD_SEP: char = '#';
16
17/// Builds the mangled free-function symbol an `impl Type { fn method ... }`
18/// lowers to.
19///
20/// Used by the parser when lowering method declarations and by the
21/// compiler/type-checker when resolving a `recv.method(...)` call site.
22#[must_use]
23pub fn mangle_method(type_name: &str, method_name: &str) -> SmolStr {
24    format_args!("{type_name}{METHOD_SEP}{method_name}").to_smolstr()
25}
26
27#[derive(Debug, Clone, PartialEq)]
28pub enum Expr {
29    Float(f64),
30    Int(i32),
31    Bool(bool),
32    Null,
33    String(SmolStr),
34    Var(SmolStr, Span),
35    /// Array(contents, [entire_array, elem_spans...])
36    Array(Box<[Self]>, Box<[Span]>),
37    /// Map(key-value pairs, span)
38    Map(Box<[(Self, Span, Self, Span)]>, Span),
39    /// Struct(name, fields, span)
40    Struct(Box<[SmolStr]>, Box<[(SmolStr, Self, Span, Span)]>, Span),
41    /// StructDeclare(name, fields, span)
42    StructDeclare(SmolStr, Box<[(SmolStr, TypeExpr, Span)]>, Span),
43    /// EnumDeclare(name, variants: [(variant_name, payload_types, name_span)], span)
44    EnumDeclare(SmolStr, Box<[(SmolStr, Box<[TypeExpr]>, Span)]>, Span),
45    /// NamespacedRef(path, span)
46    ///
47    /// A bare namespaced identifier (`Color::Red`) with no call parentheses or
48    /// struct braces. The only such form candela has is a nullary enum-variant
49    /// construction; the compiler resolves it against the enum registry.
50    NamespacedRef(Box<[SmolStr]>, Span),
51    /// Match(scrutinee, arms: [(pattern_expr, body)], wildcard_body, span)
52    ///
53    /// The arm patterns are parsed as ordinary expressions; the compiler picks
54    /// the lowering by the scrutinee's static type. For an enum scrutinee each
55    /// pattern is a variant pattern (`Circle(r)` binds the payload); otherwise
56    /// each pattern is an equality test against the scrutinee.
57    Match(
58        Box<Self>,
59        Box<[(Self, Box<[Self]>)]>,
60        Option<Box<[Self]>>,
61        Span,
62    ),
63    /// GetStructField(struct_expr, field, struct_span, field_span, value_span)
64    GetStructField(Box<Self>, SmolStr, Span, Span),
65    /// SetStructField(struct_expr, field, new_expr, struct_span, field_span, value_span)
66    SetStructField(Box<Self>, SmolStr, Box<Self>, Span, Span, Span),
67    /// VarDeclare(name, value),
68    VarDeclare(SmolStr, Box<Self>),
69    /// VarDeclare(name, value, start, end)
70    VarAssign(SmolStr, Box<Self>, Span),
71    /// Condition(condition, code (contains else_if_blocks and potentially else_block), start, end)
72    Condition(Box<Self>, Box<[Self]>, Span),
73    /// InlineCondition - expression-form if/else, always produces a value, must have an else branch
74    InlineCondition(Box<Self>, Box<[Self]>, Span),
75    ElseIfBlock(Box<Self>, Box<[Self]>),
76    ElseBlock(Box<[Self]>),
77
78    /// AnonymousFunction(args, code, span)
79    AnonymousFunction(Box<[SmolStr]>, Box<[Self]>, Span),
80    // AnonymousFunction(Box<[(SmolStr, SmolStr)]>, SmolStr, Box<[Self]>, Span),
81    WhileBlock(Box<Self>, Box<[Self]>),
82    /// FunctionCall(args, (optional namespace + name), start, end, (arg_start,arg_end))
83    FunctionCall(Box<[Self]>, Box<[SmolStr]>, Span, Box<[Span]>),
84    /// ObjFunctionCall(obj, args, namespace, obj_span, fn_span, arg_markers)
85    ObjFunctionCall(
86        // Will be removed soon.
87        Box<Self>,
88        Box<[Self]>,
89        Box<[SmolStr]>,
90        // obj_span
91        Span,
92        // fn_span
93        Span,
94        Box<[Span]>,
95    ),
96    /// FunctionDecl(name, args, code, name_span, return_type)
97    ///
98    /// `return_type` carries the optional `-> Type` annotation together with the
99    /// span of the annotation itself, which is what a mismatch between the
100    /// declared and the returned type is reported against.
101    FunctionDecl(
102        SmolStr,
103        Box<[(SmolStr, Option<TypeExpr>)]>,
104        Rc<[Self]>,
105        Span,
106        Option<(TypeExpr, Span)>,
107    ),
108
109    ReturnVal(Box<Option<Self>>),
110
111    ArrayGetIndex(Box<Self>, Box<Self>, Span),
112    /// ArrayGetSlice(array, range_start, range_end, span)
113    ArrayGetSlice(Box<Self>, Box<Self>, Box<Self>, Span),
114    ArrayModify(Box<Self>, Box<Self>, Box<Self>, Span, Span),
115
116    /// ForLoop(loop_var_name, loop_array+code, obj_markers)
117    ForLoop(SmolStr, Box<Self>, Box<[Self]>, Span),
118    /// IntForLoop(loop_var_name, first_elem, final_elem, code)
119    IntForLoop(SmolStr, Box<Self>, Box<Self>, Box<[Self]>, Span, Span),
120    /// ImportDylib(lib_path, [(fn_name, fn_args, fn_return_type, fn_name_span)], (start, end))
121    ImportDylib(
122        SmolStr,
123        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
124        Span,
125    ),
126
127    /// HostBlock(namespace, [(fn_name, fn_args, fn_return_type, fn_name_span)], (start, end))
128    ///
129    /// Declares a host namespace whose functions are backed by Rust closures
130    /// registered on the embedding [`crate::Engine`]. Mirrors [`Self::ImportDylib`]
131    /// but dispatches to a registered closure instead of a C symbol.
132    HostBlock(
133        SmolStr,
134        Box<[(SmolStr, Box<[TypeExpr]>, TypeExpr, Span)]>,
135        Span,
136    ),
137
138    /// ImportFile(path, alias, is_logical, (start, end))
139    ///
140    /// `is_logical` marks a library import (`import "std/string";`, an
141    /// extensionless path): the resolver maps it straight to the shipped
142    /// library directory and never looks source-relative. A `.cdl` file import
143    /// (`import "./local.cdl";`) has `is_logical` false and resolves
144    /// source-relative first. `alias` is `Some` for `import ... as name;`,
145    /// which binds the module under `name::`; a bare import (`None`) merges
146    /// the module's symbols into the importing file's scope.
147    ImportFile(SmolStr, Option<SmolStr>, bool, Span),
148
149    Break,
150    Continue,
151
152    EvalBlock(Box<[Self]>),
153    LoopBlock(Box<[Self]>),
154
155    /// TryCatchBlock(try_code, err_var, catch_code)
156    TryCatchBlock(Box<[Self]>, SmolStr, Box<[Self]>),
157
158    Mul(Box<Self>, Box<Self>, Span, Span),
159    Div(Box<Self>, Box<Self>, Span, Span),
160    Add(Box<Self>, Box<Self>, Span, Span),
161    Sub(Box<Self>, Box<Self>, Span, Span),
162    Mod(Box<Self>, Box<Self>, Span, Span),
163    Pow(Box<Self>, Box<Self>, Span, Span),
164    Eq(Box<Self>, Box<Self>),
165    NotEq(Box<Self>, Box<Self>),
166    Sup(Box<Self>, Box<Self>, Span, Span),
167    SupEq(Box<Self>, Box<Self>, Span, Span),
168    Inf(Box<Self>, Box<Self>, Span, Span),
169    InfEq(Box<Self>, Box<Self>, Span, Span),
170    BoolAnd(Box<Self>, Box<Self>, Span, Span),
171    BoolOr(Box<Self>, Box<Self>, Span, Span),
172    BoolNeg(Box<Self>, Span, Span),
173    Neg(Box<Self>, Span, Span),
174}
175
176#[cold]
177#[inline(never)]
178#[must_use]
179pub const fn symbol_of_expr(expr: &Expr) -> &'static str {
180    match expr {
181        Expr::Mul(_, _, _, _) => "*",
182        Expr::Div(_, _, _, _) => "/",
183        Expr::Add(_, _, _, _) => "+",
184        Expr::Sub(_, _, _, _) | Expr::Neg(_, _, _) => "-",
185        Expr::Mod(_, _, _, _) => "%",
186        Expr::Pow(_, _, _, _) => "^",
187        Expr::Eq(_, _) => "==",
188        Expr::NotEq(_, _) => "!=",
189        Expr::Sup(_, _, _, _) => ">",
190        Expr::SupEq(_, _, _, _) => ">=",
191        Expr::Inf(_, _, _, _) => "<",
192        Expr::InfEq(_, _, _, _) => "<=",
193        Expr::BoolAnd(_, _, _, _) => "&&",
194        Expr::BoolOr(_, _, _, _) => "||",
195        _ => unsafe { unreachable_unchecked() },
196    }
197}
198
199#[must_use]
200pub fn code_modifies_variable(var_name: &SmolStr, code: &[Expr]) -> bool {
201    code.iter().any(|expr| match expr {
202        Expr::VarAssign(n, _, _) => n == var_name,
203        Expr::Condition(_, body, _)
204        | Expr::WhileBlock(_, body)
205        | Expr::EvalBlock(body)
206        | Expr::LoopBlock(body)
207        | Expr::InlineCondition(_, body, _)
208        | Expr::ElseIfBlock(_, body)
209        | Expr::ElseBlock(body)
210        | Expr::ForLoop(_, _, body, _)
211        | Expr::IntForLoop(_, _, _, body, _, _) => code_modifies_variable(var_name, body),
212        Expr::Match(_, arms, wildcard, _) => {
213            arms.iter()
214                .any(|(_, body)| code_modifies_variable(var_name, body))
215                || wildcard
216                    .as_ref()
217                    .is_some_and(|body| code_modifies_variable(var_name, body))
218        }
219        _ => false,
220    })
221}
222
223#[must_use]
224pub fn var_assign(target: Expr, value: Expr, expr_span: Span, value_span: Span) -> Expr {
225    if let Expr::Var(n, s) = target {
226        Expr::VarAssign(n, Box::from(value), s)
227    } else if let Expr::ArrayGetIndex(base, idx, _) = target {
228        Expr::ArrayModify(base, idx, Box::from(value), expr_span, value_span)
229    } else if let Expr::GetStructField(obj, field, obj_span, field_span) = target {
230        Expr::SetStructField(
231            obj,
232            field,
233            Box::from(value),
234            obj_span,
235            field_span,
236            value_span,
237        )
238    } else {
239        unsafe { unreachable_unchecked() }
240    }
241}