libjuno 0.1.2

Library for JunoLang
use inkwell::{
    types::{ AsTypeRef, BasicTypeEnum },
    values::{ ArrayValue, AsValueRef, BasicValueEnum },
};

use crate::metair::*;

use super::*;

impl<'ctx> LLVMBackend<'ctx> {
    pub fn lower_expr(&mut self, expr: &MetaExpr) -> Result<BasicValueEnum<'ctx>, LLVMError> {
        match &expr.kind {
            MetaExprKind::Unary { op, expr } => { self.lower_unary(op, expr) }

            MetaExprKind::Call { target, args } => {
                self.lower_call(target, args)?.ok_or_else(||
                    LLVMError::Message("void function used as expression".into())
                )
            }

            MetaExprKind::String(id) => self.lower_string(*id),

            MetaExprKind::Array(inner) => {
                let mut items = Vec::new();

                for expr in inner {
                    items.push(self.lower_expr(expr)?);
                }

                let (elem_ty, expected_size) = match &expr.ty {
                    MetaType::Array { elem, size } => { (self.lower_type(elem)?, *size as usize) }

                    _ => {
                        return Err(LLVMError::Message("array literal has non-array type".into()));
                    }
                };

                while items.len() < expected_size {
                    items.push(elem_ty.const_zero());
                }

                if items.len() != expected_size {
                    return Err(LLVMError::Message("array literal size mismatch".into()));
                }

                let raw: Vec<_> = items
                    .iter()
                    .map(|v| v.as_value_ref())
                    .collect();

                Ok(
                    BasicValueEnum::ArrayValue(unsafe {
                        ArrayValue::new_raw_const_array(elem_ty.as_type_ref(), &raw)
                    })
                )
            }

            MetaExprKind::Binary { op, lhs, rhs } => { self.lower_binary(op, lhs, rhs) }

            MetaExprKind::Const(MetaConst::Int(value)) => {
                match self.lower_type(&expr.ty)? {
                    BasicTypeEnum::IntType(i) => { Ok(i.const_int(*value as u64, true).into()) }

                    _ => Err(LLVMError::Message("integer constant has non-integer type".into())),
                }
            }

            MetaExprKind::Const(MetaConst::Bool(value)) => {
                match self.lower_type(&expr.ty)? {
                    BasicTypeEnum::IntType(i) => { Ok(i.const_int(*value as u64, false).into()) }

                    _ => Err(LLVMError::Message("bool constant has non-bool type".into())),
                }
            }

            MetaExprKind::Const(MetaConst::Char(value)) => {
                match self.lower_type(&expr.ty)? {
                    BasicTypeEnum::IntType(i) => { Ok(i.const_int(*value as u64, false).into()) }

                    _ => Err(LLVMError::Message("char constant has non-char type".into())),
                }
            }

            MetaExprKind::Var(id) => {
                let var = self.get_variable(*id)?;
                Ok(
                    self.builder
                        .build_load(var.ty, var.ptr, &self.program.symbol_table[*id as usize])
                        .map_err(|e| LLVMError::Message(e.to_string()))?
                )
            }

            MetaExprKind::Void => Err(LLVMError::Message("void expression used as value".into())),

            other => Err(LLVMError::Message(format!("expression not implemented: {:#?}", other))),
        }
    }

    fn lower_unary(
        &mut self,
        op: &MetaUnOp,
        expr: &MetaExpr
    ) -> Result<BasicValueEnum<'ctx>, LLVMError> {
        match op {
            MetaUnOp::Neg => {
                let value = self.lower_expr(expr)?.into_int_value();

                Ok(
                    self.builder
                        .build_int_neg(value, "negtmp")
                        .map_err(|e| LLVMError::Message(e.to_string()))?
                        .into()
                )
            }

            MetaUnOp::Not => {
                let value = self.lower_expr(expr)?.into_int_value();

                Ok(
                    self.builder
                        .build_not(value, "nottmp")
                        .map_err(|e| LLVMError::Message(e.to_string()))?
                        .into()
                )
            }

            MetaUnOp::Ref =>
                match &expr.kind {
                    MetaExprKind::Var(id) => Ok(self.get_variable(*id)?.ptr.into()),

                    _ => Err(LLVMError::Message("reference requires a variable".into())),
                }

            MetaUnOp::Deref => {
                let ptr = self.lower_expr(expr)?.into_pointer_value();

                let pointee = match &expr.ty {
                    MetaType::Pointer(inner) | MetaType::Reference(inner) => inner,
                    _ => {
                        return Err(LLVMError::Message("cannot dereference non-pointer".into()));
                    }
                };

                let llvm_ty = self.lower_type(pointee)?;

                Ok(
                    self.builder
                        .build_load(llvm_ty, ptr, "deref")
                        .map_err(|e| LLVMError::Message(e.to_string()))?
                )
            }
        }
    }

    fn lower_string(&mut self, id: StringId) -> Result<BasicValueEnum<'ctx>, LLVMError> {
        let string = self.program.string_table
            .get(id as usize)
            .ok_or_else(|| { LLVMError::Message(format!("unknown string id {}", id)) })?;

        let ptr = self.builder
            .build_global_string_ptr(string, &format!("str.{}", id))
            .map_err(|e| LLVMError::Message(e.to_string()))?;

        Ok(ptr.as_pointer_value().into())
    }

    pub fn lower_call(
        &mut self,
        target: &[SymbolId],
        args: &[MetaArg]
    ) -> Result<Option<BasicValueEnum<'ctx>>, LLVMError> {
        let function = self.get_function(target)?;

        let mut llvm_args = Vec::new();

        for arg in args {
            let value = match arg {
                MetaArg::Pos(expr) => self.lower_expr(expr)?,
                MetaArg::Named(_, expr) => self.lower_expr(expr)?,
            };

            llvm_args.push(value.into());
        }

        let call = self.builder
            .build_call(function, &llvm_args, "calltmp")
            .map_err(|e| LLVMError::Message(e.to_string()))?;

        match call.try_as_basic_value() {
            inkwell::values::ValueKind::Basic(value) => Ok(Some(value)),
            inkwell::values::ValueKind::Instruction(_) => Ok(None),
        }
    }

    fn lower_binary(
        &mut self,
        op: &MetaBinOp,
        lhs: &MetaExpr,
        rhs: &MetaExpr
    ) -> Result<BasicValueEnum<'ctx>, LLVMError> {
        let lhs = self.lower_expr(lhs)?.into_int_value();
        let rhs = self.lower_expr(rhs)?.into_int_value();

        let value = (
            match op {
                MetaBinOp::Add => self.builder.build_int_add(lhs, rhs, "addtmp"),
                MetaBinOp::Sub => self.builder.build_int_sub(lhs, rhs, "subtmp"),
                MetaBinOp::Mul => self.builder.build_int_mul(lhs, rhs, "multmp"),
                MetaBinOp::Div => self.builder.build_int_signed_div(lhs, rhs, "divtmp"),
                MetaBinOp::Mod => self.builder.build_int_signed_rem(lhs, rhs, "modtmp"),

                MetaBinOp::Eq =>
                    self.builder.build_int_compare(inkwell::IntPredicate::EQ, lhs, rhs, "eqtmp"),

                MetaBinOp::Neq =>
                    self.builder.build_int_compare(inkwell::IntPredicate::NE, lhs, rhs, "netmp"),

                MetaBinOp::Lt =>
                    self.builder.build_int_compare(inkwell::IntPredicate::SLT, lhs, rhs, "lttmp"),

                MetaBinOp::Lte =>
                    self.builder.build_int_compare(inkwell::IntPredicate::SLE, lhs, rhs, "ltetmp"),

                MetaBinOp::Gt =>
                    self.builder.build_int_compare(inkwell::IntPredicate::SGT, lhs, rhs, "gttmp"),

                MetaBinOp::Gte =>
                    self.builder.build_int_compare(inkwell::IntPredicate::SGE, lhs, rhs, "gtetmp"),

                MetaBinOp::And => self.builder.build_and(lhs, rhs, "andtmp"),

                MetaBinOp::Or => self.builder.build_or(lhs, rhs, "ortmp"),
            }
        ).map_err(|e| LLVMError::Message(e.to_string()))?;

        Ok(value.into())
    }
}