use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct QualType {
pub base: Box<TypeNode>,
pub is_const: bool,
pub is_volatile: bool,
pub is_restrict: bool,
}
impl QualType {
pub fn new(base: TypeNode) -> Self {
Self {
base: Box::new(base),
is_const: false,
is_volatile: false,
is_restrict: false,
}
}
pub fn const_(base: TypeNode) -> Self {
Self {
base: Box::new(base),
is_const: true,
is_volatile: false,
is_restrict: false,
}
}
pub fn volatile(base: TypeNode) -> Self {
Self {
base: Box::new(base),
is_const: false,
is_volatile: true,
is_restrict: false,
}
}
pub fn int() -> Self {
Self::new(TypeNode::Int)
}
pub fn void() -> Self {
Self::new(TypeNode::Void)
}
pub fn char() -> Self {
Self::new(TypeNode::Char)
}
pub fn pointer_to(pointee: QualType) -> Self {
Self::new(TypeNode::Pointer(Box::new(pointee)))
}
pub fn const_char_ptr() -> Self {
Self::pointer_to(Self::const_(TypeNode::Char))
}
pub fn schar() -> Self {
Self::new(TypeNode::SChar)
}
pub fn uchar() -> Self {
Self::new(TypeNode::UChar)
}
pub fn short_() -> Self {
Self::new(TypeNode::Short)
}
pub fn ushort() -> Self {
Self::new(TypeNode::UShort)
}
pub fn uint() -> Self {
Self::new(TypeNode::UInt)
}
pub fn long_() -> Self {
Self::new(TypeNode::Long)
}
pub fn ulong() -> Self {
Self::new(TypeNode::ULong)
}
pub fn longlong() -> Self {
Self::new(TypeNode::LongLong)
}
pub fn ulonglong() -> Self {
Self::new(TypeNode::ULongLong)
}
pub fn float_() -> Self {
Self::new(TypeNode::Float)
}
pub fn double_() -> Self {
Self::new(TypeNode::Double)
}
pub fn ldouble() -> Self {
Self::new(TypeNode::LongDouble)
}
pub fn bool_() -> Self {
Self::new(TypeNode::Bool)
}
pub fn auto_ty() -> Self {
Self::new(TypeNode::Auto)
}
pub fn named(name: &str) -> Self {
Self::new(TypeNode::Record(name.to_string()))
}
pub fn long() -> Self {
Self::long_()
}
pub fn short() -> Self {
Self::short_()
}
pub fn float() -> Self {
Self::float_()
}
pub fn double() -> Self {
Self::double_()
}
pub fn unqualified(&self) -> QualType {
QualType {
base: self.base.clone(),
is_const: false,
is_volatile: false,
is_restrict: false,
}
}
pub fn is_void(&self) -> bool {
matches!(self.inner(), TypeNode::Void)
}
pub(crate) fn inner(&self) -> &TypeNode {
let mut ty = &*self.base;
while let TypeNode::Typedef { underlying, .. } = ty {
ty = &*underlying.base;
}
ty
}
pub fn is_integer(&self) -> bool {
matches!(
self.inner(),
TypeNode::Char
| TypeNode::SChar
| TypeNode::UChar
| TypeNode::Short
| TypeNode::UShort
| TypeNode::Int
| TypeNode::UInt
| TypeNode::Long
| TypeNode::ULong
| TypeNode::LongLong
| TypeNode::ULongLong
| TypeNode::Bool
| TypeNode::Enum { .. }
)
}
pub fn is_floating(&self) -> bool {
matches!(
self.inner(),
TypeNode::Float | TypeNode::Double | TypeNode::LongDouble
)
}
pub fn is_pointer(&self) -> bool {
matches!(self.inner(), TypeNode::Pointer(_))
}
pub fn is_array(&self) -> bool {
matches!(self.inner(), TypeNode::Array { .. })
}
pub fn is_char(&self) -> bool {
matches!(
*self.base,
TypeNode::Char | TypeNode::SChar | TypeNode::UChar
)
}
pub fn is_short(&self) -> bool {
matches!(*self.base, TypeNode::Short | TypeNode::UShort)
}
pub fn is_int(&self) -> bool {
matches!(*self.base, TypeNode::Int | TypeNode::UInt)
}
pub fn is_long(&self) -> bool {
matches!(*self.base, TypeNode::Long | TypeNode::ULong)
}
pub fn is_long_long(&self) -> bool {
matches!(*self.base, TypeNode::LongLong | TypeNode::ULongLong)
}
pub fn is_float(&self) -> bool {
matches!(*self.base, TypeNode::Float)
}
pub fn is_double(&self) -> bool {
matches!(*self.base, TypeNode::Double)
}
pub fn char_type() -> Self {
Self::char()
}
pub fn short_type() -> Self {
Self::short()
}
pub fn long_type() -> Self {
Self::long()
}
pub fn long_long() -> Self {
Self::longlong()
}
pub fn ulong_long() -> Self {
Self::ulonglong()
}
pub fn float_type() -> Self {
Self::float()
}
pub fn double_type() -> Self {
Self::double()
}
pub fn long_double() -> Self {
Self::ldouble()
}
}
impl fmt::Display for QualType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_const {
write!(f, "const ")?;
}
if self.is_volatile {
write!(f, "volatile ")?;
}
if self.is_restrict {
write!(f, "restrict ")?;
}
write!(f, "{}", self.base)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeNode {
Void,
Char,
SChar,
UChar,
Short,
UShort,
Int,
UInt,
Long,
ULong,
LongLong,
ULongLong,
Float,
Double,
LongDouble,
Bool,
Complex,
Auto,
Record(String),
Pointer(Box<QualType>),
Array {
elem: Box<QualType>,
size: Option<usize>,
},
Function {
ret: Box<QualType>,
params: Vec<QualType>,
is_vararg: bool,
},
Struct {
name: Option<String>,
fields: Vec<FieldDecl>,
is_union: bool,
},
Enum {
name: Option<String>,
variants: Vec<EnumVariant>,
},
Typedef {
name: String,
underlying: Box<QualType>,
},
}
impl TypeNode {
pub fn kind_name(&self) -> &str {
match self {
TypeNode::Void => "void",
TypeNode::Char => "char",
TypeNode::SChar => "signed char",
TypeNode::UChar => "unsigned char",
TypeNode::Short => "short",
TypeNode::UShort => "unsigned short",
TypeNode::Int => "int",
TypeNode::UInt => "unsigned int",
TypeNode::Long => "long",
TypeNode::ULong => "unsigned long",
TypeNode::LongLong => "long long",
TypeNode::ULongLong => "unsigned long long",
TypeNode::Float => "float",
TypeNode::Double => "double",
TypeNode::LongDouble => "long double",
TypeNode::Bool => "_Bool",
TypeNode::Complex => "_Complex",
TypeNode::Pointer(_) => "pointer",
TypeNode::Array { .. } => "array",
TypeNode::Function { .. } => "function",
TypeNode::Struct { is_union: true, .. } => "union",
TypeNode::Struct { .. } => "struct",
TypeNode::Enum { .. } => "enum",
TypeNode::Typedef { .. } => "typedef",
TypeNode::Auto => "auto",
TypeNode::Record(name) => name.as_str(),
}
}
pub fn size_bytes(&self) -> usize {
match self {
TypeNode::Void => 0,
TypeNode::Char | TypeNode::SChar | TypeNode::UChar => 1,
TypeNode::Short | TypeNode::UShort => 2,
TypeNode::Int | TypeNode::UInt => 4,
TypeNode::Long | TypeNode::ULong => 8,
TypeNode::LongLong | TypeNode::ULongLong => 8,
TypeNode::Float => 4,
TypeNode::Double => 8,
TypeNode::LongDouble => 16,
TypeNode::Bool => 1,
TypeNode::Complex => 16,
TypeNode::Pointer(_) => 8,
TypeNode::Array { .. } => 0, TypeNode::Function { .. } => 0,
TypeNode::Struct { .. } => 0, TypeNode::Enum { .. } => 4, TypeNode::Typedef { underlying, .. } => underlying.base.size_bytes(),
TypeNode::Auto => 0,
TypeNode::Record(_) => 0,
}
}
pub fn is_integer(&self) -> bool {
matches!(
self,
TypeNode::Char
| TypeNode::SChar
| TypeNode::UChar
| TypeNode::Short
| TypeNode::UShort
| TypeNode::Int
| TypeNode::UInt
| TypeNode::Long
| TypeNode::ULong
| TypeNode::LongLong
| TypeNode::ULongLong
| TypeNode::Bool
| TypeNode::Enum { .. }
)
}
pub fn is_unsigned(&self) -> bool {
match self {
TypeNode::UChar
| TypeNode::UShort
| TypeNode::UInt
| TypeNode::ULong
| TypeNode::ULongLong => true,
TypeNode::Typedef { underlying, .. } => underlying.is_unsigned(),
_ => false,
}
}
pub fn is_signed_integer(&self) -> bool {
self.is_integer() && !self.is_unsigned()
}
}
impl fmt::Display for TypeNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeNode::Void => write!(f, "void"),
TypeNode::Char => write!(f, "char"),
TypeNode::SChar => write!(f, "signed char"),
TypeNode::UChar => write!(f, "unsigned char"),
TypeNode::Short => write!(f, "short"),
TypeNode::UShort => write!(f, "unsigned short"),
TypeNode::Int => write!(f, "int"),
TypeNode::UInt => write!(f, "unsigned int"),
TypeNode::Long => write!(f, "long"),
TypeNode::ULong => write!(f, "unsigned long"),
TypeNode::LongLong => write!(f, "long long"),
TypeNode::ULongLong => write!(f, "unsigned long long"),
TypeNode::Float => write!(f, "float"),
TypeNode::Double => write!(f, "double"),
TypeNode::LongDouble => write!(f, "long double"),
TypeNode::Bool => write!(f, "_Bool"),
TypeNode::Complex => write!(f, "_Complex"),
TypeNode::Pointer(inner) => write!(f, "{}*", inner),
TypeNode::Array { elem, size } => match size {
Some(n) => write!(f, "{}[{}]", elem, n),
None => write!(f, "{}[]", elem),
},
TypeNode::Function {
ret,
params,
is_vararg,
} => {
write!(f, "{}(", ret)?;
for (i, p) in params.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", p)?;
}
if *is_vararg {
if !params.is_empty() {
write!(f, ", ")?;
}
write!(f, "...")?;
}
write!(f, ")")
}
TypeNode::Struct {
name,
fields,
is_union,
} => {
if *is_union {
write!(f, "union")?;
} else {
write!(f, "struct")?;
}
if let Some(n) = name {
write!(f, " {}", n)?;
}
write!(f, " {{ ")?;
for field in fields {
write!(f, "{} {}; ", field.ty, field.name)?;
}
write!(f, "}}")
}
TypeNode::Enum { name, variants } => {
write!(f, "enum")?;
if let Some(n) = name {
write!(f, " {}", n)?;
}
write!(f, " {{ ")?;
for v in variants {
match v.value {
Some(val) => write!(f, "{} = {}, ", v.name, val)?,
None => write!(f, "{}, ", v.name)?,
}
}
write!(f, "}}")
}
TypeNode::Typedef { name, underlying } => {
write!(f, "/* typedef {} -> {} */", name, underlying)
}
TypeNode::Auto => write!(f, "auto"),
TypeNode::Record(name) => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldDecl {
pub name: String,
pub ty: QualType,
pub bit_width: Option<u32>,
}
impl FieldDecl {
pub fn new(name: &str, ty: QualType) -> Self {
Self {
name: name.to_string(),
ty,
bit_width: None,
}
}
pub fn new_bitfield(name: &str, ty: QualType, width: u32) -> Self {
Self {
name: name.to_string(),
ty,
bit_width: Some(width),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumVariant {
pub name: String,
pub value: Option<i64>,
}
impl EnumVariant {
pub fn new(name: &str, value: Option<i64>) -> Self {
Self {
name: name.to_string(),
value,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Decl {
Function(FunctionDecl),
Variable(VarDecl),
Typedef(TypedefDecl),
Struct(StructDecl),
Enum(EnumDecl),
EnumVariant(EnumVariantDecl),
}
impl Decl {
pub fn name(&self) -> Option<&str> {
match self {
Decl::Function(d) => Some(&d.name),
Decl::Variable(d) => Some(&d.name),
Decl::Typedef(d) => Some(&d.name),
Decl::Struct(d) => d.name.as_deref(),
Decl::Enum(d) => d.name.as_deref(),
Decl::EnumVariant(d) => Some(&d.name),
}
}
pub fn is_function(&self) -> bool {
matches!(self, Decl::Function(_))
}
pub fn is_variable(&self) -> bool {
matches!(self, Decl::Variable(_))
}
}
impl fmt::Display for Decl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Decl::Function(d) => write!(f, "{}", d),
Decl::Variable(d) => write!(f, "{}", d),
Decl::Typedef(d) => write!(f, "{}", d),
Decl::Struct(d) => write!(f, "{}", d),
Decl::Enum(d) => write!(f, "{}", d),
Decl::EnumVariant(d) => write!(f, "{}", d),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionDecl {
pub name: String,
pub ret_ty: QualType,
pub params: Vec<VarDecl>,
pub body: Option<CompoundStmt>,
pub is_vararg: bool,
pub linkage: Linkage,
pub is_inline: bool,
pub is_noreturn: bool,
}
impl FunctionDecl {
pub fn new(name: &str, ret_ty: QualType) -> Self {
Self {
name: name.to_string(),
ret_ty,
params: Vec::new(),
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
}
}
pub fn with_body(mut self, body: CompoundStmt) -> Self {
self.body = Some(body);
self
}
pub fn with_params(mut self, params: Vec<VarDecl>) -> Self {
self.params = params;
self
}
}
impl fmt::Display for FunctionDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_inline {
write!(f, "inline ")?;
}
if self.is_noreturn {
write!(f, "_Noreturn ")?;
}
write!(f, "{} {}(", self.ret_ty, self.name)?;
for (i, p) in self.params.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{} {}", p.ty, p.name)?;
}
if self.is_vararg {
if !self.params.is_empty() {
write!(f, ", ")?;
}
write!(f, "...")?;
}
write!(f, ")")?;
match &self.body {
Some(body) => write!(f, " {}", body),
None => write!(f, ";"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VarDecl {
pub name: String,
pub ty: QualType,
pub init: Option<Box<Expr>>,
pub linkage: Linkage,
pub is_global: bool,
pub is_extern: bool,
pub is_static: bool,
}
impl VarDecl {
pub fn new(name: &str, ty: QualType) -> Self {
Self {
name: name.to_string(),
ty,
init: None,
linkage: Linkage::None,
is_global: false,
is_extern: false,
is_static: false,
}
}
pub fn with_init(mut self, init: Expr) -> Self {
self.init = Some(Box::new(init));
self
}
pub fn global(mut self) -> Self {
self.is_global = true;
self.linkage = Linkage::External;
self
}
pub fn static_(mut self) -> Self {
self.is_static = true;
self.linkage = Linkage::Internal;
self
}
}
impl fmt::Display for VarDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_extern {
write!(f, "extern ")?;
}
if self.is_static {
write!(f, "static ")?;
}
write!(f, "{} {}", self.ty, self.name)?;
if let Some(ref init) = self.init {
write!(f, " = {}", init)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedefDecl {
pub name: String,
pub underlying: QualType,
}
impl TypedefDecl {
pub fn new(name: &str, underlying: QualType) -> Self {
Self {
name: name.to_string(),
underlying,
}
}
}
impl fmt::Display for TypedefDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "typedef {} {};", self.underlying, self.name)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructDecl {
pub name: Option<String>,
pub fields: Vec<FieldDecl>,
pub is_union: bool,
}
impl StructDecl {
pub fn new(name: Option<&str>, is_union: bool) -> Self {
Self {
name: name.map(|s| s.to_string()),
fields: Vec::new(),
is_union,
}
}
pub fn with_fields(mut self, fields: Vec<FieldDecl>) -> Self {
self.fields = fields;
self
}
}
impl fmt::Display for StructDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let tag = if self.is_union { "union" } else { "struct" };
write!(f, "{}", tag)?;
if let Some(ref n) = self.name {
write!(f, " {}", n)?;
}
write!(f, " {{ ")?;
for field in &self.fields {
write!(f, "{} {}; ", field.ty, field.name)?;
}
write!(f, "}};")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumDecl {
pub name: Option<String>,
pub variants: Vec<EnumVariant>,
pub underlying: QualType,
}
impl EnumDecl {
pub fn new(name: Option<&str>) -> Self {
Self {
name: name.map(|s| s.to_string()),
variants: Vec::new(),
underlying: QualType::int(),
}
}
pub fn with_variants(mut self, variants: Vec<EnumVariant>) -> Self {
self.variants = variants;
self
}
}
impl fmt::Display for EnumDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "enum")?;
if let Some(ref n) = self.name {
write!(f, " {}", n)?;
}
write!(f, " {{ ")?;
for v in &self.variants {
match v.value {
Some(val) => write!(f, "{} = {}, ", v.name, val)?,
None => write!(f, "{}, ", v.name)?,
}
}
write!(f, "}};")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumVariantDecl {
pub name: String,
pub value: Option<i64>,
pub enum_name: Option<String>,
}
impl EnumVariantDecl {
pub fn new(name: &str, value: Option<i64>) -> Self {
Self {
name: name.to_string(),
value,
enum_name: None,
}
}
}
impl fmt::Display for EnumVariantDecl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"enum {}::{}",
self.enum_name.as_deref().unwrap_or("?"),
self.name
)?;
if let Some(v) = self.value {
write!(f, " = {}", v)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
Compound(CompoundStmt),
Return(Option<Box<Expr>>),
If {
cond: Box<Expr>,
then: Box<Stmt>,
els: Option<Box<Stmt>>,
},
While { cond: Box<Expr>, body: Box<Stmt> },
DoWhile { body: Box<Stmt>, cond: Box<Expr> },
For {
init: Option<Box<Stmt>>,
cond: Option<Box<Expr>>,
incr: Option<Box<Expr>>,
body: Box<Stmt>,
},
Switch { expr: Box<Expr>, body: Box<Stmt> },
Case { value: Box<Expr>, stmt: Box<Stmt> },
Default { stmt: Box<Stmt> },
Break,
Continue,
Goto { label: String },
Label { name: String, stmt: Box<Stmt> },
Expr(Box<Expr>),
Decl(Box<VarDecl>),
Null,
}
impl Stmt {
pub fn is_compound(&self) -> bool {
matches!(self, Stmt::Compound(_))
}
pub fn is_null(&self) -> bool {
matches!(self, Stmt::Null)
}
pub fn return_(expr: Option<Expr>) -> Self {
Stmt::Return(expr.map(Box::new))
}
pub fn if_(cond: Expr, then: Stmt, els: Option<Stmt>) -> Self {
Stmt::If {
cond: Box::new(cond),
then: Box::new(then),
els: els.map(Box::new),
}
}
pub fn while_(cond: Expr, body: Stmt) -> Self {
Stmt::While {
cond: Box::new(cond),
body: Box::new(body),
}
}
pub fn expr(expr: Expr) -> Self {
Stmt::Expr(Box::new(expr))
}
pub fn decl(var: VarDecl) -> Self {
Stmt::Decl(Box::new(var))
}
}
impl fmt::Display for Stmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.pretty_print(f, 0)
}
}
impl Stmt {
fn pretty_print(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
let pad = " ".repeat(indent);
match self {
Stmt::Compound(compound) => {
writeln!(f, "{} {{", pad)?;
for s in &compound.stmts {
s.pretty_print(f, indent + 1)?;
}
write!(f, "{}}}", pad)
}
Stmt::Return(None) => write!(f, "{}return;", pad),
Stmt::Return(Some(e)) => write!(f, "{}return {};", pad, e),
Stmt::If { cond, then, els } => {
write!(f, "{}if ({}) ", pad, cond)?;
then.pretty_print(f, indent)?;
if let Some(else_stmt) = els {
write!(f, " else ")?;
else_stmt.pretty_print(f, indent)?;
}
Ok(())
}
Stmt::While { cond, body } => {
write!(f, "{}while ({}) ", pad, cond)?;
body.pretty_print(f, indent)
}
Stmt::DoWhile { body, cond } => {
write!(f, "{}do ", pad)?;
body.pretty_print(f, indent)?;
write!(f, " while ({});", cond)
}
Stmt::For {
init,
cond,
incr,
body,
} => {
write!(f, "{}for (", pad)?;
if let Some(i) = init {
write!(f, "{}", i)?;
}
write!(f, "; ")?;
if let Some(c) = cond {
write!(f, "{}", c)?;
}
write!(f, "; ")?;
if let Some(i) = incr {
write!(f, "{}", i)?;
}
write!(f, ") ")?;
body.pretty_print(f, indent)
}
Stmt::Switch { expr, body } => {
write!(f, "{}switch ({}) ", pad, expr)?;
body.pretty_print(f, indent)
}
Stmt::Case { value, stmt } => {
write!(f, "{}case {}: ", pad, value)?;
stmt.pretty_print(f, indent)
}
Stmt::Default { stmt } => {
write!(f, "{}default: ", pad)?;
stmt.pretty_print(f, indent)
}
Stmt::Break => write!(f, "{}break;", pad),
Stmt::Continue => write!(f, "{}continue;", pad),
Stmt::Goto { label } => write!(f, "{}goto {};", pad, label),
Stmt::Label { name, stmt } => {
write!(f, "{}{}: ", pad, name)?;
stmt.pretty_print(f, indent)
}
Stmt::Expr(e) => write!(f, "{}{};", pad, e),
Stmt::Decl(d) => write!(f, "{}{};", pad, d),
Stmt::Null => write!(f, "{};", pad),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CompoundStmt {
pub stmts: Vec<Stmt>,
}
impl CompoundStmt {
pub fn new() -> Self {
Self { stmts: Vec::new() }
}
pub fn push(&mut self, stmt: Stmt) {
self.stmts.push(stmt);
}
pub fn is_empty(&self) -> bool {
self.stmts.is_empty()
}
pub fn len(&self) -> usize {
self.stmts.len()
}
}
impl Default for CompoundStmt {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for CompoundStmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{{")?;
for s in &self.stmts {
writeln!(f, " {}", s)?;
}
write!(f, "}}")
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
IntLiteral(i64),
UIntLiteral(u64, bool),
FloatLiteral(f64),
DoubleLiteral(f64),
CharLiteral(char),
StringLiteral(String),
Ident(String),
Unary(UnaryOp, Box<Expr>),
SizeOf(Box<Expr>),
SizeOfType(QualType),
AlignOf(Box<Expr>),
AlignOfType(QualType),
Cast(QualType, Box<Expr>),
Binary(BinaryOp, Box<Expr>, Box<Expr>),
Assign(BinaryOp, Box<Expr>, Box<Expr>),
Conditional(Box<Expr>, Box<Expr>, Box<Expr>),
Call {
callee: Box<Expr>,
args: Vec<Expr>,
},
Subscript {
base: Box<Expr>,
index: Box<Expr>,
},
Member {
base: Box<Expr>,
field: String,
is_arrow: bool,
},
PostInc(Box<Expr>),
PostDec(Box<Expr>),
PreInc(Box<Expr>),
PreDec(Box<Expr>),
CompoundLiteral(QualType, Box<Expr>),
AggregateLiteral(Vec<Expr>),
}
impl Expr {
pub fn ident(name: &str) -> Self {
Expr::Ident(name.to_string())
}
pub fn int(v: i64) -> Self {
Expr::IntLiteral(v)
}
pub fn binary(op: BinaryOp, lhs: Expr, rhs: Expr) -> Self {
Expr::Binary(op, Box::new(lhs), Box::new(rhs))
}
pub fn unary(op: UnaryOp, expr: Expr) -> Self {
Expr::Unary(op, Box::new(expr))
}
pub fn call(callee: Expr, args: &[Expr]) -> Self {
Expr::Call {
callee: Box::new(callee),
args: args.to_vec(),
}
}
pub fn assign(lhs: Expr, rhs: Expr) -> Self {
Expr::Assign(BinaryOp::Assign, Box::new(lhs), Box::new(rhs))
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expr::IntLiteral(v) => write!(f, "{}", v),
Expr::UIntLiteral(v, _) => write!(f, "{}u", v),
Expr::FloatLiteral(v) => write!(f, "{:?}f", v),
Expr::DoubleLiteral(v) => write!(f, "{:?}", v),
Expr::CharLiteral(c) => match c {
'\n' => write!(f, "'\\n'"),
'\t' => write!(f, "'\\t'"),
'\r' => write!(f, "'\\r'"),
'\\' => write!(f, "'\\\\'"),
'\'' => write!(f, "'\\''"),
_ if c.is_ascii_graphic() || *c == ' ' => write!(f, "'{}'", c),
_ => write!(f, "'\\x{:02X}'", *c as u32),
},
Expr::StringLiteral(s) => write!(f, "\"{}\"", s.escape_default()),
Expr::Ident(name) => write!(f, "{}", name),
Expr::Unary(op, e) => write!(f, "{:?}{}", op, e),
Expr::SizeOf(e) => write!(f, "sizeof({})", e),
Expr::SizeOfType(ty) => write!(f, "sizeof({})", ty),
Expr::AlignOf(e) => write!(f, "_Alignof({})", e),
Expr::AlignOfType(ty) => write!(f, "_Alignof({})", ty),
Expr::Cast(ty, e) => write!(f, "({}){}", ty, e),
Expr::Binary(op, lhs, rhs) => write!(f, "({} {:?} {})", lhs, op, rhs),
Expr::Assign(op, lhs, rhs) => write!(f, "({} {:?} {})", lhs, op, rhs),
Expr::Conditional(cond, then, els) => {
write!(f, "({} ? {} : {})", cond, then, els)
}
Expr::Call { callee, args } => {
write!(f, "{}(", callee)?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", a)?;
}
write!(f, ")")
}
Expr::Subscript { base, index } => write!(f, "{}[{}]", base, index),
Expr::Member {
base,
field,
is_arrow,
} => {
if *is_arrow {
write!(f, "{}->{}", base, field)
} else {
write!(f, "{}.{}", base, field)
}
}
Expr::PostInc(e) => write!(f, "({}++)", e),
Expr::PostDec(e) => write!(f, "({}--)", e),
Expr::PreInc(e) => write!(f, "(++{})", e),
Expr::PreDec(e) => write!(f, "(--{})", e),
Expr::CompoundLiteral(ty, init) => {
write!(f, "({}){}", ty, init)
}
Expr::AggregateLiteral(vals) => {
write!(f, "{{}}")?;
for (i, v) in vals.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", v)?;
}
write!(f, "}}")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UnaryOp {
Plus,
Minus,
Not,
BitNot,
AddrOf,
Deref,
}
impl UnaryOp {
pub fn as_str(self) -> &'static str {
match self {
UnaryOp::Plus => "+",
UnaryOp::Minus => "-",
UnaryOp::Not => "!",
UnaryOp::BitNot => "~",
UnaryOp::AddrOf => "&",
UnaryOp::Deref => "*",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Mod,
And,
Or,
Xor,
Shl,
Shr,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
LogicAnd,
LogicOr,
Comma,
Assign,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
ModAssign,
AndAssign,
OrAssign,
XorAssign,
ShlAssign,
ShrAssign,
}
impl BinaryOp {
pub fn as_str(self) -> &'static str {
match self {
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Mod => "%",
BinaryOp::And => "&",
BinaryOp::Or => "|",
BinaryOp::Xor => "^",
BinaryOp::Shl => "<<",
BinaryOp::Shr => ">>",
BinaryOp::Eq => "==",
BinaryOp::Ne => "!=",
BinaryOp::Lt => "<",
BinaryOp::Gt => ">",
BinaryOp::Le => "<=",
BinaryOp::Ge => ">=",
BinaryOp::LogicAnd => "&&",
BinaryOp::LogicOr => "||",
BinaryOp::Comma => ",",
BinaryOp::Assign => "=",
BinaryOp::AddAssign => "+=",
BinaryOp::SubAssign => "-=",
BinaryOp::MulAssign => "*=",
BinaryOp::DivAssign => "/=",
BinaryOp::ModAssign => "%=",
BinaryOp::AndAssign => "&=",
BinaryOp::OrAssign => "|=",
BinaryOp::XorAssign => "^=",
BinaryOp::ShlAssign => "<<=",
BinaryOp::ShrAssign => ">>=",
}
}
pub fn is_comparison(self) -> bool {
matches!(
self,
BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Lt | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Ge
)
}
pub fn is_logical(self) -> bool {
matches!(self, BinaryOp::LogicAnd | BinaryOp::LogicOr)
}
pub fn is_assignment(self) -> bool {
matches!(
self,
BinaryOp::Assign
| BinaryOp::AddAssign
| BinaryOp::SubAssign
| BinaryOp::MulAssign
| BinaryOp::DivAssign
| BinaryOp::ModAssign
| BinaryOp::AndAssign
| BinaryOp::OrAssign
| BinaryOp::XorAssign
| BinaryOp::ShlAssign
| BinaryOp::ShrAssign
)
}
pub fn without_assign(self) -> Option<BinaryOp> {
match self {
BinaryOp::Add | BinaryOp::AddAssign => Some(BinaryOp::Add),
BinaryOp::Sub | BinaryOp::SubAssign => Some(BinaryOp::Sub),
BinaryOp::Mul | BinaryOp::MulAssign => Some(BinaryOp::Mul),
BinaryOp::Div | BinaryOp::DivAssign => Some(BinaryOp::Div),
BinaryOp::Mod | BinaryOp::ModAssign => Some(BinaryOp::Mod),
BinaryOp::And | BinaryOp::AndAssign => Some(BinaryOp::And),
BinaryOp::Or | BinaryOp::OrAssign => Some(BinaryOp::Or),
BinaryOp::Xor | BinaryOp::XorAssign => Some(BinaryOp::Xor),
BinaryOp::Shl | BinaryOp::ShlAssign => Some(BinaryOp::Shl),
BinaryOp::Shr | BinaryOp::ShrAssign => Some(BinaryOp::Shr),
BinaryOp::Assign => None,
_ => Some(self),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Linkage {
External,
Internal,
None,
}
impl Linkage {
pub fn as_str(self) -> &'static str {
match self {
Linkage::External => "external",
Linkage::Internal => "internal",
Linkage::None => "none",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TranslationUnit {
pub decls: Vec<Decl>,
pub filename: String,
}
impl TranslationUnit {
pub fn new(filename: &str) -> Self {
Self {
decls: Vec::new(),
filename: filename.to_string(),
}
}
pub fn add_decl(&mut self, decl: Decl) {
self.decls.push(decl);
}
pub fn functions(&self) -> impl Iterator<Item = &FunctionDecl> {
self.decls.iter().filter_map(|d| match d {
Decl::Function(f) => Some(f),
_ => None,
})
}
pub fn globals(&self) -> impl Iterator<Item = &VarDecl> {
self.decls.iter().filter_map(|d| match d {
Decl::Variable(v) if v.is_global => Some(v),
_ => None,
})
}
pub fn is_empty(&self) -> bool {
self.decls.is_empty()
}
pub fn len(&self) -> usize {
self.decls.len()
}
}
impl fmt::Display for TranslationUnit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "// Translation unit: {}", self.filename)?;
for decl in &self.decls {
writeln!(f, "{}", decl)?;
}
Ok(())
}
}
pub trait AstVisitor {
fn visit_translation_unit(&mut self, _tu: &TranslationUnit) -> bool {
true
}
fn visit_decl(&mut self, _decl: &Decl) -> bool {
true
}
fn visit_function_decl(&mut self, _f: &FunctionDecl) -> bool {
true
}
fn visit_var_decl(&mut self, _v: &VarDecl) -> bool {
true
}
fn visit_stmt(&mut self, _s: &Stmt) -> bool {
true
}
fn visit_expr(&mut self, _e: &Expr) -> bool {
true
}
fn visit_type(&mut self, _t: &QualType) -> bool {
true
}
}
pub fn walk_translation_unit<V: AstVisitor>(tu: &TranslationUnit, visitor: &mut V) {
if !visitor.visit_translation_unit(tu) {
return;
}
for decl in &tu.decls {
walk_decl(decl, visitor);
}
}
pub fn walk_decl<V: AstVisitor>(decl: &Decl, visitor: &mut V) {
if !visitor.visit_decl(decl) {
return;
}
match decl {
Decl::Function(f) => walk_function_decl(f, visitor),
Decl::Variable(v) => walk_var_decl(v, visitor),
Decl::Typedef(_) | Decl::Struct(_) | Decl::Enum(_) | Decl::EnumVariant(_) => {}
}
}
pub fn walk_function_decl<V: AstVisitor>(f: &FunctionDecl, visitor: &mut V) {
if !visitor.visit_function_decl(f) {
return;
}
visitor.visit_type(&f.ret_ty);
for param in &f.params {
walk_var_decl(param, visitor);
}
if let Some(ref body) = f.body {
walk_compound_stmt(body, visitor);
}
}
pub fn walk_var_decl<V: AstVisitor>(v: &VarDecl, visitor: &mut V) {
if !visitor.visit_var_decl(v) {
return;
}
visitor.visit_type(&v.ty);
if let Some(ref init) = v.init {
walk_expr(init, visitor);
}
}
pub fn walk_stmt<V: AstVisitor>(stmt: &Stmt, visitor: &mut V) {
if !visitor.visit_stmt(stmt) {
return;
}
match stmt {
Stmt::Compound(c) => walk_compound_stmt(c, visitor),
Stmt::Return(Some(e)) => walk_expr(e, visitor),
Stmt::Return(None) => {}
Stmt::If { cond, then, els } => {
walk_expr(cond, visitor);
walk_stmt(then, visitor);
if let Some(e) = els {
walk_stmt(e, visitor);
}
}
Stmt::While { cond, body } => {
walk_expr(cond, visitor);
walk_stmt(body, visitor);
}
Stmt::DoWhile { body, cond } => {
walk_stmt(body, visitor);
walk_expr(cond, visitor);
}
Stmt::For {
init,
cond,
incr,
body,
} => {
if let Some(i) = init {
walk_stmt(i, visitor);
}
if let Some(c) = cond {
walk_expr(c, visitor);
}
if let Some(i) = incr {
walk_expr(i, visitor);
}
walk_stmt(body, visitor);
}
Stmt::Switch { expr, body } => {
walk_expr(expr, visitor);
walk_stmt(body, visitor);
}
Stmt::Case { value, stmt } => {
walk_expr(value, visitor);
walk_stmt(stmt, visitor);
}
Stmt::Default { stmt } => {
walk_stmt(stmt, visitor);
}
Stmt::Goto { .. } | Stmt::Break | Stmt::Continue | Stmt::Null => {}
Stmt::Label { stmt, .. } => {
walk_stmt(stmt, visitor);
}
Stmt::Expr(e) => walk_expr(e, visitor),
Stmt::Decl(d) => walk_var_decl(d, visitor),
}
}
pub fn walk_compound_stmt<V: AstVisitor>(c: &CompoundStmt, visitor: &mut V) {
for s in &c.stmts {
walk_stmt(s, visitor);
}
}
pub fn walk_expr<V: AstVisitor>(expr: &Expr, visitor: &mut V) {
if !visitor.visit_expr(expr) {
return;
}
match expr {
Expr::IntLiteral(_)
| Expr::UIntLiteral(..)
| Expr::FloatLiteral(_)
| Expr::DoubleLiteral(_)
| Expr::CharLiteral(_)
| Expr::StringLiteral(_)
| Expr::Ident(_) => {}
Expr::Unary(_, e) => walk_expr(e, visitor),
Expr::SizeOf(e) | Expr::AlignOf(e) => walk_expr(e, visitor),
Expr::SizeOfType(_) | Expr::AlignOfType(_) => {}
Expr::Cast(ty, e) => {
visitor.visit_type(ty);
walk_expr(e, visitor);
}
Expr::Binary(_, lhs, rhs) | Expr::Assign(_, lhs, rhs) => {
walk_expr(lhs, visitor);
walk_expr(rhs, visitor);
}
Expr::Conditional(cond, then, els) => {
walk_expr(cond, visitor);
walk_expr(then, visitor);
walk_expr(els, visitor);
}
Expr::Call { callee, args } => {
walk_expr(callee, visitor);
for a in args {
walk_expr(a, visitor);
}
}
Expr::Subscript { base, index } => {
walk_expr(base, visitor);
walk_expr(index, visitor);
}
Expr::Member { base, .. } => {
walk_expr(base, visitor);
}
Expr::PostInc(e) | Expr::PostDec(e) | Expr::PreInc(e) | Expr::PreDec(e) => {
walk_expr(e, visitor)
}
Expr::CompoundLiteral(ty, init) => {
visitor.visit_type(ty);
walk_expr(init, visitor);
}
Expr::AggregateLiteral(vals) => {
for v in vals {
walk_expr(v, visitor);
}
}
}
}
#[derive(Debug, Clone)]
pub enum ExtendedTypeNode {
Atomic(Box<QualType>),
IncompleteArray(Box<QualType>),
VariableLengthArray(Box<QualType>, Box<Expr>),
Complex(Box<QualType>),
Vector(Box<QualType>, u64),
ExtVector(Box<QualType>, u64),
BlockPointer(Box<QualType>),
ObjCObjectPointer(Box<QualType>),
MemberPointer(Box<QualType>, Box<QualType>),
Pipe(Box<QualType>),
Attributed(Box<QualType>, TypeAttrKind),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeAttrKind {
Aligned(u64),
Packed,
TransparentUnion,
MayAlias,
Deprecated(String),
Unavailable(String),
NoDeref,
AddressSpace(u32),
ObjCGC,
ObjCOwnership,
NoReturn,
RegParm(u32),
Mode(String),
VectorSize(u64),
NoUniqueAddress,
Annotate(String),
}
impl ExtendedTypeNode {
pub fn kind_name(&self) -> &str {
match self {
Self::Atomic(_) => "Atomic",
Self::IncompleteArray(_) => "IncompleteArray",
Self::VariableLengthArray(_, _) => "VLA",
Self::Complex(_) => "Complex",
Self::Vector(_, _) => "Vector",
Self::ExtVector(_, _) => "ExtVector",
Self::BlockPointer(_) => "BlockPointer",
Self::ObjCObjectPointer(_) => "ObjCObjectPointer",
Self::MemberPointer(_, _) => "MemberPointer",
Self::Pipe(_) => "Pipe",
Self::Attributed(_, _) => "Attributed",
}
}
}
#[derive(Debug, Clone)]
pub enum ExtendedExpr {
GenericSelection(Box<Expr>, Vec<(QualType, Expr)>),
AtomicExpr(AtomicExprOp, Box<Expr>, Vec<Expr>),
OffsetOf(QualType, Vec<String>),
ChooseExpr(Box<Expr>, Box<Expr>, Box<Expr>),
StmtExpr(CompoundStmt),
AddrLabelExpr(String),
BlockExpr(BlockDecl),
OpaqueValueExpr(Box<Expr>, QualType),
StaticCast(QualType, Box<Expr>),
DynamicCast(QualType, Box<Expr>),
ConstCast(QualType, Box<Expr>),
ReinterpretCast(QualType, Box<Expr>),
TypeidExpr(Box<Expr>),
TypeidType(QualType),
ThrowExpr(Box<Expr>),
DefaultArgExpr(String, Box<Expr>),
DefaultInitExpr(String, Box<Expr>),
MaterializeTemporaryExpr(Box<Expr>),
PackExpansionExpr(Box<Expr>),
FoldExpr(FoldOperator, Box<Expr>),
NoexceptExpr(Box<Expr>),
SizeOfPackExpr(String),
LambdaExpr(LambdaDecl),
CoawaitExpr(Box<Expr>),
CoyieldExpr(Box<Expr>),
CoreturnExpr(Box<Expr>),
FunctionalCast(QualType, Box<Expr>),
UserDefinedLiteral(String, Box<Expr>),
DependentScopeMemberExpr(QualType, String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtomicExprOp {
Init,
Load,
Store,
Exchange,
CompareExchangeStrong,
CompareExchangeWeak,
FetchAdd,
FetchSub,
FetchAnd,
FetchOr,
FetchXor,
FetchNand,
ThreadFence,
SignalFence,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FoldOperator {
Add,
Sub,
Mul,
Div,
Mod,
Xor,
And,
Or,
Shl,
Shr,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
LogicAnd,
LogicOr,
Comma,
Assign,
}
#[derive(Debug, Clone)]
pub enum ExtendedDecl {
LinkageSpec(String, Vec<Decl>),
NamespaceAlias(String, String),
UsingDecl(String),
UsingDirectiveDecl(String),
UnresolvedUsingValueDecl(String, QualType),
UnresolvedUsingTypeDecl(String, QualType),
StaticAssert(Expr, String),
FriendDecl(Box<Decl>),
FriendFunctionDecl(FunctionDecl),
FriendClassDecl(String),
AccessSpec(AccessSpecifier),
EmptyDecl,
NamespaceDecl(String, Vec<Decl>),
AliasTemplateDecl(String, Vec<QualType>, QualType),
ConceptDecl(String, Expr),
RequiresDecl(Vec<Expr>),
DeductionGuideDecl(String, QualType),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessSpecifier {
Public,
Protected,
Private,
None,
}
impl AccessSpecifier {
pub fn as_str(&self) -> &'static str {
match self {
Self::Public => "public",
Self::Protected => "protected",
Self::Private => "private",
Self::None => "",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageDuration {
Automatic,
Static,
Thread,
Dynamic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageClass {
None,
Extern,
Static,
Auto,
Register,
Mutable,
ThreadLocal,
}
#[derive(Debug, Clone)]
pub enum ExtendedStmt {
NullStmt,
DeclStmt(Box<Decl>),
CapturedStmt {
captures: Vec<Capture>,
body: Box<Stmt>,
},
CoroutineBodyStmt {
promise_ctor: Option<Box<Expr>>,
initial_suspend: Option<Box<Expr>>,
final_suspend: Option<Box<Expr>>,
body: Box<Stmt>,
exception_handler: Option<Box<Stmt>>,
return_value: Option<Box<Expr>>,
return_value_on_alloc_failure: Option<Box<Expr>>,
},
TryStmt(Box<Stmt>, Vec<CatchStmt>),
CatchStmt(QualType, Box<Stmt>),
SEHTryStmt(Box<Stmt>),
SEHExceptStmt(Box<Stmt>),
SEHFinallyStmt(Box<Stmt>),
}
#[derive(Debug, Clone)]
pub struct Capture {
pub name: String,
pub kind: CaptureKind,
pub init_expr: Option<Box<Expr>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaptureKind {
Copy,
Reference,
This,
StarThis,
InitCapture,
}
#[derive(Debug, Clone)]
pub struct BlockDecl {
pub params: Vec<VarDecl>,
pub ret_ty: QualType,
pub body: CompoundStmt,
pub captures: Vec<Capture>,
}
impl BlockDecl {
pub fn new(ret_ty: QualType, params: Vec<VarDecl>, body: CompoundStmt) -> Self {
Self {
params,
ret_ty,
body,
captures: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct LambdaDecl {
pub captures: Vec<Capture>,
pub params: Vec<VarDecl>,
pub ret_ty: QualType,
pub body: CompoundStmt,
pub is_mutable: bool,
pub is_generic: bool,
pub is_constexpr: bool,
}
impl LambdaDecl {
pub fn new(
captures: Vec<Capture>,
params: Vec<VarDecl>,
ret_ty: QualType,
body: CompoundStmt,
) -> Self {
Self {
captures,
params,
ret_ty,
body,
is_mutable: false,
is_generic: false,
is_constexpr: false,
}
}
}
pub trait ExtendedAstVisitor: AstVisitor {
fn visit_atomic_type(&mut self, inner: &QualType) -> bool {
let _ = inner;
true
}
fn visit_extended_expr(&mut self, expr: &ExtendedExpr) -> bool {
let _ = expr;
true
}
fn visit_extended_decl(&mut self, decl: &ExtendedDecl) -> bool {
let _ = decl;
true
}
fn visit_extended_stmt(&mut self, stmt: &ExtendedStmt) -> bool {
let _ = stmt;
true
}
fn visit_generic_selection(
&mut self,
controlling: &Expr,
associations: &[(QualType, Expr)],
) -> bool {
let _ = (controlling, associations);
true
}
fn visit_atomic_expr(&mut self, op: AtomicExprOp, expr: &Expr, args: &[Expr]) -> bool {
let _ = (op, expr, args);
true
}
fn visit_cxx_named_cast(&mut self, kind: &str, ty: &QualType, expr: &Expr) -> bool {
let _ = (kind, ty, expr);
true
}
fn visit_offset_of(&mut self, ty: &QualType, fields: &[String]) -> bool {
let _ = (ty, fields);
true
}
fn visit_choose_expr(&mut self, cond: &Expr, then: &Expr, els: &Expr) -> bool {
let _ = (cond, then, els);
true
}
fn visit_stmt_expr(&mut self, body: &CompoundStmt) -> bool {
let _ = body;
true
}
fn visit_addr_label_expr(&mut self, label: &str) -> bool {
let _ = label;
true
}
fn visit_block_expr(&mut self, block: &BlockDecl) -> bool {
let _ = block;
true
}
fn visit_lambda_expr(&mut self, lambda: &LambdaDecl) -> bool {
let _ = lambda;
true
}
fn visit_captured_stmt(&mut self, captures: &[Capture], body: &Stmt) -> bool {
let _ = (captures, body);
true
}
fn visit_coroutine_body(&mut self, body: &CoroutineStmt) -> bool {
let _ = body;
true
}
fn visit_linkage_spec(&mut self, lang: &str, decls: &[Decl]) -> bool {
let _ = (lang, decls);
true
}
fn visit_using_decl(&mut self, name: &str) -> bool {
let _ = name;
true
}
fn visit_using_directive(&mut self, namespace: &str) -> bool {
let _ = namespace;
true
}
fn visit_static_assert(&mut self, cond: &Expr, msg: &str) -> bool {
let _ = (cond, msg);
true
}
fn visit_friend_decl(&mut self, decl: &Decl) -> bool {
let _ = decl;
true
}
fn visit_access_spec(&mut self, access: AccessSpecifier) -> bool {
let _ = access;
true
}
fn visit_namespace_decl(&mut self, name: &str, decls: &[Decl]) -> bool {
let _ = (name, decls);
true
}
}
#[derive(Debug, Clone)]
pub struct CoroutineStmt {
pub promise_ctor: Option<Box<Expr>>,
pub initial_suspend: Option<Box<Expr>>,
pub final_suspend: Option<Box<Expr>>,
pub body: Box<Stmt>,
}
pub fn walk_extended_expr<V: ExtendedAstVisitor>(expr: &ExtendedExpr, visitor: &mut V) -> bool {
if !visitor.visit_extended_expr(expr) {
return false;
}
match expr {
ExtendedExpr::GenericSelection(controlling, associations) => {
if !visitor.visit_generic_selection(controlling, associations) {
return false;
}
walk_expr(controlling, visitor);
for (ty, e) in associations {
visitor.visit_type(ty);
walk_expr(e, visitor);
}
}
ExtendedExpr::AtomicExpr(op, e, args) => {
if !visitor.visit_atomic_expr(*op, e, args) {
return false;
}
walk_expr(e, visitor);
for a in args {
walk_expr(a, visitor);
}
}
ExtendedExpr::OffsetOf(ty, fields) => {
visitor.visit_offset_of(ty, fields);
visitor.visit_type(ty);
}
ExtendedExpr::ChooseExpr(cond, then, els) => {
visitor.visit_choose_expr(cond, then, els);
walk_expr(cond, visitor);
walk_expr(then, visitor);
walk_expr(els, visitor);
}
ExtendedExpr::StmtExpr(body) => {
visitor.visit_stmt_expr(body);
walk_compound_stmt(body, visitor);
}
ExtendedExpr::AddrLabelExpr(label) => {
visitor.visit_addr_label_expr(label);
}
ExtendedExpr::BlockExpr(block) => {
visitor.visit_block_expr(block);
if !visitor.visit_type(&block.ret_ty) {
return false;
}
walk_compound_stmt(&block.body, visitor);
}
ExtendedExpr::StaticCast(ty, e)
| ExtendedExpr::DynamicCast(ty, e)
| ExtendedExpr::ConstCast(ty, e)
| ExtendedExpr::ReinterpretCast(ty, e) => {
let kind = match expr {
ExtendedExpr::StaticCast(_, _) => "static_cast",
ExtendedExpr::DynamicCast(_, _) => "dynamic_cast",
ExtendedExpr::ConstCast(_, _) => "const_cast",
ExtendedExpr::ReinterpretCast(_, _) => "reinterpret_cast",
_ => unreachable!(),
};
visitor.visit_cxx_named_cast(kind, ty, e);
visitor.visit_type(ty);
walk_expr(e, visitor);
}
ExtendedExpr::TypeidExpr(e) => walk_expr(e, visitor),
ExtendedExpr::TypeidType(ty) => {
visitor.visit_type(ty);
}
ExtendedExpr::ThrowExpr(e) => walk_expr(e, visitor),
ExtendedExpr::DefaultArgExpr(_, e)
| ExtendedExpr::DefaultInitExpr(_, e)
| ExtendedExpr::MaterializeTemporaryExpr(e)
| ExtendedExpr::PackExpansionExpr(e)
| ExtendedExpr::FunctionalCast(_, e)
| ExtendedExpr::UserDefinedLiteral(_, e) => {
walk_expr(e, visitor);
}
ExtendedExpr::DependentScopeMemberExpr(_, _) => {}
ExtendedExpr::NoexceptExpr(e) => walk_expr(e, visitor),
ExtendedExpr::FoldExpr(_, e) => walk_expr(e, visitor),
ExtendedExpr::LambdaExpr(lambda) => {
visitor.visit_lambda_expr(lambda);
if !visitor.visit_type(&lambda.ret_ty) {
return false;
}
walk_compound_stmt(&lambda.body, visitor);
}
ExtendedExpr::CoawaitExpr(e)
| ExtendedExpr::CoyieldExpr(e)
| ExtendedExpr::CoreturnExpr(e) => walk_expr(e, visitor),
ExtendedExpr::OpaqueValueExpr(e, _) => walk_expr(e, visitor),
ExtendedExpr::SizeOfPackExpr(_) => {}
}
true
}
pub fn walk_extended_decl<V: ExtendedAstVisitor>(decl: &ExtendedDecl, visitor: &mut V) -> bool {
if !visitor.visit_extended_decl(decl) {
return false;
}
match decl {
ExtendedDecl::LinkageSpec(lang, decls) => {
visitor.visit_linkage_spec(lang, decls);
for d in decls {
walk_decl(d, visitor);
}
}
ExtendedDecl::NamespaceAlias(_, _) => {}
ExtendedDecl::UsingDecl(name) => {
visitor.visit_using_decl(name);
}
ExtendedDecl::UsingDirectiveDecl(ns) => {
visitor.visit_using_directive(ns);
}
ExtendedDecl::UnresolvedUsingValueDecl(_, _)
| ExtendedDecl::UnresolvedUsingTypeDecl(_, _) => {}
ExtendedDecl::StaticAssert(cond, msg) => {
visitor.visit_static_assert(cond, msg);
walk_expr(cond, visitor);
}
ExtendedDecl::FriendDecl(d) => {
visitor.visit_friend_decl(d);
walk_decl(d, visitor);
}
ExtendedDecl::FriendFunctionDecl(_) | ExtendedDecl::FriendClassDecl(_) => {}
ExtendedDecl::AccessSpec(access) => {
visitor.visit_access_spec(*access);
}
ExtendedDecl::EmptyDecl => {}
ExtendedDecl::NamespaceDecl(name, decls) => {
visitor.visit_namespace_decl(name, decls);
for d in decls {
walk_decl(d, visitor);
}
}
ExtendedDecl::AliasTemplateDecl(_, _, _)
| ExtendedDecl::ConceptDecl(_, _)
| ExtendedDecl::RequiresDecl(_)
| ExtendedDecl::DeductionGuideDecl(_, _) => {}
}
true
}
pub fn walk_extended_stmt<V: ExtendedAstVisitor>(stmt: &ExtendedStmt, visitor: &mut V) -> bool {
if !visitor.visit_extended_stmt(stmt) {
return false;
}
match stmt {
ExtendedStmt::NullStmt => {}
ExtendedStmt::DeclStmt(d) => {
walk_decl(d, visitor);
}
ExtendedStmt::CapturedStmt { captures, body } => {
visitor.visit_captured_stmt(captures, body);
walk_stmt(body, visitor);
}
ExtendedStmt::CoroutineBodyStmt {
promise_ctor,
initial_suspend,
final_suspend,
body,
exception_handler,
return_value,
return_value_on_alloc_failure: _,
} => {
let coro = CoroutineStmt {
promise_ctor: promise_ctor.clone(),
initial_suspend: initial_suspend.clone(),
final_suspend: final_suspend.clone(),
body: body.clone(),
};
visitor.visit_coroutine_body(&coro);
if let Some(ctor) = promise_ctor {
walk_expr(ctor, visitor);
}
if let Some(suspend) = initial_suspend {
walk_expr(suspend, visitor);
}
if let Some(suspend) = final_suspend {
walk_expr(suspend, visitor);
}
walk_stmt(body, visitor);
if let Some(handler) = exception_handler {
walk_stmt(handler, visitor);
}
if let Some(ret) = return_value {
walk_expr(ret, visitor);
}
}
ExtendedStmt::TryStmt(body, handlers) => {
walk_stmt(body, visitor);
for handler in handlers {
visitor.visit_type(&handler.ty);
walk_stmt(&handler.body, visitor);
}
}
ExtendedStmt::CatchStmt(ty, body) => {
visitor.visit_type(ty);
walk_stmt(body, visitor);
}
ExtendedStmt::SEHTryStmt(body)
| ExtendedStmt::SEHExceptStmt(body)
| ExtendedStmt::SEHFinallyStmt(body) => {
walk_stmt(body, visitor);
}
}
true
}
#[derive(Debug, Clone)]
pub struct CatchStmt {
pub ty: QualType,
pub body: Box<Stmt>,
pub is_ellipsis: bool,
}
impl ExtendedExpr {
pub fn generic_sel(expr: Expr, assocs: Vec<(QualType, Expr)>) -> Self {
Self::GenericSelection(Box::new(expr), assocs)
}
pub fn static_cast(ty: QualType, expr: Expr) -> Self {
Self::StaticCast(ty, Box::new(expr))
}
pub fn dynamic_cast(ty: QualType, expr: Expr) -> Self {
Self::DynamicCast(ty, Box::new(expr))
}
pub fn const_cast(ty: QualType, expr: Expr) -> Self {
Self::ConstCast(ty, Box::new(expr))
}
pub fn reinterpret_cast(ty: QualType, expr: Expr) -> Self {
Self::ReinterpretCast(ty, Box::new(expr))
}
pub fn typeid_expr(expr: Expr) -> Self {
Self::TypeidExpr(Box::new(expr))
}
pub fn typeid_type(ty: QualType) -> Self {
Self::TypeidType(ty)
}
pub fn throw_expr(expr: Expr) -> Self {
Self::ThrowExpr(Box::new(expr))
}
pub fn stmt_expr(body: CompoundStmt) -> Self {
Self::StmtExpr(body)
}
pub fn addr_label(label: String) -> Self {
Self::AddrLabelExpr(label)
}
pub fn block_expr(block: BlockDecl) -> Self {
Self::BlockExpr(block)
}
pub fn lambda(lambda: LambdaDecl) -> Self {
Self::LambdaExpr(lambda)
}
pub fn pack_expansion(e: Expr) -> Self {
Self::PackExpansionExpr(Box::new(e))
}
pub fn noexcept_expr(e: Expr) -> Self {
Self::NoexceptExpr(Box::new(e))
}
pub fn fold_expr(op: FoldOperator, e: Expr) -> Self {
Self::FoldExpr(op, Box::new(e))
}
pub fn coawait(e: Expr) -> Self {
Self::CoawaitExpr(Box::new(e))
}
pub fn coyield(e: Expr) -> Self {
Self::CoyieldExpr(Box::new(e))
}
pub fn coreturn(e: Expr) -> Self {
Self::CoreturnExpr(Box::new(e))
}
pub fn offset_of(ty: QualType, fields: Vec<String>) -> Self {
Self::OffsetOf(ty, fields)
}
pub fn choose_expr(cond: Expr, then: Expr, els: Expr) -> Self {
Self::ChooseExpr(Box::new(cond), Box::new(then), Box::new(els))
}
pub fn opaque_value(e: Expr, ty: QualType) -> Self {
Self::OpaqueValueExpr(Box::new(e), ty)
}
pub fn materialize_temporary(e: Expr) -> Self {
Self::MaterializeTemporaryExpr(Box::new(e))
}
pub fn functional_cast(ty: QualType, e: Expr) -> Self {
Self::FunctionalCast(ty, Box::new(e))
}
pub fn user_defined_literal(name: String, e: Expr) -> Self {
Self::UserDefinedLiteral(name, Box::new(e))
}
}
impl ExtendedDecl {
pub fn linkage_spec(lang: String, decls: Vec<Decl>) -> Self {
Self::LinkageSpec(lang, decls)
}
pub fn using_decl(name: String) -> Self {
Self::UsingDecl(name)
}
pub fn using_directive(namespace: String) -> Self {
Self::UsingDirectiveDecl(namespace)
}
pub fn static_assert(cond: Expr, msg: String) -> Self {
Self::StaticAssert(cond, msg)
}
pub fn friend_decl(decl: Decl) -> Self {
Self::FriendDecl(Box::new(decl))
}
pub fn access_spec(access: AccessSpecifier) -> Self {
Self::AccessSpec(access)
}
pub fn namespace_decl(name: String, decls: Vec<Decl>) -> Self {
Self::NamespaceDecl(name, decls)
}
pub fn empty() -> Self {
Self::EmptyDecl
}
}
impl ExtendedStmt {
pub fn null_stmt() -> Self {
Self::NullStmt
}
pub fn decl_stmt(decl: Decl) -> Self {
Self::DeclStmt(Box::new(decl))
}
pub fn captured_stmt(captures: Vec<Capture>, body: Stmt) -> Self {
Self::CapturedStmt {
captures,
body: Box::new(body),
}
}
pub fn try_stmt(body: Stmt, handlers: Vec<CatchStmt>) -> Self {
Self::TryStmt(Box::new(body), handlers)
}
}
#[derive(Debug, Clone)]
pub enum AstMatcher {
Anything,
Kind(String),
Named(String),
OfType(QualType),
HasChildren(Vec<AstMatcher>),
HasDescendant(Box<AstMatcher>),
AllOf(Vec<AstMatcher>),
AnyOf(Vec<AstMatcher>),
Not(Box<AstMatcher>),
IntLiteral(i64),
FloatLiteral(f64),
StringLiteral(String),
BinaryOp(BinaryOp),
UnaryOp(UnaryOp),
AtLeast(usize, Box<AstMatcher>),
}
impl AstMatcher {
pub fn anything() -> Self {
AstMatcher::Anything
}
pub fn kind(kind: &str) -> Self {
AstMatcher::Kind(kind.to_string())
}
pub fn named(name: &str) -> Self {
AstMatcher::Named(name.to_string())
}
pub fn of_type(ty: QualType) -> Self {
AstMatcher::OfType(ty)
}
pub fn has_children(children: Vec<AstMatcher>) -> Self {
AstMatcher::HasChildren(children)
}
pub fn has_descendant(m: AstMatcher) -> Self {
AstMatcher::HasDescendant(Box::new(m))
}
pub fn all_of(matchers: Vec<AstMatcher>) -> Self {
AstMatcher::AllOf(matchers)
}
pub fn any_of(matchers: Vec<AstMatcher>) -> Self {
AstMatcher::AnyOf(matchers)
}
pub fn not(m: AstMatcher) -> Self {
AstMatcher::Not(Box::new(m))
}
pub fn int_literal(v: i64) -> Self {
AstMatcher::IntLiteral(v)
}
pub fn float_literal(v: f64) -> Self {
AstMatcher::FloatLiteral(v)
}
pub fn string_literal(s: &str) -> Self {
AstMatcher::StringLiteral(s.to_string())
}
pub fn binary_op(op: BinaryOp) -> Self {
AstMatcher::BinaryOp(op)
}
pub fn unary_op(op: UnaryOp) -> Self {
AstMatcher::UnaryOp(op)
}
pub fn at_least(n: usize, m: AstMatcher) -> Self {
AstMatcher::AtLeast(n, Box::new(m))
}
pub fn matches_decl(&self, decl: &Decl) -> bool {
match self {
AstMatcher::Anything => true,
AstMatcher::Kind(k) => {
let kind_str = match decl {
Decl::Function(_) => "Function",
Decl::Variable(_) => "Variable",
Decl::Typedef(_) => "Typedef",
Decl::Struct(_) => "Struct",
Decl::Enum(_) => "Enum",
Decl::EnumVariant(_) => "EnumVariant",
};
k == kind_str
}
AstMatcher::Named(name) => decl.name() == Some(name),
AstMatcher::OfType(ty) => self.type_matches_decl(ty, decl),
AstMatcher::AllOf(matchers) => matchers.iter().all(|m| m.matches_decl(decl)),
AstMatcher::AnyOf(matchers) => matchers.iter().any(|m| m.matches_decl(decl)),
AstMatcher::Not(m) => !m.matches_decl(decl),
_ => false,
}
}
pub fn matches_stmt(&self, stmt: &Stmt) -> bool {
match self {
AstMatcher::Anything => true,
AstMatcher::Kind(k) => {
let kind_str = match stmt {
Stmt::Compound(_) => "Compound",
Stmt::Return(_) => "Return",
Stmt::If { .. } => "If",
Stmt::While { .. } => "While",
Stmt::DoWhile { .. } => "DoWhile",
Stmt::For { .. } => "For",
Stmt::Switch { .. } => "Switch",
Stmt::Case { .. } => "Case",
Stmt::Default { .. } => "Default",
Stmt::Break => "Break",
Stmt::Continue => "Continue",
Stmt::Goto { .. } => "Goto",
Stmt::Label { .. } => "Label",
Stmt::Expr(_) => "Expr",
Stmt::Decl(_) => "Decl",
Stmt::Null => "Null",
};
k == kind_str
}
AstMatcher::AllOf(matchers) => matchers.iter().all(|m| m.matches_stmt(stmt)),
AstMatcher::AnyOf(matchers) => matchers.iter().any(|m| m.matches_stmt(stmt)),
AstMatcher::Not(m) => !m.matches_stmt(stmt),
AstMatcher::HasDescendant(m) => self.has_descendant_in_stmt(stmt, m),
_ => false,
}
}
pub fn matches_expr(&self, expr: &Expr) -> bool {
match self {
AstMatcher::Anything => true,
AstMatcher::Kind(k) => {
let kind_str = match expr {
Expr::IntLiteral(_) => "IntLiteral",
Expr::UIntLiteral(..) => "UIntLiteral",
Expr::FloatLiteral(_) => "FloatLiteral",
Expr::DoubleLiteral(_) => "DoubleLiteral",
Expr::CharLiteral(_) => "CharLiteral",
Expr::StringLiteral(_) => "StringLiteral",
Expr::Ident(_) => "Ident",
Expr::Unary(_, _) => "Unary",
Expr::SizeOf(_) => "SizeOf",
Expr::SizeOfType(_) => "SizeOfType",
Expr::AlignOf(_) => "AlignOf",
Expr::AlignOfType(_) => "AlignOfType",
Expr::Cast(_, _) => "Cast",
Expr::Binary(_, _, _) => "Binary",
Expr::Assign(_, _, _) => "Assign",
Expr::Conditional(..) => "Conditional",
Expr::Call { .. } => "Call",
Expr::Subscript { .. } => "Subscript",
Expr::Member { .. } => "Member",
Expr::PostInc(_) => "PostInc",
Expr::PostDec(_) => "PostDec",
Expr::PreInc(_) => "PreInc",
Expr::PreDec(_) => "PreDec",
Expr::CompoundLiteral(_, _) => "CompoundLiteral",
Expr::AggregateLiteral(_) => "AggregateLiteral",
};
k == kind_str
}
AstMatcher::Named(name) => {
if let Expr::Ident(n) = expr {
n == name
} else {
false
}
}
AstMatcher::IntLiteral(v) => {
if let Expr::IntLiteral(n) = expr {
n == v
} else {
false
}
}
AstMatcher::FloatLiteral(v) => {
if let Expr::FloatLiteral(n) = expr {
(*n - v).abs() < f64::EPSILON
} else if let Expr::DoubleLiteral(n) = expr {
(*n - v).abs() < f64::EPSILON
} else {
false
}
}
AstMatcher::StringLiteral(s) => {
if let Expr::StringLiteral(str_val) = expr {
str_val == s
} else {
false
}
}
AstMatcher::BinaryOp(op) => {
if let Expr::Binary(o, _, _) = expr {
o == op
} else {
false
}
}
AstMatcher::UnaryOp(op) => {
if let Expr::Unary(o, _) = expr {
o == op
} else {
false
}
}
AstMatcher::AllOf(matchers) => matchers.iter().all(|m| m.matches_expr(expr)),
AstMatcher::AnyOf(matchers) => matchers.iter().any(|m| m.matches_expr(expr)),
AstMatcher::Not(m) => !m.matches_expr(expr),
AstMatcher::HasChildren(matchers) => self.children_match_expr(expr, matchers),
_ => false,
}
}
fn type_matches_decl(&self, _ty: &QualType, decl: &Decl) -> bool {
match decl {
Decl::Function(f) => &f.ret_ty == _ty,
Decl::Variable(v) => &v.ty == _ty,
_ => false,
}
}
fn has_descendant_in_stmt(&self, stmt: &Stmt, m: &AstMatcher) -> bool {
match stmt {
Stmt::Compound(c) => c
.stmts
.iter()
.any(|s| m.matches_stmt(s) || self.has_descendant_in_stmt(s, m)),
Stmt::If { cond, then, els } => {
m.matches_expr(cond)
|| m.matches_stmt(then)
|| self.has_descendant_in_stmt(then, m)
|| els
.as_ref()
.map(|e| m.matches_stmt(e) || self.has_descendant_in_stmt(e, m))
.unwrap_or(false)
}
Stmt::While { cond, body } | Stmt::DoWhile { cond, body } => {
m.matches_expr(cond) || m.matches_stmt(body) || self.has_descendant_in_stmt(body, m)
}
Stmt::For {
init,
cond,
incr,
body,
} => {
init.as_ref()
.and_then(|s| match s.as_ref() {
Stmt::Expr(e) => Some(e.as_ref()),
_ => None,
})
.map(|e| m.matches_expr(e))
.unwrap_or(false)
|| cond.as_ref().map(|e| m.matches_expr(e)).unwrap_or(false)
|| incr.as_ref().map(|e| m.matches_expr(e)).unwrap_or(false)
|| m.matches_stmt(body)
|| self.has_descendant_in_stmt(body, m)
}
Stmt::Switch { expr, body } => {
m.matches_expr(expr) || m.matches_stmt(body) || self.has_descendant_in_stmt(body, m)
}
Stmt::Case { stmt: s, .. } => m.matches_stmt(s) || self.has_descendant_in_stmt(s, m),
Stmt::Label { stmt: s, .. } => m.matches_stmt(s) || self.has_descendant_in_stmt(s, m),
Stmt::Return(Some(e)) => m.matches_expr(e),
_ => false,
}
}
fn children_match_expr(&self, expr: &Expr, matchers: &[AstMatcher]) -> bool {
let children: Vec<&Expr> = match expr {
Expr::Binary(_, l, r) | Expr::Assign(_, l, r) => vec![l.as_ref(), r.as_ref()],
Expr::Unary(_, e) => vec![e.as_ref()],
Expr::Call { args, .. } => args.iter().collect(),
Expr::Subscript { base, index } => vec![base.as_ref(), index.as_ref()],
Expr::Member { base, .. } => vec![base.as_ref()],
Expr::Conditional(cond, then, els) => {
vec![cond.as_ref(), then.as_ref(), els.as_ref()]
}
Expr::Cast(_, e) => vec![e.as_ref()],
_ => vec![],
};
if children.len() != matchers.len() {
return false;
}
children
.iter()
.zip(matchers.iter())
.all(|(c, m)| m.matches_expr(c))
}
}
pub struct AstDumper {
indent: usize,
output: String,
}
impl AstDumper {
pub fn new() -> Self {
AstDumper {
indent: 0,
output: String::new(),
}
}
fn push_indent(&self) -> String {
" ".repeat(self.indent)
}
pub fn dump_translation_unit(&mut self, tu: &TranslationUnit) -> &str {
self.output.clear();
self.output
.push_str(&format!("TranslationUnit ({} decls)\n", tu.decls.len()));
self.indent = 1;
for decl in &tu.decls {
self.dump_decl(decl);
}
&self.output
}
pub fn dump_decl(&mut self, decl: &Decl) {
match decl {
Decl::Function(f) => {
self.output.push_str(&format!(
"{}FunctionDecl: {} -> {}\n",
self.push_indent(),
f.name,
f.ret_ty
));
if !f.params.is_empty() {
self.indent += 1;
for p in &f.params {
self.output.push_str(&format!(
"{}Param: {}: {}\n",
self.push_indent(),
p.name,
p.ty
));
}
self.indent -= 1;
}
if let Some(ref body) = f.body {
self.indent += 1;
self.dump_compound(body);
self.indent -= 1;
}
}
Decl::Variable(v) => {
self.output.push_str(&format!(
"{}VarDecl: {}: {}\n",
self.push_indent(),
v.name,
v.ty
));
if let Some(ref init) = v.init {
self.indent += 1;
self.dump_expr(init, "init");
self.indent -= 1;
}
}
Decl::Typedef(t) => {
self.output.push_str(&format!(
"{}TypedefDecl: {} = {}\n",
self.push_indent(),
t.name,
t.underlying
));
}
Decl::Struct(s) => {
let kind = if s.is_union { "union" } else { "struct" };
self.output.push_str(&format!(
"{}StructDecl({}): {}\n",
self.push_indent(),
kind,
s.name.as_deref().unwrap_or("<anonymous>")
));
self.indent += 1;
for field in &s.fields {
self.output.push_str(&format!(
"{}Field: {}: {}\n",
self.push_indent(),
field.name,
field.ty
));
}
self.indent -= 1;
}
Decl::Enum(e) => {
self.output.push_str(&format!(
"{}EnumDecl: {}\n",
self.push_indent(),
e.name.as_deref().unwrap_or("<anonymous>")
));
self.indent += 1;
for v in &e.variants {
self.output.push_str(&format!(
"{}Variant: {} = {}\n",
self.push_indent(),
v.name,
v.value
.map(|val| val.to_string())
.unwrap_or_else(|| "<implicit>".into())
));
}
self.indent -= 1;
}
Decl::EnumVariant(ev) => {
self.output.push_str(&format!(
"{}EnumVariantDecl: {} = {}\n",
self.push_indent(),
ev.name,
ev.value
.map(|v| v.to_string())
.unwrap_or_else(|| "<implicit>".into())
));
}
}
}
pub fn dump_compound(&mut self, cs: &CompoundStmt) {
self.output
.push_str(&format!("{}CompoundStmt:\n", self.push_indent()));
self.indent += 1;
for stmt in &cs.stmts {
self.dump_stmt(stmt);
}
self.indent -= 1;
}
pub fn dump_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::Compound(cs) => self.dump_compound(cs),
Stmt::Return(expr) => {
if let Some(e) = expr {
self.output
.push_str(&format!("{}Return:\n", self.push_indent()));
self.indent += 1;
self.dump_expr(e, "val");
self.indent -= 1;
} else {
self.output
.push_str(&format!("{}Return: void\n", self.push_indent()));
}
}
Stmt::If { cond, then, els } => {
self.output
.push_str(&format!("{}If:\n", self.push_indent()));
self.indent += 1;
self.dump_expr(cond, "cond");
self.output
.push_str(&format!("{}Then:\n", self.push_indent()));
self.indent += 1;
self.dump_stmt(then);
self.indent -= 1;
if let Some(e) = els {
self.output
.push_str(&format!("{}Else:\n", self.push_indent()));
self.indent += 1;
self.dump_stmt(e);
self.indent -= 1;
}
self.indent -= 1;
}
Stmt::While { cond, body } => {
self.output
.push_str(&format!("{}While:\n", self.push_indent()));
self.indent += 1;
self.dump_expr(cond, "cond");
self.dump_stmt(body);
self.indent -= 1;
}
Stmt::For {
init,
cond,
incr,
body,
} => {
self.output
.push_str(&format!("{}For:\n", self.push_indent()));
self.indent += 1;
if let Some(i) = init {
if let Stmt::Expr(e) = i.as_ref() {
self.dump_expr(e, "init");
}
}
if let Some(c) = cond {
self.dump_expr(c, "cond");
}
if let Some(i) = incr {
self.dump_expr(i, "incr");
}
self.dump_stmt(body);
self.indent -= 1;
}
Stmt::Switch { expr, body } => {
self.output
.push_str(&format!("{}Switch:\n", self.push_indent()));
self.indent += 1;
self.dump_expr(expr, "expr");
self.dump_stmt(body);
self.indent -= 1;
}
Stmt::Case { value, stmt } => {
self.output
.push_str(&format!("{}Case {}:\n", self.push_indent(), value));
self.indent += 1;
self.dump_stmt(stmt);
self.indent -= 1;
}
Stmt::Default { stmt } => {
self.output
.push_str(&format!("{}Default:\n", self.push_indent()));
self.indent += 1;
self.dump_stmt(stmt);
self.indent -= 1;
}
Stmt::Break => {
self.output
.push_str(&format!("{}Break\n", self.push_indent()));
}
Stmt::Continue => {
self.output
.push_str(&format!("{}Continue\n", self.push_indent()));
}
Stmt::Goto { label } => {
self.output
.push_str(&format!("{}Goto: {}\n", self.push_indent(), label));
}
Stmt::Label { name, stmt } => {
self.output
.push_str(&format!("{}Label: {}\n", self.push_indent(), name));
self.indent += 1;
self.dump_stmt(stmt);
self.indent -= 1;
}
Stmt::Expr(e) => self.dump_expr(e, "expr"),
Stmt::Decl(_) => {
self.output
.push_str(&format!("{}DeclStmt\n", self.push_indent()));
}
Stmt::Null => {
self.output
.push_str(&format!("{}NullStmt\n", self.push_indent()));
}
Stmt::DoWhile { cond, body } => {
self.output
.push_str(&format!("{}DoWhile:\n", self.push_indent()));
self.indent += 1;
self.dump_stmt(body);
self.dump_expr(cond, "cond");
self.indent -= 1;
}
}
}
pub fn dump_expr(&mut self, expr: &Expr, label: &str) {
match expr {
Expr::IntLiteral(v) => self.output.push_str(&format!(
"{}{}: IntLiteral({})\n",
self.push_indent(),
label,
v
)),
Expr::FloatLiteral(v) => self.output.push_str(&format!(
"{}{}: FloatLiteral({})\n",
self.push_indent(),
label,
v
)),
Expr::StringLiteral(s) => self.output.push_str(&format!(
"{}{}: StringLiteral(\"{}\")\n",
self.push_indent(),
label,
s
)),
Expr::Ident(n) => {
self.output
.push_str(&format!("{}{}: Ident({})\n", self.push_indent(), label, n))
}
Expr::Binary(op, l, r) => {
self.output.push_str(&format!(
"{}{}: Binary({})\n",
self.push_indent(),
label,
op.as_str()
));
self.indent += 1;
self.dump_expr(l, "lhs");
self.dump_expr(r, "rhs");
self.indent -= 1;
}
Expr::Unary(op, e) => {
self.output.push_str(&format!(
"{}{}: Unary({})\n",
self.push_indent(),
label,
op.as_str()
));
self.indent += 1;
self.dump_expr(e, "operand");
self.indent -= 1;
}
Expr::Call { callee, args } => {
self.output
.push_str(&format!("{}{}: Call\n", self.push_indent(), label));
self.indent += 1;
self.dump_expr(callee, "callee");
for (i, arg) in args.iter().enumerate() {
self.dump_expr(arg, &format!("arg{}", i));
}
self.indent -= 1;
}
Expr::Conditional(cond, then, els) => {
self.output
.push_str(&format!("{}{}: Conditional\n", self.push_indent(), label));
self.indent += 1;
self.dump_expr(cond, "cond");
self.dump_expr(then, "then");
self.dump_expr(els, "else");
self.indent -= 1;
}
Expr::Cast(ty, e) => {
self.output
.push_str(&format!("{}{}: Cast({})\n", self.push_indent(), label, ty));
self.indent += 1;
self.dump_expr(e, "operand");
self.indent -= 1;
}
_ => self
.output
.push_str(&format!("{}{}: {}\n", self.push_indent(), label, expr)),
}
}
pub fn finish(&self) -> &str {
&self.output
}
}
impl Default for AstDumper {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct AstComparison {
pub differences: Vec<String>,
}
impl AstComparison {
pub fn new() -> Self {
AstComparison {
differences: Vec::new(),
}
}
pub fn is_equal(&self) -> bool {
self.differences.is_empty()
}
pub fn compare_exprs(&mut self, a: &Expr, b: &Expr, path: &str) -> bool {
use std::mem::discriminant;
if discriminant(a) != discriminant(b) {
self.differences
.push(format!("{}: variant mismatch {:?} vs {:?}", path, a, b));
return false;
}
match (a, b) {
(Expr::IntLiteral(va), Expr::IntLiteral(vb)) => {
if va != vb {
self.differences
.push(format!("{}: IntLiteral {} vs {}", path, va, vb));
return false;
}
}
(Expr::UIntLiteral(va, _), Expr::UIntLiteral(vb, _)) => {
if va != vb {
self.differences
.push(format!("{}: UIntLiteral {} vs {}", path, va, vb));
return false;
}
}
(Expr::FloatLiteral(va), Expr::FloatLiteral(vb)) => {
if (va - vb).abs() > f64::EPSILON {
self.differences
.push(format!("{}: FloatLiteral {} vs {}", path, va, vb));
return false;
}
}
(Expr::StringLiteral(sa), Expr::StringLiteral(sb)) => {
if sa != sb {
self.differences
.push(format!("{}: StringLiteral \"{}\" vs \"{}\"", path, sa, sb));
return false;
}
}
(Expr::Ident(na), Expr::Ident(nb)) => {
if na != nb {
self.differences
.push(format!("{}: Ident {} vs {}", path, na, nb));
return false;
}
}
(Expr::Binary(op_a, la, ra), Expr::Binary(op_b, lb, rb)) => {
if op_a != op_b {
self.differences
.push(format!("{}: Binary op {:?} vs {:?}", path, op_a, op_b));
return false;
}
return self.compare_exprs(la, lb, &format!("{}/lhs", path))
&& self.compare_exprs(ra, rb, &format!("{}/rhs", path));
}
(Expr::Unary(op_a, ea), Expr::Unary(op_b, eb)) => {
if op_a != op_b {
self.differences
.push(format!("{}: Unary op {:?} vs {:?}", path, op_a, op_b));
return false;
}
return self.compare_exprs(ea, eb, &format!("{}/operand", path));
}
(
Expr::Call {
callee: ca,
args: aa,
},
Expr::Call {
callee: cb,
args: ab,
},
) => {
if aa.len() != ab.len() {
self.differences.push(format!(
"{}: Call arg count {} vs {}",
path,
aa.len(),
ab.len()
));
return false;
}
if !self.compare_exprs(ca, cb, &format!("{}/callee", path)) {
return false;
}
for (i, (a_arg, b_arg)) in aa.iter().zip(ab.iter()).enumerate() {
if !self.compare_exprs(a_arg, b_arg, &format!("{}/arg{}", path, i)) {
return false;
}
}
return true;
}
_ => {
return true;
}
}
true
}
pub fn compare_function_decls(&mut self, a: &FunctionDecl, b: &FunctionDecl) -> bool {
let mut ok = true;
if a.name != b.name {
self.differences
.push(format!("Function name: {} vs {}", a.name, b.name));
ok = false;
}
if a.ret_ty != b.ret_ty {
self.differences.push(format!(
"Function {} return type: {} vs {}",
a.name, a.ret_ty, b.ret_ty
));
ok = false;
}
if a.params.len() != b.params.len() {
self.differences.push(format!(
"Function {} param count: {} vs {}",
a.name,
a.params.len(),
b.params.len()
));
ok = false;
}
ok
}
}
impl Default for AstComparison {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceLocation {
pub line: u32,
pub column: u32,
pub offset: u32,
}
impl SourceLocation {
pub fn new(line: u32, column: u32, offset: u32) -> Self {
SourceLocation {
line,
column,
offset,
}
}
pub fn unknown() -> Self {
SourceLocation {
line: 0,
column: 0,
offset: 0,
}
}
pub fn is_valid(&self) -> bool {
self.line > 0
}
pub fn to_string(&self) -> String {
if self.is_valid() {
format!("{}:{}:{}", self.line, self.column, self.offset)
} else {
"<unknown>".to_string()
}
}
}
impl std::fmt::Display for SourceLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
#[derive(Debug, Clone)]
pub struct AstVerification {
pub valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl AstVerification {
pub fn new() -> Self {
AstVerification {
valid: true,
errors: vec![],
warnings: vec![],
}
}
pub fn add_error(&mut self, msg: &str) {
self.valid = false;
self.errors.push(msg.to_string());
}
pub fn add_warning(&mut self, msg: &str) {
self.warnings.push(msg.to_string());
}
}
impl Default for AstVerification {
fn default() -> Self {
Self::new()
}
}
pub fn verify_ast_invariants(tu: &TranslationUnit) -> AstVerification {
let mut result = AstVerification::new();
for decl in &tu.decls {
match decl {
Decl::Function(f) => verify_function_invariants(f, &mut result),
Decl::Variable(v) => verify_variable_invariants(v, &mut result),
Decl::Struct(s) => verify_struct_invariants(s, &mut result),
Decl::Enum(e) => verify_enum_invariants(e, &mut result),
_ => {}
}
}
result
}
fn verify_function_invariants(f: &FunctionDecl, result: &mut AstVerification) {
if f.name.is_empty() {
result.add_error(&format!("Function has empty name"));
}
if *f.ret_ty.base == TypeNode::Void {
}
for param in &f.params {
if param.name.is_empty() {
result.add_warning(&format!("Function '{}' has unnamed parameter", f.name));
}
}
if let Some(ref body) = f.body {
verify_compound_invariants(body, &f.ret_ty, result);
}
}
fn verify_variable_invariants(v: &VarDecl, result: &mut AstVerification) {
if v.name.is_empty() {
result.add_error("Variable has empty name");
}
if v.is_extern && v.init.is_some() {
result.add_warning(&format!(
"Extern variable '{}' has initializer (non-standard)",
v.name
));
}
}
fn verify_struct_invariants(s: &StructDecl, result: &mut AstVerification) {
if s.name.as_ref().map_or(true, |n| n.is_empty()) {
result.add_error("Struct has empty name");
}
for field in &s.fields {
if field.name.is_empty() {
result.add_warning("Struct has unnamed field");
}
}
}
fn verify_enum_invariants(e: &EnumDecl, result: &mut AstVerification) {
if e.name.as_ref().map_or(true, |n| n.is_empty()) {
result.add_error("Enum has empty name");
}
if e.variants.is_empty() {
result.add_warning(&format!(
"Enum '{}' has no variants",
e.name.as_deref().unwrap_or("<anonymous>")
));
}
}
fn verify_compound_invariants(
cs: &CompoundStmt,
expected_ret: &QualType,
result: &mut AstVerification,
) {
if cs.stmts.is_empty() {
result.add_warning("Compound statement is empty");
}
if *expected_ret.base != TypeNode::Void {
let has_return = cs.stmts.iter().any(|s| matches!(s, Stmt::Return(_)));
if !has_return && !cs.stmts.is_empty() {
result.add_warning("Non-void function may not return a value on all paths");
}
}
for stmt in &cs.stmts {
verify_stmt_invariants(stmt, result);
}
}
fn verify_stmt_invariants(stmt: &Stmt, result: &mut AstVerification) {
match stmt {
Stmt::Compound(cs) => {
verify_compound_invariants(cs, &QualType::void(), result);
}
Stmt::If { cond, then, els } => {
verify_expr_invariants(cond, result);
verify_stmt_invariants(then, result);
if let Some(e) = els {
verify_stmt_invariants(e, result);
}
}
Stmt::While { cond, body } | Stmt::DoWhile { cond, body } => {
verify_expr_invariants(cond, result);
verify_stmt_invariants(body, result);
}
Stmt::For {
init,
cond,
incr,
body,
} => {
if let Some(i) = init {
if let Stmt::Expr(e) = i.as_ref() {
verify_expr_invariants(e, result);
}
}
if let Some(c) = cond {
verify_expr_invariants(c, result);
}
if let Some(i) = incr {
verify_expr_invariants(i, result);
}
verify_stmt_invariants(body, result);
}
Stmt::Switch { expr, body } => {
verify_expr_invariants(expr, result);
verify_stmt_invariants(body, result);
}
Stmt::Case { stmt: s, .. } | Stmt::Label { stmt: s, .. } => {
verify_stmt_invariants(s, result);
}
Stmt::Expr(e) => verify_expr_invariants(e, result),
_ => {}
}
}
fn verify_expr_invariants(expr: &Expr, result: &mut AstVerification) {
match expr {
Expr::Ident(name) => {
if name.is_empty() {
result.add_error("Identifier expression has empty name");
}
}
Expr::Call { callee, args } => {
verify_expr_invariants(callee, result);
for arg in args {
verify_expr_invariants(arg, result);
}
}
Expr::Binary(_, l, r) | Expr::Assign(_, l, r) => {
verify_expr_invariants(l, result);
verify_expr_invariants(r, result);
}
Expr::Unary(_, e) => verify_expr_invariants(e, result),
Expr::Conditional(cond, then, els) => {
verify_expr_invariants(cond, result);
verify_expr_invariants(then, result);
verify_expr_invariants(els, result);
}
Expr::Subscript { base, index } => {
verify_expr_invariants(base, result);
verify_expr_invariants(index, result);
}
Expr::Member { base, field, .. } => {
verify_expr_invariants(base, result);
if field.is_empty() {
result.add_error("Member expression has empty field name");
}
}
Expr::Cast(_, e) => verify_expr_invariants(e, result),
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extended_type_kinds() {
let atomic = ExtendedTypeNode::Atomic(Box::new(QualType::int()));
assert_eq!(atomic.kind_name(), "Atomic");
let vla = ExtendedTypeNode::VariableLengthArray(
Box::new(QualType::int()),
Box::new(Expr::IntLiteral(10)),
);
assert_eq!(vla.kind_name(), "VLA");
let vec = ExtendedTypeNode::Vector(Box::new(QualType::new(TypeNode::Float)), 4);
assert_eq!(vec.kind_name(), "Vector");
}
#[test]
fn test_extended_expr_constructors() {
let e = ExtendedExpr::static_cast(QualType::int(), Expr::IntLiteral(42));
match e {
ExtendedExpr::StaticCast(ty, _) => assert!(ty.is_integer()),
_ => panic!("expected StaticCast"),
}
let e2 = ExtendedExpr::stmt_expr(CompoundStmt::new());
match e2 {
ExtendedExpr::StmtExpr(_) => {}
_ => panic!("expected StmtExpr"),
}
}
#[test]
fn test_extended_decl_constructors() {
let d = ExtendedDecl::using_decl("Base::foo".into());
match d {
ExtendedDecl::UsingDecl(name) => assert_eq!(name, "Base::foo"),
_ => panic!("expected UsingDecl"),
}
let d2 = ExtendedDecl::static_assert(Expr::IntLiteral(1), "always true".into());
match d2 {
ExtendedDecl::StaticAssert(_, msg) => assert_eq!(msg, "always true"),
_ => panic!("expected StaticAssert"),
}
}
#[test]
fn test_extended_stmt_constructors() {
let s = ExtendedStmt::null_stmt();
match s {
ExtendedStmt::NullStmt => {}
_ => panic!("expected NullStmt"),
}
let s2 = ExtendedStmt::decl_stmt(Decl::Variable(VarDecl::new("x", QualType::int())));
match s2 {
ExtendedStmt::DeclStmt(_) => {}
_ => panic!("expected DeclStmt"),
}
}
#[test]
fn test_access_specifier_strings() {
assert_eq!(AccessSpecifier::Public.as_str(), "public");
assert_eq!(AccessSpecifier::Protected.as_str(), "protected");
assert_eq!(AccessSpecifier::Private.as_str(), "private");
assert_eq!(AccessSpecifier::None.as_str(), "");
}
#[test]
fn test_block_decl_creation() {
let block = BlockDecl::new(QualType::void(), vec![], CompoundStmt::new());
assert!(block.params.is_empty());
assert!(block.body.is_empty());
}
#[test]
fn test_lambda_decl_creation() {
let lambda = LambdaDecl::new(vec![], vec![], QualType::void(), CompoundStmt::new());
assert!(!lambda.is_mutable);
assert!(!lambda.is_generic);
}
#[test]
fn test_capture_kinds() {
let cap = Capture {
name: "x".into(),
kind: CaptureKind::Copy,
init_expr: None,
};
assert_eq!(cap.name, "x");
assert!(matches!(cap.kind, CaptureKind::Copy));
}
#[test]
fn test_fold_operator() {
let op = FoldOperator::Add;
match op {
FoldOperator::Add => {}
_ => panic!("expected Add"),
}
}
#[test]
fn test_atomic_expr_op() {
let op = AtomicExprOp::Load;
match op {
AtomicExprOp::Load => {}
_ => panic!("expected Load"),
}
}
#[test]
fn test_qualtype_creation() {
let ty = QualType::new(TypeNode::Int);
assert!(!ty.is_const);
assert!(!ty.is_volatile);
assert_eq!(format!("{}", ty), "int");
}
#[test]
fn test_qualtype_const() {
let ty = QualType::const_(TypeNode::Int);
assert!(ty.is_const);
assert_eq!(format!("{}", ty), "const int");
}
#[test]
fn test_qualtype_volatile() {
let ty = QualType::volatile(TypeNode::Char);
assert!(ty.is_volatile);
assert_eq!(format!("{}", ty), "volatile char");
}
#[test]
fn test_qualtype_pointer() {
let ty = QualType::pointer_to(QualType::int());
assert!(ty.is_pointer());
assert_eq!(format!("{}", ty), "int*");
}
#[test]
fn test_qualtype_const_char_ptr() {
let ty = QualType::const_char_ptr();
assert!(ty.is_pointer());
}
#[test]
fn test_qualtype_unqualified() {
let ty = QualType::const_(TypeNode::Int);
let unqual = ty.unqualified();
assert!(!unqual.is_const);
assert_eq!(format!("{}", unqual), "int");
}
#[test]
fn test_typenode_kind_names() {
assert_eq!(TypeNode::Void.kind_name(), "void");
assert_eq!(TypeNode::Int.kind_name(), "int");
assert_eq!(
TypeNode::Pointer(Box::new(QualType::int())).kind_name(),
"pointer"
);
assert_eq!(
TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10)
}
.kind_name(),
"array"
);
assert_eq!(
TypeNode::Struct {
name: None,
fields: vec![],
is_union: false
}
.kind_name(),
"struct"
);
assert_eq!(
TypeNode::Struct {
name: None,
fields: vec![],
is_union: true
}
.kind_name(),
"union"
);
assert_eq!(
TypeNode::Enum {
name: None,
variants: vec![]
}
.kind_name(),
"enum"
);
}
#[test]
fn test_typenode_size_bytes() {
assert_eq!(TypeNode::Char.size_bytes(), 1);
assert_eq!(TypeNode::Int.size_bytes(), 4);
assert_eq!(TypeNode::Long.size_bytes(), 8);
assert_eq!(TypeNode::Float.size_bytes(), 4);
assert_eq!(TypeNode::Double.size_bytes(), 8);
assert_eq!(TypeNode::Pointer(Box::new(QualType::int())).size_bytes(), 8);
assert_eq!(TypeNode::Void.size_bytes(), 0);
}
#[test]
fn test_typenode_is_integer() {
assert!(TypeNode::Int.is_integer());
assert!(TypeNode::UInt.is_integer());
assert!(TypeNode::Char.is_integer());
assert!(!TypeNode::Float.is_integer());
assert!(!TypeNode::Void.is_integer());
}
#[test]
fn test_typenode_is_unsigned() {
assert!(TypeNode::UInt.is_unsigned());
assert!(TypeNode::UChar.is_unsigned());
assert!(!TypeNode::Int.is_unsigned());
assert!(!TypeNode::Char.is_unsigned());
}
#[test]
fn test_typenode_display() {
assert_eq!(format!("{}", TypeNode::Void), "void");
assert_eq!(format!("{}", TypeNode::Int), "int");
assert_eq!(format!("{}", TypeNode::LongLong), "long long");
assert_eq!(
format!(
"{}",
TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(5)
}
),
"int[5]"
);
assert_eq!(
format!(
"{}",
TypeNode::Array {
elem: Box::new(QualType::int()),
size: None
}
),
"int[]"
);
}
#[test]
fn test_field_decl() {
let f = FieldDecl::new("x", QualType::int());
assert_eq!(f.name, "x");
assert_eq!(f.ty, QualType::int());
assert!(f.bit_width.is_none());
}
#[test]
fn test_field_decl_bitfield() {
let f = FieldDecl::new_bitfield("flags", QualType::new(TypeNode::UInt), 3);
assert_eq!(f.bit_width, Some(3));
}
#[test]
fn test_enum_variant() {
let v = EnumVariant::new("RED", Some(0));
assert_eq!(v.name, "RED");
assert_eq!(v.value, Some(0));
let v2 = EnumVariant::new("BLUE", None);
assert_eq!(v2.value, None);
}
#[test]
fn test_function_decl() {
let f = FunctionDecl::new("main", QualType::int());
assert_eq!(f.name, "main");
assert_eq!(f.ret_ty, QualType::int());
assert!(f.body.is_none());
assert!(!f.is_vararg);
}
#[test]
fn test_function_decl_display() {
let f = FunctionDecl::new("foo", QualType::void());
let s = format!("{}", f);
assert!(s.contains("foo"));
assert!(s.contains("void"));
}
#[test]
fn test_var_decl() {
let v = VarDecl::new("counter", QualType::int());
assert_eq!(v.name, "counter");
assert!(!v.is_global);
assert!(!v.is_static);
}
#[test]
fn test_var_decl_global() {
let v = VarDecl::new("g_counter", QualType::int()).global();
assert!(v.is_global);
assert_eq!(v.linkage, Linkage::External);
}
#[test]
fn test_var_decl_static() {
let v = VarDecl::new("s_counter", QualType::int()).static_();
assert!(v.is_static);
assert_eq!(v.linkage, Linkage::Internal);
}
#[test]
fn test_var_decl_init() {
let v = VarDecl::new("x", QualType::int()).with_init(Expr::IntLiteral(42));
assert!(v.init.is_some());
}
#[test]
fn test_typedef_decl() {
let t = TypedefDecl::new("size_t", QualType::new(TypeNode::ULong));
assert_eq!(t.name, "size_t");
let s = format!("{}", t);
assert!(s.contains("size_t"));
assert!(s.contains("typedef"));
}
#[test]
fn test_struct_decl() {
let s = StructDecl::new(Some("Point"), false).with_fields(vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
]);
assert_eq!(s.name.as_deref(), Some("Point"));
assert_eq!(s.fields.len(), 2);
assert!(!s.is_union);
}
#[test]
fn test_enum_decl() {
let e = EnumDecl::new(Some("Color")).with_variants(vec![
EnumVariant::new("RED", Some(0)),
EnumVariant::new("GREEN", Some(1)),
EnumVariant::new("BLUE", Some(2)),
]);
assert_eq!(e.name.as_deref(), Some("Color"));
assert_eq!(e.variants.len(), 3);
}
#[test]
fn test_decl_name() {
let f = Decl::Function(FunctionDecl::new("f", QualType::void()));
assert_eq!(f.name(), Some("f"));
let v = Decl::Variable(VarDecl::new("v", QualType::int()));
assert_eq!(v.name(), Some("v"));
let s = Decl::Struct(StructDecl::new(Some("S"), false));
assert_eq!(s.name(), Some("S"));
let anon = Decl::Struct(StructDecl::new(None, false));
assert_eq!(anon.name(), None);
}
#[test]
fn test_compound_stmt() {
let mut c = CompoundStmt::new();
assert!(c.is_empty());
c.push(Stmt::Null);
assert_eq!(c.len(), 1);
}
#[test]
fn test_stmt_helpers() {
let ret = Stmt::return_(Some(Expr::IntLiteral(0)));
assert!(matches!(ret, Stmt::Return(Some(_))));
let if_stmt = Stmt::if_(Expr::IntLiteral(1), Stmt::return_(None), None);
assert!(matches!(if_stmt, Stmt::If { .. }));
let expr_stmt = Stmt::expr(Expr::ident("foo"));
assert!(matches!(expr_stmt, Stmt::Expr(_)));
let null = Stmt::Null;
assert!(null.is_null());
}
#[test]
fn test_stmt_display() {
let ret = Stmt::return_(Some(Expr::IntLiteral(0)));
let s = format!("{}", ret);
assert!(s.contains("return"));
assert!(s.contains("0"));
}
#[test]
fn test_compound_stmt_display() {
let mut body = CompoundStmt::new();
body.push(Stmt::return_(Some(Expr::IntLiteral(42))));
let s = format!("{}", body);
assert!(s.contains("return"));
assert!(s.contains("42"));
}
#[test]
fn test_expr_ident() {
let e = Expr::ident("x");
assert_eq!(format!("{}", e), "x");
}
#[test]
fn test_expr_int_literal() {
let e = Expr::IntLiteral(42);
assert_eq!(format!("{}", e), "42");
}
#[test]
fn test_expr_binary() {
let e = Expr::binary(BinaryOp::Add, Expr::IntLiteral(1), Expr::IntLiteral(2));
assert_eq!(format!("{}", e), "(1 Add 2)");
}
#[test]
fn test_expr_unary() {
let e = Expr::unary(UnaryOp::Minus, Expr::IntLiteral(5));
assert_eq!(format!("{}", e), "Minus5");
}
#[test]
fn test_expr_call() {
let e = Expr::call(
Expr::ident("printf"),
vec![Expr::StringLiteral("hello".into())],
);
let s = format!("{}", e);
assert!(s.contains("printf"));
assert!(s.contains("hello"));
}
#[test]
fn test_expr_conditional() {
let e = Expr::Conditional(
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(2)),
Box::new(Expr::IntLiteral(3)),
);
let s = format!("{}", e);
assert!(s.contains("?"));
assert!(s.contains(":"));
}
#[test]
fn test_expr_member_access() {
let e = Expr::Member {
base: Box::new(Expr::ident("s")),
field: "x".into(),
is_arrow: false,
};
assert_eq!(format!("{}", e), "s.x");
let e2 = Expr::Member {
base: Box::new(Expr::ident("p")),
field: "y".into(),
is_arrow: true,
};
assert_eq!(format!("{}", e2), "p->y");
}
#[test]
fn test_expr_subscript() {
let e = Expr::Subscript {
base: Box::new(Expr::ident("arr")),
index: Box::new(Expr::IntLiteral(0)),
};
assert_eq!(format!("{}", e), "arr[0]");
}
#[test]
fn test_expr_char_literal() {
let e = Expr::CharLiteral('A');
assert_eq!(format!("{}", e), "'A'");
}
#[test]
fn test_expr_sizeof() {
let e = Expr::SizeOf(Box::new(Expr::ident("x")));
assert_eq!(format!("{}", e), "sizeof(x)");
let e2 = Expr::SizeOfType(QualType::int());
assert_eq!(format!("{}", e2), "sizeof(int)");
}
#[test]
fn test_binary_op_as_str() {
assert_eq!(BinaryOp::Add.as_str(), "+");
assert_eq!(BinaryOp::Eq.as_str(), "==");
assert_eq!(BinaryOp::LogicAnd.as_str(), "&&");
assert_eq!(BinaryOp::AddAssign.as_str(), "+=");
}
#[test]
fn test_binary_op_is_comparison() {
assert!(BinaryOp::Eq.is_comparison());
assert!(BinaryOp::Lt.is_comparison());
assert!(!BinaryOp::Add.is_comparison());
}
#[test]
fn test_binary_op_is_assignment() {
assert!(BinaryOp::Assign.is_assignment());
assert!(BinaryOp::AddAssign.is_assignment());
assert!(!BinaryOp::Add.is_assignment());
}
#[test]
fn test_binary_op_without_assign() {
assert_eq!(BinaryOp::AddAssign.without_assign(), Some(BinaryOp::Add));
assert_eq!(BinaryOp::Add.without_assign(), Some(BinaryOp::Add));
assert_eq!(BinaryOp::Assign.without_assign(), None);
assert_eq!(BinaryOp::Eq.without_assign(), Some(BinaryOp::Eq));
}
#[test]
fn test_unary_op_as_str() {
assert_eq!(UnaryOp::Plus.as_str(), "+");
assert_eq!(UnaryOp::Minus.as_str(), "-");
assert_eq!(UnaryOp::Not.as_str(), "!");
assert_eq!(UnaryOp::AddrOf.as_str(), "&");
assert_eq!(UnaryOp::Deref.as_str(), "*");
}
#[test]
fn test_translation_unit_empty() {
let tu = TranslationUnit::new("test.c");
assert!(tu.is_empty());
assert_eq!(tu.len(), 0);
}
#[test]
fn test_translation_unit_add_decl() {
let mut tu = TranslationUnit::new("test.c");
tu.add_decl(Decl::Function(FunctionDecl::new("main", QualType::int())));
assert_eq!(tu.len(), 1);
assert!(!tu.is_empty());
}
#[test]
fn test_translation_unit_functions() {
let mut tu = TranslationUnit::new("test.c");
tu.add_decl(Decl::Function(FunctionDecl::new("f1", QualType::void())));
tu.add_decl(Decl::Variable(VarDecl::new("x", QualType::int()).global()));
tu.add_decl(Decl::Function(FunctionDecl::new("f2", QualType::int())));
let funcs: Vec<_> = tu.functions().collect();
assert_eq!(funcs.len(), 2);
}
#[test]
fn test_translation_unit_display() {
let mut tu = TranslationUnit::new("example.c");
tu.add_decl(Decl::Function(FunctionDecl::new("main", QualType::int())));
let s = format!("{}", tu);
assert!(s.contains("example.c"));
assert!(s.contains("main"));
}
#[test]
fn test_linkage_as_str() {
assert_eq!(Linkage::External.as_str(), "external");
assert_eq!(Linkage::Internal.as_str(), "internal");
assert_eq!(Linkage::None.as_str(), "none");
}
#[test]
fn test_ast_walker_counts_nodes() {
struct CountingVisitor {
decls: usize,
stmts: usize,
exprs: usize,
types: usize,
}
impl AstVisitor for CountingVisitor {
fn visit_decl(&mut self, _d: &Decl) -> bool {
self.decls += 1;
true
}
fn visit_stmt(&mut self, _s: &Stmt) -> bool {
self.stmts += 1;
true
}
fn visit_expr(&mut self, _e: &Expr) -> bool {
self.exprs += 1;
true
}
fn visit_type(&mut self, _t: &QualType) -> bool {
self.types += 1;
true
}
}
let mut tu = TranslationUnit::new("count.c");
let mut body = CompoundStmt::new();
body.push(Stmt::return_(Some(Expr::IntLiteral(0))));
let f = FunctionDecl::new("count", QualType::int()).with_body(body);
tu.add_decl(Decl::Function(f));
let mut visitor = CountingVisitor {
decls: 0,
stmts: 0,
exprs: 0,
types: 0,
};
walk_translation_unit(&tu, &mut visitor);
assert_eq!(visitor.decls, 1);
assert!(
visitor.stmts >= 1,
"expected at least 1 stmt, got {}",
visitor.stmts
); assert!(
visitor.exprs >= 1,
"expected at least 1 expr, got {}",
visitor.exprs
); assert!(
visitor.types >= 1,
"expected at least 1 type, got {}",
visitor.types
); }
#[test]
fn test_function_decl_with_body_and_params() {
let mut body = CompoundStmt::new();
body.push(Stmt::return_(Some(Expr::binary(
BinaryOp::Add,
Expr::ident("a"),
Expr::ident("b"),
))));
let f = FunctionDecl::new("add", QualType::int())
.with_params(vec![
VarDecl::new("a", QualType::int()),
VarDecl::new("b", QualType::int()),
])
.with_body(body);
assert_eq!(f.params.len(), 2);
assert!(f.body.is_some());
let s = format!("{}", f);
assert!(s.contains("add"));
assert!(s.contains("int a"));
assert!(s.contains("int b"));
}
#[test]
fn test_qualtype_is_void() {
assert!(QualType::void().is_void());
assert!(!QualType::int().is_void());
}
#[test]
fn test_qualtype_is_integer() {
assert!(QualType::int().is_integer());
assert!(QualType::new(TypeNode::UChar).is_integer());
assert!(!QualType::new(TypeNode::Float).is_integer());
}
#[test]
fn test_qualtype_is_floating() {
assert!(QualType::new(TypeNode::Float).is_floating());
assert!(QualType::new(TypeNode::Double).is_floating());
assert!(!QualType::int().is_floating());
}
#[test]
fn test_qualtype_is_array() {
let arr = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
});
assert!(arr.is_array());
assert!(!arr.is_pointer());
}
#[test]
fn test_ast_matcher_anything_matches() {
let m = AstMatcher::anything();
let f = FunctionDecl::new("test", QualType::void());
let decl = Decl::Function(f);
assert!(m.matches_decl(&decl));
}
#[test]
fn test_ast_matcher_kind_function() {
let m = AstMatcher::kind("Function");
let f = FunctionDecl::new("test", QualType::void());
let decl = Decl::Function(f);
assert!(m.matches_decl(&decl));
}
#[test]
fn test_ast_matcher_kind_variable() {
let m = AstMatcher::kind("Variable");
let v = VarDecl::new("x", QualType::int());
let decl = Decl::Variable(v);
assert!(m.matches_decl(&decl));
}
#[test]
fn test_ast_matcher_kind_mismatch() {
let m = AstMatcher::kind("Function");
let v = VarDecl::new("x", QualType::int());
let decl = Decl::Variable(v);
assert!(!m.matches_decl(&decl));
}
#[test]
fn test_ast_matcher_named() {
let m = AstMatcher::named("add");
let f = FunctionDecl::new("add", QualType::int());
let decl = Decl::Function(f);
assert!(m.matches_decl(&decl));
}
#[test]
fn test_ast_matcher_all_of() {
let m = AstMatcher::all_of(vec![AstMatcher::kind("Function"), AstMatcher::named("foo")]);
let f = FunctionDecl::new("foo", QualType::void());
let decl = Decl::Function(f);
assert!(m.matches_decl(&decl));
}
#[test]
fn test_ast_matcher_any_of() {
let m = AstMatcher::any_of(vec![
AstMatcher::kind("Function"),
AstMatcher::kind("Variable"),
]);
let f = FunctionDecl::new("f", QualType::void());
assert!(m.matches_decl(&Decl::Function(f)));
let v = VarDecl::new("v", QualType::int());
assert!(m.matches_decl(&Decl::Variable(v)));
}
#[test]
fn test_ast_matcher_not() {
let m = AstMatcher::not(AstMatcher::kind("Function"));
let v = VarDecl::new("v", QualType::int());
assert!(m.matches_decl(&Decl::Variable(v)));
}
#[test]
fn test_ast_matcher_expr_int_literal() {
let m = AstMatcher::int_literal(42);
assert!(m.matches_expr(&Expr::IntLiteral(42)));
assert!(!m.matches_expr(&Expr::IntLiteral(99)));
}
#[test]
fn test_ast_matcher_expr_binary_op() {
let m = AstMatcher::binary_op(BinaryOp::Add);
let expr = Expr::binary(BinaryOp::Add, Expr::IntLiteral(1), Expr::IntLiteral(2));
assert!(m.matches_expr(&expr));
}
#[test]
fn test_ast_matcher_expr_binary_op_mismatch() {
let m = AstMatcher::binary_op(BinaryOp::Add);
let expr = Expr::binary(BinaryOp::Mul, Expr::IntLiteral(1), Expr::IntLiteral(2));
assert!(!m.matches_expr(&expr));
}
#[test]
fn test_ast_matcher_expr_ident() {
let m = AstMatcher::named("myvar");
assert!(m.matches_expr(&Expr::ident("myvar")));
assert!(!m.matches_expr(&Expr::ident("other")));
}
#[test]
fn test_ast_matcher_has_children_binary() {
let m =
AstMatcher::has_children(vec![AstMatcher::int_literal(1), AstMatcher::int_literal(2)]);
let expr = Expr::binary(BinaryOp::Add, Expr::IntLiteral(1), Expr::IntLiteral(2));
assert!(m.matches_expr(&expr));
}
#[test]
fn test_ast_matcher_stmt_kind_return() {
let m = AstMatcher::kind("Return");
let stmt = Stmt::return_(Some(Expr::IntLiteral(0)));
assert!(m.matches_stmt(&stmt));
}
#[test]
fn test_ast_matcher_stmt_kind_if() {
let m = AstMatcher::kind("If");
let stmt = Stmt::if_(
Expr::IntLiteral(1),
Stmt::return_(Some(Expr::IntLiteral(0))),
None,
);
assert!(m.matches_stmt(&stmt));
}
#[test]
fn test_ast_matcher_has_descendant() {
let inner = Stmt::compound(vec![Stmt::return_(Some(Expr::IntLiteral(42)))]);
let stmt = Stmt::if_(Expr::ident("x"), inner, None);
let m = AstMatcher::has_descendant(AstMatcher::kind("Return"));
assert!(m.matches_stmt(&stmt));
}
#[test]
fn test_ast_matcher_has_descendant_not_found() {
let stmt = Stmt::return_(Some(Expr::IntLiteral(0)));
let m = AstMatcher::has_descendant(AstMatcher::kind("If"));
assert!(!m.matches_stmt(&stmt));
}
#[test]
fn test_ast_matcher_at_least() {
let m = AstMatcher::at_least(2, AstMatcher::kind("Return"));
match m {
AstMatcher::AtLeast(n, _) => assert_eq!(n, 2),
_ => panic!("Expected AtLeast"),
}
}
#[test]
fn test_ast_dumper_translation_unit_empty() {
let mut dumper = AstDumper::new();
let tu = TranslationUnit::new("test.c");
let output = dumper.dump_translation_unit(&tu);
assert!(output.contains("TranslationUnit"));
assert!(output.contains("0 decls"));
}
#[test]
fn test_ast_dumper_translation_unit_with_decl() {
let mut dumper = AstDumper::new();
let mut tu = TranslationUnit::new("test.c");
let f = FunctionDecl::new("main", QualType::int());
tu.add_decl(Decl::Function(f));
let output = dumper.dump_translation_unit(&tu);
assert!(output.contains("main"));
assert!(output.contains("FunctionDecl"));
}
#[test]
fn test_ast_dumper_variable() {
let mut dumper = AstDumper::new();
let v = VarDecl::new("x", QualType::int()).with_init(Expr::IntLiteral(5));
dumper.dump_decl(&Decl::Variable(v));
let output = dumper.finish().to_string();
assert!(output.contains("VarDecl"));
assert!(output.contains("IntLiteral(5)"));
}
#[test]
fn test_ast_dumper_default() {
let dumper = AstDumper::default();
assert!(dumper.finish().is_empty());
}
#[test]
fn test_ast_comparison_new_is_equal() {
let cmp = AstComparison::new();
assert!(cmp.is_equal());
}
#[test]
fn test_ast_comparison_int_literals_equal() {
let mut cmp = AstComparison::new();
let a = Expr::IntLiteral(42);
let b = Expr::IntLiteral(42);
assert!(cmp.compare_exprs(&a, &b, "root"));
assert!(cmp.is_equal());
}
#[test]
fn test_ast_comparison_int_literals_different() {
let mut cmp = AstComparison::new();
let a = Expr::IntLiteral(42);
let b = Expr::IntLiteral(99);
assert!(!cmp.compare_exprs(&a, &b, "root"));
assert!(!cmp.is_equal());
}
#[test]
fn test_ast_comparison_binary_exprs_equal() {
let mut cmp = AstComparison::new();
let a = Expr::binary(BinaryOp::Add, Expr::IntLiteral(1), Expr::IntLiteral(2));
let b = Expr::binary(BinaryOp::Add, Expr::IntLiteral(1), Expr::IntLiteral(2));
assert!(cmp.compare_exprs(&a, &b, "root"));
assert!(cmp.is_equal());
}
#[test]
fn test_ast_comparison_binary_exprs_different_op() {
let mut cmp = AstComparison::new();
let a = Expr::binary(BinaryOp::Add, Expr::IntLiteral(1), Expr::IntLiteral(2));
let b = Expr::binary(BinaryOp::Sub, Expr::IntLiteral(1), Expr::IntLiteral(2));
assert!(!cmp.compare_exprs(&a, &b, "root"));
assert!(!cmp.is_equal());
assert!(cmp.differences.len() > 0);
}
#[test]
fn test_ast_comparison_call_exprs_equal() {
let mut cmp = AstComparison::new();
let a = Expr::call(Expr::ident("f"), vec![Expr::IntLiteral(1)]);
let b = Expr::call(Expr::ident("f"), vec![Expr::IntLiteral(1)]);
assert!(cmp.compare_exprs(&a, &b, "root"));
assert!(cmp.is_equal());
}
#[test]
fn test_ast_comparison_call_exprs_different_args() {
let mut cmp = AstComparison::new();
let a = Expr::call(Expr::ident("f"), vec![Expr::IntLiteral(1)]);
let b = Expr::call(Expr::ident("f"), vec![Expr::IntLiteral(2)]);
assert!(!cmp.compare_exprs(&a, &b, "root"));
assert!(!cmp.is_equal());
}
#[test]
fn test_ast_comparison_function_decls() {
let mut cmp = AstComparison::new();
let a = FunctionDecl::new("add", QualType::int());
let b = FunctionDecl::new("add", QualType::int());
assert!(cmp.compare_function_decls(&a, &b));
}
#[test]
fn test_ast_comparison_function_decls_different_name() {
let mut cmp = AstComparison::new();
let a = FunctionDecl::new("add", QualType::int());
let b = FunctionDecl::new("sub", QualType::int());
assert!(!cmp.compare_function_decls(&a, &b));
}
#[test]
fn test_source_location_new() {
let loc = SourceLocation::new(10, 5, 100);
assert_eq!(loc.line, 10);
assert_eq!(loc.column, 5);
assert_eq!(loc.offset, 100);
assert!(loc.is_valid());
}
#[test]
fn test_source_location_unknown() {
let loc = SourceLocation::unknown();
assert!(!loc.is_valid());
assert_eq!(loc.to_string(), "<unknown>");
}
#[test]
fn test_source_location_display() {
let loc = SourceLocation::new(1, 2, 3);
let s = format!("{}", loc);
assert!(s.contains("1:2:3"));
}
#[test]
fn test_ast_verification_new() {
let v = AstVerification::new();
assert!(v.valid);
assert!(v.errors.is_empty());
assert!(v.warnings.is_empty());
}
#[test]
fn test_ast_verification_add_error() {
let mut v = AstVerification::new();
v.add_error("test error");
assert!(!v.valid);
assert_eq!(v.errors.len(), 1);
}
#[test]
fn test_ast_verification_add_warning() {
let mut v = AstVerification::new();
v.add_warning("test warning");
assert!(v.valid); assert_eq!(v.warnings.len(), 1);
}
#[test]
fn test_verify_ast_empty_tu() {
let tu = TranslationUnit::new("empty.c");
let result = verify_ast_invariants(&tu);
assert!(result.valid);
assert!(result.errors.is_empty());
}
#[test]
fn test_verify_ast_function_no_name() {
let mut tu = TranslationUnit::new("test.c");
let f = FunctionDecl::new("", QualType::void());
tu.add_decl(Decl::Function(f));
let result = verify_ast_invariants(&tu);
assert!(!result.valid);
assert!(result.errors.iter().any(|e| e.contains("empty name")));
}
#[test]
fn test_verify_ast_variable_no_name() {
let mut tu = TranslationUnit::new("test.c");
let v = VarDecl::new("", QualType::int());
tu.add_decl(Decl::Variable(v));
let result = verify_ast_invariants(&tu);
assert!(!result.valid);
assert!(result.errors.iter().any(|e| e.contains("empty name")));
}
#[test]
fn test_verify_ast_struct_no_name() {
let mut tu = TranslationUnit::new("test.c");
let s = StructDecl::new("");
tu.add_decl(Decl::Struct(s));
let result = verify_ast_invariants(&tu);
assert!(!result.valid);
}
#[test]
fn test_verify_ast_enum_no_name() {
let mut tu = TranslationUnit::new("test.c");
let e = EnumDecl::new("");
tu.add_decl(Decl::Enum(e));
let result = verify_ast_invariants(&tu);
assert!(!result.valid);
}
#[test]
fn test_verify_ast_enum_no_variants() {
let mut tu = TranslationUnit::new("test.c");
let e = EnumDecl::new("Color");
tu.add_decl(Decl::Enum(e));
let result = verify_ast_invariants(&tu);
assert!(result.valid); assert!(result.warnings.iter().any(|w| w.contains("no variants")));
}
#[test]
fn test_verify_ast_valid_function() {
let mut tu = TranslationUnit::new("test.c");
let mut body = CompoundStmt::new();
body.push(Stmt::return_(Some(Expr::IntLiteral(0))));
let f = FunctionDecl::new("main", QualType::int())
.with_body(body)
.with_params(vec![VarDecl::new("argc", QualType::int())]);
tu.add_decl(Decl::Function(f));
let result = verify_ast_invariants(&tu);
assert!(result.valid);
}
#[test]
fn test_verify_ast_nonvoid_no_return() {
let mut tu = TranslationUnit::new("test.c");
let body = CompoundStmt::new(); let f = FunctionDecl::new("nonvoid", QualType::int()).with_body(body);
tu.add_decl(Decl::Function(f));
let result = verify_ast_invariants(&tu);
assert!(result.warnings.iter().any(|w| w.contains("may not return")));
}
#[test]
fn test_verify_ast_extern_with_init() {
let mut tu = TranslationUnit::new("test.c");
let v = VarDecl {
name: "ext".into(),
ty: QualType::int(),
init: Some(Expr::IntLiteral(42)),
linkage: Linkage::External,
is_global: true,
is_extern: true,
is_static: false,
};
tu.add_decl(Decl::Variable(v));
let result = verify_ast_invariants(&tu);
assert!(result.warnings.iter().any(|w| w.contains("Extern")));
}
#[test]
fn test_source_location_minimal() {
let loc = SourceLocation::new(1, 1, 0);
assert!(loc.is_valid());
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 1);
assert_eq!(loc.offset, 0);
}
}