rune/compile/ir/
compiler.rs

1use core::mem::take;
2
3use crate::alloc::prelude::*;
4use crate::alloc::{try_format, Box, Vec};
5use crate::ast::{self, Span, Spanned};
6use crate::compile::ir;
7use crate::compile::{self, ErrorKind, WithSpan};
8use crate::hir;
9use crate::query::Query;
10use crate::runtime::{Bytes, Value};
11use crate::SourceId;
12
13use tracing::instrument_ast;
14
15/// A c that compiles AST into Rune IR.
16pub(crate) struct Ctxt<'a, 'arena> {
17    /// The source id of the source.
18    #[cfg_attr(not(feature = "tracing"), allow(unused))]
19    pub(crate) source_id: SourceId,
20    /// Query associated with the compiler.
21    pub(crate) q: Query<'a, 'arena>,
22}
23
24#[instrument_ast]
25pub(crate) fn expr(hir: &hir::Expr<'_>, c: &mut Ctxt<'_, '_>) -> compile::Result<ir::Ir> {
26    let span = hir.span();
27
28    Ok(match hir.kind {
29        hir::ExprKind::Vec(hir) => ir::Ir::new(span, expr_vec(span, c, hir)?),
30        hir::ExprKind::Tuple(hir) => expr_tuple(c, span, hir)?,
31        hir::ExprKind::Object(hir) => ir::Ir::new(span, expr_object(span, c, hir)?),
32        hir::ExprKind::Group(hir) => expr(hir, c)?,
33        hir::ExprKind::Binary(hir) => expr_binary(span, c, hir)?,
34        hir::ExprKind::Assign(hir) => expr_assign(span, c, hir)?,
35        hir::ExprKind::Call(hir) => ir::Ir::new(span, expr_call(span, c, hir)?),
36        hir::ExprKind::If(hir) => ir::Ir::new(span, expr_if(span, c, hir)?),
37        hir::ExprKind::Loop(hir) => ir::Ir::new(span, expr_loop(span, c, hir)?),
38        hir::ExprKind::Lit(hir) => lit(c, span, hir)?,
39        hir::ExprKind::Block(hir) => ir::Ir::new(span, block(hir, c)?),
40        hir::ExprKind::FieldAccess(..) => ir::Ir::new(span, ir_target(hir)?),
41        hir::ExprKind::Break(hir) => ir::Ir::new(span, ir::IrBreak::compile_ast(span, c, hir)?),
42        hir::ExprKind::Template(template) => {
43            let ir_template = builtin_template(template, c)?;
44            ir::Ir::new(hir.span(), ir_template)
45        }
46        hir::ExprKind::Const(hash) => {
47            let Some(value) = c.q.get_const_value(hash) else {
48                return Err(compile::Error::msg(
49                    hir,
50                    try_format!("Missing constant for hash {hash}"),
51                ));
52            };
53
54            let value = value.to_value_with(c.q.context).with_span(span)?;
55            ir::Ir::new(span, value)
56        }
57        hir::ExprKind::Variable(name) => {
58            return Ok(ir::Ir::new(span, name));
59        }
60        _ => {
61            return Err(compile::Error::msg(
62                hir,
63                "Expression kind not supported yet in constant contexts",
64            ))
65        }
66    })
67}
68
69/// Resolve an ir target from an expression.
70fn ir_target(expr: &hir::Expr<'_>) -> compile::Result<ir::IrTarget> {
71    match expr.kind {
72        hir::ExprKind::Variable(name) => {
73            return Ok(ir::IrTarget {
74                span: expr.span(),
75                kind: ir::IrTargetKind::Name(name),
76            });
77        }
78        hir::ExprKind::FieldAccess(expr_field_access) => {
79            let target = ir_target(&expr_field_access.expr)?;
80
81            match expr_field_access.expr_field {
82                hir::ExprField::Ident(name) => {
83                    return Ok(ir::IrTarget {
84                        span: expr.span(),
85                        kind: ir::IrTargetKind::Field(Box::try_new(target)?, name.try_into()?),
86                    });
87                }
88                hir::ExprField::Index(index) => {
89                    return Ok(ir::IrTarget {
90                        span: expr.span(),
91                        kind: ir::IrTargetKind::Index(Box::try_new(target)?, index),
92                    });
93                }
94                _ => {
95                    return Err(compile::Error::new(expr, ErrorKind::BadFieldAccess));
96                }
97            }
98        }
99        _ => (),
100    }
101
102    Err(compile::Error::msg(expr, "Not supported as a target"))
103}
104
105#[instrument_ast]
106fn expr_assign(
107    span: Span,
108    c: &mut Ctxt<'_, '_>,
109    hir: &hir::ExprAssign<'_>,
110) -> compile::Result<ir::Ir> {
111    let target = ir_target(&hir.lhs)?;
112
113    Ok(ir::Ir::new(
114        span,
115        ir::IrSet {
116            span,
117            target,
118            value: Box::try_new(expr(&hir.rhs, c)?)?,
119        },
120    ))
121}
122
123#[instrument_ast]
124fn expr_call(
125    span: Span,
126    c: &mut Ctxt<'_, '_>,
127    hir: &hir::ExprCall<'_>,
128) -> compile::Result<ir::IrCall> {
129    let mut args = Vec::try_with_capacity(hir.args.len())?;
130
131    for e in hir.args {
132        args.try_push(expr(e, c)?)?;
133    }
134
135    if let hir::Call::ConstFn { id, .. } = hir.call {
136        return Ok(ir::IrCall { span, id, args });
137    }
138
139    Err(compile::Error::msg(
140        span,
141        "Call not supported in constant contexts",
142    ))
143}
144
145#[instrument_ast]
146fn expr_binary(
147    span: Span,
148    c: &mut Ctxt<'_, '_>,
149    hir: &hir::ExprBinary<'_>,
150) -> compile::Result<ir::Ir> {
151    if hir.op.is_assign() {
152        let op = match hir.op {
153            ast::BinOp::AddAssign(..) => ir::IrAssignOp::Add,
154            ast::BinOp::SubAssign(..) => ir::IrAssignOp::Sub,
155            ast::BinOp::MulAssign(..) => ir::IrAssignOp::Mul,
156            ast::BinOp::DivAssign(..) => ir::IrAssignOp::Div,
157            ast::BinOp::ShlAssign(..) => ir::IrAssignOp::Shl,
158            ast::BinOp::ShrAssign(..) => ir::IrAssignOp::Shr,
159            _ => return Err(compile::Error::msg(hir.op, "op not supported yet")),
160        };
161
162        let target = ir_target(&hir.lhs)?;
163
164        return Ok(ir::Ir::new(
165            span,
166            ir::IrAssign {
167                span,
168                target,
169                value: Box::try_new(expr(&hir.rhs, c)?)?,
170                op,
171            },
172        ));
173    }
174
175    let lhs = expr(&hir.lhs, c)?;
176    let rhs = expr(&hir.rhs, c)?;
177
178    let op = match hir.op {
179        ast::BinOp::Add(..) => ir::IrBinaryOp::Add,
180        ast::BinOp::Sub(..) => ir::IrBinaryOp::Sub,
181        ast::BinOp::Mul(..) => ir::IrBinaryOp::Mul,
182        ast::BinOp::Div(..) => ir::IrBinaryOp::Div,
183        ast::BinOp::Shl(..) => ir::IrBinaryOp::Shl,
184        ast::BinOp::Shr(..) => ir::IrBinaryOp::Shr,
185        ast::BinOp::Lt(..) => ir::IrBinaryOp::Lt,
186        ast::BinOp::Lte(..) => ir::IrBinaryOp::Lte,
187        ast::BinOp::Eq(..) => ir::IrBinaryOp::Eq,
188        ast::BinOp::Gt(..) => ir::IrBinaryOp::Gt,
189        ast::BinOp::Gte(..) => ir::IrBinaryOp::Gte,
190        _ => return Err(compile::Error::msg(hir.op, "op not supported yet")),
191    };
192
193    Ok(ir::Ir::new(
194        span,
195        ir::IrBinary {
196            span,
197            op,
198            lhs: Box::try_new(lhs)?,
199            rhs: Box::try_new(rhs)?,
200        },
201    ))
202}
203
204#[instrument_ast(span = span)]
205fn lit(_c: &mut Ctxt<'_, '_>, span: Span, hir: hir::Lit<'_>) -> compile::Result<ir::Ir> {
206    Ok(match hir {
207        hir::Lit::Bool(boolean) => {
208            let value = Value::from(boolean);
209            ir::Ir::new(span, value)
210        }
211        hir::Lit::Unsigned(n) => {
212            let value = Value::from(n);
213            ir::Ir::new(span, value)
214        }
215        hir::Lit::Signed(n) => {
216            let value = Value::from(n);
217            ir::Ir::new(span, value)
218        }
219        hir::Lit::Float(n) => {
220            let value = Value::from(n);
221            ir::Ir::new(span, value)
222        }
223        hir::Lit::Char(c) => {
224            let value = Value::from(c);
225            ir::Ir::new(span, value)
226        }
227        hir::Lit::Str(string) => {
228            let string = string.try_to_owned().with_span(span)?;
229            let value = Value::try_from(string).with_span(span)?;
230            ir::Ir::new(span, value)
231        }
232        hir::Lit::ByteStr(byte_str) => {
233            let value = Bytes::from_vec(Vec::try_from(byte_str).with_span(span)?);
234            let value = Value::try_from(value).with_span(span)?;
235            ir::Ir::new(span, value)
236        }
237    })
238}
239
240#[instrument_ast(span = span)]
241fn expr_tuple(c: &mut Ctxt<'_, '_>, span: Span, hir: &hir::ExprSeq<'_>) -> compile::Result<ir::Ir> {
242    if hir.items.is_empty() {
243        let value = Value::unit();
244        return Ok(ir::Ir::new(span, value));
245    }
246
247    let mut items = Vec::new();
248
249    for e in hir.items {
250        items.try_push(expr(e, c)?)?;
251    }
252
253    Ok(ir::Ir::new(
254        span,
255        ir::Tuple {
256            span,
257            items: items.try_into_boxed_slice()?,
258        },
259    ))
260}
261
262#[instrument_ast]
263fn expr_vec(
264    span: Span,
265    c: &mut Ctxt<'_, '_>,
266    hir: &hir::ExprSeq<'_>,
267) -> compile::Result<ir::IrVec> {
268    let mut items = Vec::new();
269
270    for e in hir.items {
271        items.try_push(expr(e, c)?)?;
272    }
273
274    Ok(ir::IrVec {
275        span,
276        items: items.try_into_boxed_slice()?,
277    })
278}
279
280#[instrument_ast]
281fn expr_object(
282    span: Span,
283    c: &mut Ctxt<'_, '_>,
284    hir: &hir::ExprObject<'_>,
285) -> compile::Result<ir::IrObject> {
286    let mut assignments = Vec::new();
287
288    for assign in hir.assignments {
289        let (_, key) = assign.key;
290        let ir = expr(&assign.assign, c)?;
291        assignments.try_push((key.try_into()?, ir))?
292    }
293
294    Ok(ir::IrObject {
295        span,
296        assignments: assignments.try_into_boxed_slice()?,
297    })
298}
299
300#[instrument_ast]
301pub(crate) fn block(hir: &hir::Block<'_>, c: &mut Ctxt<'_, '_>) -> compile::Result<ir::IrScope> {
302    let span = hir.span();
303
304    let mut last = None::<(&hir::Expr<'_>, bool)>;
305    let mut instructions = Vec::new();
306
307    for stmt in hir.statements {
308        match stmt {
309            hir::Stmt::Local(l) => {
310                if let Some((e, _)) = take(&mut last) {
311                    instructions.try_push(expr(e, c)?)?;
312                }
313
314                instructions.try_push(local(l, c)?)?;
315            }
316            hir::Stmt::Expr(e) => {
317                instructions.try_push(expr(e, c)?)?;
318            }
319        };
320    }
321
322    let last = if let Some(e) = hir.value {
323        Some(Box::try_new(expr(e, c)?)?)
324    } else {
325        None
326    };
327
328    Ok(ir::IrScope {
329        span,
330        instructions,
331        last,
332    })
333}
334
335#[instrument_ast]
336fn builtin_template(
337    template: &hir::BuiltInTemplate,
338    c: &mut Ctxt<'_, '_>,
339) -> compile::Result<ir::IrTemplate> {
340    let span = template.span;
341    let mut components = Vec::new();
342
343    for e in template.exprs {
344        if let hir::ExprKind::Lit(hir::Lit::Str(s)) = e.kind {
345            components.try_push(ir::IrTemplateComponent::String(s.try_into()?))?;
346            continue;
347        }
348
349        let ir = expr(e, c)?;
350        components.try_push(ir::IrTemplateComponent::Ir(ir))?;
351    }
352
353    Ok(ir::IrTemplate { span, components })
354}
355
356#[instrument_ast]
357fn local(hir: &hir::Local<'_>, c: &mut Ctxt<'_, '_>) -> compile::Result<ir::Ir> {
358    let span = hir.span();
359
360    let name = match hir.pat.pat.kind {
361        hir::PatKind::Ignore => {
362            return expr(&hir.expr, c);
363        }
364        hir::PatKind::Path(&hir::PatPathKind::Ident(name)) => name,
365        _ => {
366            return Err(compile::Error::msg(span, "not supported yet"));
367        }
368    };
369
370    Ok(ir::Ir::new(
371        span,
372        ir::IrDecl {
373            span,
374            name,
375            value: Box::try_new(expr(&hir.expr, c)?)?,
376        },
377    ))
378}
379
380#[instrument_ast]
381fn condition(hir: &hir::Condition<'_>, c: &mut Ctxt<'_, '_>) -> compile::Result<ir::IrCondition> {
382    match hir {
383        hir::Condition::Expr(e) => Ok(ir::IrCondition::Ir(expr(e, c)?)),
384        hir::Condition::ExprLet(hir) => {
385            let pat = ir::IrPat::compile_ast(&hir.pat.pat)?;
386            let ir = expr(&hir.expr, c)?;
387
388            Ok(ir::IrCondition::Let(ir::IrLet {
389                span: hir.span(),
390                pat,
391                ir,
392            }))
393        }
394    }
395}
396
397#[instrument_ast]
398fn expr_if(
399    span: Span,
400    c: &mut Ctxt<'_, '_>,
401    hir: &hir::Conditional<'_>,
402) -> compile::Result<ir::IrBranches> {
403    let mut branches = Vec::new();
404
405    for hir in hir.branches {
406        let cond = condition(hir.condition, c)?;
407        let ir = block(&hir.block, c)?;
408        branches.try_push((cond, ir))?;
409    }
410
411    let default_branch = match hir.fallback {
412        Some(hir) => Some(block(hir, c)?),
413        None => None,
414    };
415
416    Ok(ir::IrBranches {
417        span,
418        branches,
419        default_branch,
420    })
421}
422
423#[instrument_ast]
424fn expr_loop(
425    span: Span,
426    c: &mut Ctxt<'_, '_>,
427    hir: &hir::ExprLoop<'_>,
428) -> compile::Result<ir::IrLoop> {
429    Ok(ir::IrLoop {
430        span,
431        label: hir.label.map(TryInto::try_into).transpose()?,
432        condition: match hir.condition {
433            Some(hir) => Some(Box::try_new(condition(hir, c)?)?),
434            None => None,
435        },
436        body: block(&hir.body, c)?,
437    })
438}