c3 0.11.2

Complete C AST. Enables analysis and generation of code derived from C. Built using LLVM 4/Clang using some fragile C++ APIs to work around missing data and ambiguities in libclang.

use std::iter;
use std::vec;
use std::ptr;
use std::cmp::{min,max};
use clang_sys::*;
use crate::bindgen::clang::*;
use crate::expr::Loc;
use super::Res;

pub(crate) trait MapResult<T> {
    fn map_res<U, E, F>(self, f: F) -> Result<Option<U>, E> where F: FnOnce(T) -> Result<U, E>;
}

impl<T> MapResult<T> for Option<T> {
    fn map_res<U, E, F>(self, f: F) -> Result<Option<U>, E> where F: FnOnce(T) -> Result<U, E> {
        if let Some(val) = self {
            match f(val) {
                Ok(v) => Ok(Some(v)),
                Err(e) => Err(e),
            }
        } else {
            Ok(None)
        }
    }
}

pub(crate) trait CursorExt where Self: Sized {
    fn map<T, F: FnMut(Cursor) -> T>(&self, cb: F) -> iter::Map<vec::IntoIter<Self>, F>;
    fn map_children<T, F: FnMut(Cursor) -> T>(&self, cb: F) -> Vec<T>;
    fn map_children_err<T,F: FnMut(Cursor) -> Res<T>>(&self, cb: F) -> Res<Vec<T>>;
    fn first_expr_child(&self) -> Option<Cursor>;

    fn loc(&self) -> Loc;
    fn file(&self) -> File;

    fn new_cur(x: CXCursor) -> Option<Cursor>;
    fn for_body(&self) -> Cursor;
    fn var_init(&self) -> Option<Cursor>;
    fn for_cond(&self) -> Option<Cursor>;
    fn for_inc(&self) -> Option<Cursor>;
    fn for_init(&self) -> Option<Cursor>;
    fn if_else(&self) -> Option<Cursor>;
    fn if_then(&self) -> Cursor;
    fn if_cond(&self) -> Cursor;
    fn if_condition_variable(&self) -> Option<Cursor>;
    fn sub_expr(&self) -> Option<Cursor>;
    fn is_anon(&self) -> bool;
    fn is_function_macro(&self, tu: &TranslationUnit) -> bool;

    fn binary_opcode(&self) -> BinaryOperatorKind;
    fn unary_opcode(&self) -> UnaryOperatorKind;
    fn get_int_value(&self) -> u64;
    fn get_float_value(&self) -> f64;

    fn storage(&self) -> CX_StorageClass ;

    fn sizeof_arg_type(&self) -> Option<Type>;
}

impl CursorExt for Cursor {
    fn map<T, F: FnMut(Cursor) -> T>(&self, cb: F) -> iter::Map<vec::IntoIter<Self>, F> {
        self.collect_children().into_iter().map(cb)
    }

    /// Collect all of this cursor's children into a vec and return them.
    fn map_children<T, F: FnMut(Cursor) -> T>(&self, mut cb: F) -> Vec<T> {
        let mut children = vec![];
        self.visit(|c| {
            children.push(cb(c));
            CXChildVisit_Continue
        });
        children
    }

    fn map_children_err<T,F: FnMut(Cursor) -> Res<T>>(&self, mut cb: F) -> Res<Vec<T>> {
        let mut children = vec![];
        let mut err = None;
        self.visit(|c| {
            match cb(c) {
                Ok(c) => {
                    children.push(c);
                    CXChildVisit_Continue
                }
                Err(e) => {
                    err = Some(e);
                    CXChildVisit_Break
                }
            }
        });
        if let Some(e) = err {
            Err(e)
        } else {
            Ok(children)
        }
    }

    fn first_expr_child(&self) -> Option<Cursor> {
        let mut r = None;
        self.visit(|ch| {
            match ch.kind() {
                CXCursor_TypeRef => CXChildVisit_Continue,
                _ => {
                    r = Some(ch);
                    CXChildVisit_Break
                },
            }
        });
        r
    }

    fn file(&self) -> File {
        let (file, ..) = self.location().location();
        file
    }

    fn loc(&self) -> Loc {
        Loc::new(self.extent())
    }

    fn is_function_macro(&self, tu: &TranslationUnit) -> bool {
        if let Some(t) = tu.tokens(self) {
            if let Some(t) = t.get(1) {
                return t.spelling == "("; // FIXME: that's bad approximation, read C++ API
            }
        }
        false
    }

    fn is_anon(&self) -> bool {
        unsafe {
            clang_Cursor_isAnonymous(self.x) != 0
        }
    }

    fn new_cur(x: CXCursor) -> Option<Cursor> {
        let cur = Cursor {x};
        if cur.is_valid() {
            Some(cur)
        } else {
            None
        }
    }

    fn for_body(&self) -> Cursor {
        unsafe { Cursor {
            x: c3_ForStmt_getBody(self.x)
        } }
    }

    fn var_init(&self) -> Option<Cursor> {
        unsafe { Self::new_cur(c3_VarDecl_getInit(self.x)) }
    }

    fn for_cond(&self) -> Option<Cursor> {
        unsafe { Self::new_cur(c3_ForStmt_getCond(self.x)) }
    }

    fn for_inc(&self) -> Option<Cursor> {
        unsafe { Self::new_cur(c3_ForStmt_getInc(self.x)) }
    }

    fn for_init(&self) -> Option<Cursor> {
        unsafe { Self::new_cur(c3_ForStmt_getInit(self.x)) }
    }

    fn if_else(&self) -> Option<Cursor> {
        unsafe { Self::new_cur(c3_IfStmt_getElse(self.x)) }
    }

    fn if_then(&self) -> Cursor {
        unsafe { Cursor { x: c3_IfStmt_getThen(self.x) } }
    }

    fn if_cond(&self) -> Cursor {
        unsafe { Cursor { x: c3_IfStmt_getCond(self.x) } }
    }

    fn if_condition_variable(&self) -> Option<Cursor> {
        unsafe { Self::new_cur(c3_IfStmt_getConditionVariable(self.x)) }
    }

    fn sub_expr(&self) -> Option<Cursor> {
        unsafe {
            Self::new_cur(c3_Cursor_getSubExpr(self.x))
                .or_else(||self.first_expr_child())
        }
    }

    fn binary_opcode(&self) -> BinaryOperatorKind {
        unsafe {
            c3_Cursor_getBinaryOpcode(self.x)
        }
    }

    fn unary_opcode(&self) -> UnaryOperatorKind {
        unsafe {
            c3_Cursor_getUnaryOpcode(self.x)
        }
    }

    fn get_int_value(&self) -> u64 {
        unsafe { c3_Cursor_getIntValue(self.x) }
    }

    fn get_float_value(&self) -> f64 {
        unsafe { c3_Cursor_getFloatValue(self.x) }
    }

    fn sizeof_arg_type(&self) -> Option<Type> {
        let ty = unsafe { Type { x: c3_UnaryExpr_getArgType(self.x) } };
        if ty.is_valid() {
            Some(ty)
        } else {
            None
        }
    }

    fn storage(&self) -> CX_StorageClass {
        unsafe {
            clang_Cursor_getStorageClass(self.x)
        }
    }
}

pub trait RangeLocations {
    fn locations(self) -> (SourceLocation, SourceLocation);
}

impl RangeLocations for CXSourceRange {
    fn locations(self) -> (SourceLocation, SourceLocation) {
        unsafe {
            (SourceLocation {x:clang_getRangeStart(self)}, SourceLocation{x:clang_getRangeEnd(self)})
        }
    }
}

impl Loc {
    #[must_use] pub fn new(ext: CXSourceRange) -> Self {
        fn pos(loc: CXSourceLocation) -> (u32, u32, u32) {
            unsafe {
                let mut line = 0;
                let mut col = 0;
                let mut byte_pos = 0;
                clang_getFileLocation(loc, ptr::null_mut(), &mut line, &mut col, &mut byte_pos);
                (line as u32, col as u32, byte_pos as u32)
            }
        }
        let (start_line, start_col, byte_pos) = pos(unsafe {clang_getRangeStart(ext)});
        let (end_line, _, end_pos) = pos(unsafe {clang_getRangeEnd(ext)});
        Loc {
            byte_pos,
            byte_len: end_pos - byte_pos,
            start_col: start_col as u16,
            start_line,
            line_len: (end_line - start_line) as u16,
        }
    }

    #[must_use] pub fn is_builtin(&self) -> bool {
        self.start_line == 0
    }
}

use std::slice;
use std::os::raw::c_uint;

pub fn comment_tokens(tu: &TranslationUnit) -> Option<Vec<crate::expr::Comment>> {
    let range = tu.cursor().extent();
    let mut tokens = vec![];
    unsafe {
        let mut token_ptr = ptr::null_mut();
        let mut num_tokens: c_uint = 0;
        clang_tokenize(tu.x, range, &mut token_ptr, &mut num_tokens);
        if token_ptr.is_null() {
            return None;
        }
        let token_array = slice::from_raw_parts(token_ptr, num_tokens as usize);

        let mut last_code_line = 0;
        for &token in token_array {
            let kind = clang_getTokenKind(token);
            let extent = clang_getTokenExtent(tu.x, token);

            if kind != CXToken_Comment {
                clang_getFileLocation(clang_getRangeEnd(extent), ptr::null_mut(), &mut last_code_line, ptr::null_mut(), ptr::null_mut());
                continue;
            }
            let syntax = cxstring_into_string(clang_getTokenSpelling(tu.x, token));
            let loc = Loc::new(extent);
            tokens.push(crate::expr::Comment {
                syntax,
                loc,
                after_code: last_code_line == loc.start_line,
            });
        }
        clang_disposeTokens(tu.x, token_ptr, num_tokens);
    }
    Some(tokens)
}