Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Expr handling in header-translator #578

Merged
merged 4 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/header-translator/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ impl<'a> Cache<'a> {
}) = iter.peek_mut()
{
if enum_ty.is_typedef_to(&id.name) {
*enum_id = id.clone().to_some();
*enum_id = id.clone();
*enum_ty = ty.clone();
// Skip adding the now-redundant alias to the list of statements
continue;
Expand Down
252 changes: 168 additions & 84 deletions crates/header-translator/src/expr.rs
Original file line number Diff line number Diff line change
@@ -1,95 +1,136 @@
use std::collections::BTreeMap;
use std::fmt;
use std::fmt::Write;

use clang::token::TokenKind;
use clang::{Entity, EntityKind, EntityVisitResult};
use clang::{Entity, EntityKind, EntityVisitResult, EvaluationResult};

use crate::context::Context;
use crate::immediate_children;
use crate::unexposed_attr::UnexposedAttr;
use crate::rust_type::Ty;
use crate::stmt::new_enum_id;
use crate::{Context, ItemIdentifier};

#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Punctuation(String),
Literal(String),
Expr(Expr),
}

#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
NSUIntegerMax,
NSIntegerMax,
Signed(i64),
Unsigned(u64),
String(String),
Float(f64),
MacroInvocation {
name: String,
evaluated: Option<Box<Expr>>,
},
Enum {
id: ItemIdentifier,
variant: String,
},
Const(ItemIdentifier),
Var {
id: ItemIdentifier,
ty: Ty,
},
Tokens(Vec<Token>),
}

impl Expr {
pub fn from_val((signed, unsigned): (i64, u64), is_signed: bool, pointer_width: usize) -> Self {
let (signed_max, unsigned_max) = match pointer_width {
64 => (i64::MAX, u64::MAX),
32 => (i32::MAX as i64, u32::MAX as u64),
16 => (i16::MAX as i64, u16::MAX as u64),
pw => panic!("unhandled pointer width {pw}"),
};

if unsigned == unsigned_max {
Expr::NSUIntegerMax
} else if signed == signed_max {
Expr::NSIntegerMax
} else if is_signed {
Expr::Signed(signed)
} else {
Expr::Unsigned(unsigned)
fn from_evaluated(entity: &Entity<'_>) -> Self {
let res = entity
.evaluate()
.expect("must be able to evaluate result of macro in expression");
match res {
EvaluationResult::SignedInteger(n) => Expr::Signed(n),
EvaluationResult::UnsignedInteger(n) => Expr::Unsigned(n),
EvaluationResult::Float(n) => Self::Float(n),
res => panic!("unexpected evaluation result {res:?}"),
}
}

pub fn parse_enum_constant(entity: &Entity<'_>, context: &Context<'_>) -> Option<Self> {
let mut declaration_references = Vec::new();
pub fn parse_enum_constant(entity: &Entity<'_>, context: &Context<'_>) -> Self {
Self::parse(entity, context)
}

entity.visit_children(|entity, _parent| {
if let EntityKind::DeclRefExpr = entity.get_kind() {
let name = entity.get_name().expect("expr decl ref name");
declaration_references.push(name);
pub fn parse_var(entity: &Entity<'_>, context: &Context<'_>) -> Self {
Self::parse(entity, context)
}

fn parse(entity: &Entity<'_>, context: &Context<'_>) -> Self {
match (entity.get_kind(), &*entity.get_children()) {
(EntityKind::ParenExpr, [_]) => {
// TODO: Remove unnecessary top-level parentheses
// Self::parse(child, context)
Self::parse_from_tokens(entity, context)
}
EntityVisitResult::Recurse
});
(EntityKind::DeclRefExpr, []) => Self::parse_from_decl_ref(entity, context),
// We can't really use the information in here for much, since the
// kind of operator is not exposed. So we fall back to parsing raw
// tokens instead.
(EntityKind::UnaryOperator, [_]) => Self::parse_from_tokens(entity, context),
(EntityKind::BinaryOperator, [_, _]) => Self::parse_from_tokens(entity, context),
(EntityKind::IntegerLiteral, []) => Self::parse_from_tokens(entity, context),
(EntityKind::FloatingLiteral, []) => Self::parse_from_tokens(entity, context),
// Remove unnecessary cast
(EntityKind::CStyleCastExpr, [_type, child]) => Self::parse(child, context),
(EntityKind::UnexposedExpr, _) => Self::parse_from_tokens(entity, context),
(_, children) => panic!("unknown expr: {entity:?}, {children:#?}"),
}
}

let mut res = None;
fn parse_from_tokens(entity: &Entity<'_>, context: &Context<'_>) -> Self {
let mut declaration_references = BTreeMap::new();

immediate_children(entity, |entity, _span| match entity.get_kind() {
EntityKind::UnexposedAttr => {
if let Some(attr) = UnexposedAttr::parse(&entity, context) {
error!(?attr, "unknown attribute");
}
}
_ => {
if res.is_none() {
res = Self::parse(&entity, &declaration_references);
} else {
panic!("found multiple expressions where one was expected");
}
entity.visit_children(|entity, _parent| {
if let EntityKind::DeclRefExpr = entity.get_kind() {
let name = entity.get_name().expect("DeclRefExpr name");
declaration_references.insert(name, Self::parse_from_decl_ref(&entity, context));
}
EntityVisitResult::Recurse
});

res
}

pub fn parse_var(entity: &Entity<'_>) -> Option<Self> {
Self::parse(entity, &[])
}

fn parse(entity: &Entity<'_>, declaration_references: &[String]) -> Option<Self> {
let range = entity.get_range().expect("expr range");
let tokens = range.tokenize();

if tokens.is_empty() {
// TODO: Find a better way to parse macros
return None;
let location = entity.get_location().expect("expr location");
if let Some(macro_invocation) = context
.macro_invocations
.get(&location.get_spelling_location())
{
let name = macro_invocation
.get_name()
.expect("expr macro invocation name");
return Expr::MacroInvocation {
name,
evaluated: Some(Box::new(Self::from_evaluated(entity))),
};
} else {
return Self::from_evaluated(entity);
}
}

let mut s = String::new();
let mut res = vec![];

for token in &tokens {
match (token.get_kind(), token.get_spelling()) {
res.push(match (token.get_kind(), token.get_spelling()) {
(TokenKind::Identifier, ident) => {
if declaration_references.contains(&ident) {
// TODO: Handle these specially when we need to
}
write!(s, "{ident}").unwrap();
Token::Expr(if let Some(expr) = declaration_references.get(&ident) {
expr.clone()
} else {
let macro_invocation = context
.macro_invocations
.get(&token.get_location().get_spelling_location())
.expect("expr macro invocation");
let name = macro_invocation
.get_name()
.expect("expr macro invocation name");
Expr::MacroInvocation {
name,
evaluated: None,
}
})
}
(TokenKind::Literal, lit) => {
let lit = lit
Expand All @@ -98,52 +139,95 @@ impl Expr {
.trim_end_matches('u')
.trim_end_matches('U');
let lit = lit.replace("0X", "0x");
write!(s, "{lit}").unwrap();
Token::Literal(lit)
}
(TokenKind::Punctuation, punct) => {
match &*punct {
// These have the same semantics in C and Rust
"(" | ")" | "<<" | "-" | "+" | "|" | "&" | "^" => {
write!(s, "{punct}").unwrap()
}
"(" | ")" | "<<" | "-" | "+" | "|" | "&" | "^" => Token::Punctuation(punct),
// Bitwise not
"~" => write!(s, "!").unwrap(),
"~" => Token::Punctuation("!".to_string()),
punct => panic!("unknown expr punctuation {punct}"),
}
}
(kind, spelling) => panic!("unknown expr token {kind:?}/{spelling}"),
}
});
}

// Trim casts
s = s
.trim_start_matches("(NSBoxType)")
.trim_start_matches("(NSBezelStyle)")
.trim_start_matches("(NSEventSubtype)")
.trim_start_matches("(NSWindowButton)")
.trim_start_matches("(NSExpressionType)")
.to_string();

// Trim unnecessary parentheses
if s.starts_with('(')
&& s.ends_with(')')
&& s.chars().filter(|&c| c == '(' || c == ')').count() == 2
if res.first() == Some(&Token::Punctuation("(".to_string()))
&& res.last() == Some(&Token::Punctuation(")".to_string()))
{
s = s.trim_start_matches('(').trim_end_matches(')').to_string();
res.remove(0);
res.pop();
}

Some(Self::String(s))
Self::Tokens(res)
}

fn parse_from_decl_ref(entity: &Entity<'_>, context: &Context<'_>) -> Self {
assert_eq!(entity.get_kind(), EntityKind::DeclRefExpr);
let definition = entity.get_definition().expect("DeclRefExpr definition");
assert_eq!(entity.get_name(), definition.get_name());
match definition.get_kind() {
EntityKind::EnumConstantDecl => {
let parent = definition
.get_semantic_parent()
.expect("EnumConstantDecl parent");
let parent_id = new_enum_id(&parent, context);
let name = entity.get_name().expect("EnumConstantDecl name");
if parent_id.name.is_some() {
Self::Enum {
id: parent_id.map_name(|name| name.unwrap()),
variant: name,
}
} else {
Self::Const(parent_id.map_name(|_| name))
}
}
EntityKind::VarDecl => Self::Var {
id: ItemIdentifier::new(&definition, context),
ty: Ty::parse_static(definition.get_type().expect("var type"), context),
},
_ => panic!("unknown DeclRefExpr {definition:#?} in {entity:#?}"),
}
}
}

impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NSUIntegerMax => write!(f, "NSUIntegerMax as _"),
Self::NSIntegerMax => write!(f, "NSIntegerMax as _"),
Self::Signed(signed) => write!(f, "{signed}"),
Self::Unsigned(unsigned) => write!(f, "{unsigned}"),
Self::String(s) => write!(f, "{s}"),
Self::Float(n) => write!(f, "{n}"),
Self::MacroInvocation { name, evaluated } => {
if name == "NSIntegerMax" {
write!(f, "NSIntegerMax as _")
} else if name == "NSUIntegerMax" {
write!(f, "NSUIntegerMax as _")
} else if name == "FLT_MAX" {
write!(f, "c_float::MAX as _")
} else if name == "DBL_MAX" {
write!(f, "c_double::MAX as _")
} else if let Some(evaluated) = evaluated {
write!(f, "{evaluated}")
} else {
write!(f, "{name}")
}
}
Self::Enum { id: _, variant } => write!(f, "{variant}"),
Self::Const(id) => write!(f, "{}", id.name),
Self::Var { id, ty: _ } => write!(f, "{}", id.name),
Self::Tokens(tokens) => {
for token in tokens {
match token {
Token::Punctuation(punct) => write!(f, "{punct}")?,
Token::Literal(lit) => write!(f, "{lit}")?,
Token::Expr(expr) => write!(f, "{expr}")?,
}
}
Ok(())
}
}
}
}
Loading
Loading