Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
[package]
name = "cppshift"
version = "0.1.0"
version = "0.1.1"
authors = ["Jérémy HERGAULT", "Enzo PASQUALINI"]
description = "CPP parser and transpiler"
repository = "https://github.com/worldline/cppshift"
edition = "2024"
license = "Apache-2.0"

[features]
default = ["transpiler"]
ast = []
transpiler = ["ast", "dep:serde", "dep:syn", "dep:quote", "dep:proc-macro2"]

[dependencies]
miette = { version = "7", features = ["fancy"] }
thiserror = "2"
serde = { version = "1", features = ["derive"], optional = true }
syn = { version = "2", features = ["full", "extra-traits", "printing"], optional = true }
quote = { version = "1", optional = true }
proc-macro2 = { version = "1", optional = true }

[dev-dependencies]
tokio = { version = "1", features = ["macros"] }
Expand Down
44 changes: 44 additions & 0 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! Analogous to `syn::Expr`.

use crate::SourceSpan;
use crate::ast::ty::{FundamentalKind, Type};

use super::item::{Ident, Path};
use super::punct::Punctuated;
Expand All @@ -16,6 +17,49 @@ pub enum LitKind {
Char,
}

impl LitKind {
/// Check if the fundamental kind matches the literal kind
pub fn match_fundamental(&self, kind: FundamentalKind) -> bool {
match self {
LitKind::Integer => matches!(
kind,
FundamentalKind::Short
| FundamentalKind::Int
| FundamentalKind::Long
| FundamentalKind::LongLong
| FundamentalKind::SignedChar
| FundamentalKind::UnsignedChar
| FundamentalKind::UnsignedShort
| FundamentalKind::UnsignedInt
| FundamentalKind::UnsignedLong
| FundamentalKind::UnsignedLongLong
),
LitKind::Float => matches!(
kind,
FundamentalKind::Float | FundamentalKind::Double | FundamentalKind::LongDouble
),
LitKind::Char => matches!(
kind,
FundamentalKind::Char
| FundamentalKind::Wchar
| FundamentalKind::Char8
| FundamentalKind::Char16
| FundamentalKind::Char32
),
_ => false,
}
}

/// Check if the literal kind matches the type
pub fn match_type(&self, ty: &Type) -> bool {
match ty {
Type::Fundamental(fund) => self.match_fundamental(fund.kind),
Type::Qualified(qualified) => self.match_type(&qualified.ty),
_ => false,
}
}
}

/// Unary operator.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UnaryOp {
Expand Down
27 changes: 25 additions & 2 deletions src/ast/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
//! Each variant of [`Item`] corresponds to a top-level declaration in a C++ translation unit,
//! following the naming conventions of `syn::Item`.

use std::collections::LinkedList;
use std::fmt;

use crate::SourceSpan;
use crate::lex::Token;

Expand All @@ -22,6 +25,12 @@ pub struct Ident<'de> {
pub span: SourceSpan<'de>,
}

impl<'de> fmt::Display for Ident<'de> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.sym)
}
}

/// Visibility of a declaration.
///
/// In C++, visibility applies within class/struct bodies via access specifiers.
Expand Down Expand Up @@ -53,6 +62,20 @@ pub struct Path<'de> {
pub segments: Vec<PathSegment<'de>>,
}

impl<'de> fmt::Display for Path<'de> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.segments
.iter()
.map(|s| s.ident.sym)
.collect::<Vec<_>>()
.join("::")
)
}
}

/// A single segment of a path, analogous to `syn::PathSegment`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PathSegment<'de> {
Expand Down Expand Up @@ -414,9 +437,9 @@ pub struct ItemMacro<'de> {
}

/// Tokens not interpreted by the parser, analogous to `syn::Item::Verbatim`.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct ItemVerbatim<'de> {
pub tokens: Vec<Token<'de>>,
pub tokens: LinkedList<Token<'de>>,
}

/// A constructor declaration/definition.
Expand Down
156 changes: 156 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,4 +391,160 @@ mod tests {

assert_eq!(None, main_item_iter.next());
}

/// Test the ast parser with a simple class definition that includes a constructor, a member function, and a member variable
#[test]
fn class_header_ast() {
let class_header_src = r#"
#include <iostream>

/**
* This is a simple class definition for testing the AST parser.
* It includes a constructor, a member function, and a member variable.
*/
class MyClass: public MyMotherClass
{
MACRO_DEF(param1, 1);
typedef MyMotherClass BaseClass;

private: int member_var;
public: MyClass(int x) : member_var(x) {}
public: void member_function();
};
"#;

let class_header_file = parse_file(class_header_src).unwrap();
assert!(!class_header_file.items.is_empty());
let mut class_header_item_iter = class_header_file.items.iter();

let include_system_iostream = class_header_item_iter.next();
if let Some(Item::Include(ItemInclude { span, path })) = include_system_iostream {
assert_eq!(span.src(), "#include <iostream>");
if let IncludePath::System(path_span) = path {
assert_eq!(path_span.src(), "iostream");
} else {
panic!("Expected a system include path, got {:#?}", path);
}
} else {
panic!(
"Wrong item: expected an include directive, got {:#?}",
include_system_iostream
);
}

let class_header = class_header_item_iter.next();
if let Some(Item::Class(ItemClass {
attrs,
ident,
generics,
bases,
fields: Fields::Named(fields_named),
})) = class_header
{
assert_eq!(attrs.len(), 0);
assert_eq!(Some("MyClass"), ident.as_ref().map(|id| id.sym));
assert_eq!(&None, generics);

if bases.len() == 1
&& let Some(base) = bases.first()
{
assert_eq!(base.access, Visibility::Public);
assert!(!base.virtual_token);
assert_eq!(base.path.to_string(), "MyMotherClass");
} else {
panic!(
"Wrong class.bases: expected an inheritance, got {:#?}",
bases
);
}

let mut fields_named_iter = fields_named.members.iter();

// 1. MACRO_DEF(param1, 1); → Verbatim
let verbatim_item = fields_named_iter.next();
if let Some(Member::Item(item)) = verbatim_item
&& let Item::Verbatim(verbatim) = item.as_ref()
{
assert!(!verbatim.tokens.is_empty());
} else {
panic!("Expected a macro verbatim, got {:#?}", verbatim_item);
}

// 2. typedef MyMotherClass BaseClass; → Typedef
let typedef_item = fields_named_iter.next();
if let Some(Member::Item(item)) = typedef_item
&& let Item::Typedef(td) = item.as_ref()
{
assert_eq!(td.ident.sym, "BaseClass");
} else {
panic!("Expected a typedef, got {:#?}", typedef_item);
}

// 3. private: → AccessSpecifier
let access_private = fields_named_iter.next();
assert_eq!(
Some(&Member::AccessSpecifier(Visibility::Private)),
access_private,
);

// 4. int member_var; → Field
let field_member_var = fields_named_iter.next();
if let Some(Member::Field(field)) = field_member_var {
assert_eq!(Some("member_var"), field.ident.as_ref().map(|id| id.sym));
assert_eq!(field.default_value, None);
} else {
panic!("Expected a field, got {:#?}", field_member_var);
}

// 5. public: → AccessSpecifier
let access_public1 = fields_named_iter.next();
assert_eq!(
Some(&Member::AccessSpecifier(Visibility::Public)),
access_public1,
);

// 6. MyClass(int x) : member_var(x) {} → Constructor
let constructor = fields_named_iter.next();
if let Some(Member::Constructor(ctor)) = constructor {
assert_eq!(ctor.ident.sym, "MyClass");
assert!(!ctor.explicit_token);
assert!(!ctor.constexpr_token);
assert!(!ctor.noexcept_token);
assert!(!ctor.defaulted);
assert!(!ctor.deleted);
assert_eq!(ctor.inputs.len(), 1);
assert_eq!(ctor.member_init_list.len(), 1);
assert_eq!(ctor.member_init_list[0].member.sym, "member_var");
assert!(ctor.block.is_some());
} else {
panic!("Expected a constructor, got {:#?}", constructor);
}

// 7. public: → AccessSpecifier
let access_public2 = fields_named_iter.next();
assert_eq!(
Some(&Member::AccessSpecifier(Visibility::Public)),
access_public2,
);

// 8. void member_function(); → Method
let method = fields_named_iter.next();
if let Some(Member::Method(m)) = method {
assert_eq!(m.sig.ident.sym, "member_function");
assert!(m.block.is_none());
} else {
panic!("Expected a method, got {:#?}", method);
}

// No more members
assert_eq!(None, fields_named_iter.next());
} else {
panic!(
"Wrong item: expected a class definition, got {:#?}",
class_header
);
}

assert_eq!(None, class_header_item_iter.next());
}
}
Loading
Loading