diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 1b7412f4..4e7ae3ff 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -41,6 +41,7 @@ jobs: cargo fmt --manifest-path rules/Cargo.toml -- --check cargo fmt --manifest-path rule-preprocessor/Cargo.toml -- --check cargo fmt --manifest-path libcc2rs/Cargo.toml -- --check + cargo fmt --manifest-path libcc2rs-macros/Cargo.toml -- --check find tests -name '*.rs' -print0 | xargs -0 rustfmt --check - name: Check Rust lints @@ -48,3 +49,4 @@ jobs: cargo clippy --manifest-path rules/Cargo.toml --all-targets --all-features -- -Dwarnings cargo +nightly clippy --manifest-path rule-preprocessor/Cargo.toml --all-targets --all-features -- -Dwarnings cargo clippy --manifest-path libcc2rs/Cargo.toml --all-targets --all-features -- -Dwarnings + cargo clippy --manifest-path libcc2rs-macros/Cargo.toml --all-targets --all-features -- -Dwarnings diff --git a/CMakeLists.txt b/CMakeLists.txt index c53fd6b9..003bc860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ add_custom_target("format" COMMAND rustup run ${RUST_STABLE_VERSION} cargo fmt --manifest-path ${PROJECT_SOURCE_DIR}/rules/Cargo.toml COMMAND rustup run ${RUST_STABLE_VERSION} cargo fmt --manifest-path ${PROJECT_SOURCE_DIR}/rule-preprocessor/Cargo.toml COMMAND rustup run ${RUST_STABLE_VERSION} cargo fmt --manifest-path ${PROJECT_SOURCE_DIR}/libcc2rs/Cargo.toml + COMMAND rustup run ${RUST_STABLE_VERSION} cargo fmt --manifest-path ${PROJECT_SOURCE_DIR}/libcc2rs-macros/Cargo.toml DEPENDS "install-rust-toolchain" COMMENT "Running clang-format and cargo fmt on all source files" VERBATIM diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 5cad686c..12077b91 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -2704,14 +2704,31 @@ void Converter::EmitSwitchArm(clang::CompoundStmt *body, clang::SwitchCase *sc, } bool Converter::VisitSwitchStmt(clang::SwitchStmt *stmt) { - PushBreakTarget push(break_target_, BreakTarget::Switch); + bool has_fallthrough = SwitchHasFallthrough(stmt); + PushBreakTarget push(break_target_, has_fallthrough + ? BreakTarget::FallthroughSwitch + : BreakTarget::Switch); auto *body = clang::dyn_cast(stmt->getBody()); assert(body); - StrCat("'switch: {"); - StrCat(std::format("let __match_cond = {};", ToString(stmt->getCond()))); - StrCat("match __match_cond"); - StrCat("{"); + if (has_fallthrough) { + // Use the switch-with-fallthrough macro + StrCat("switch!"); + } else { + StrCat("'switch:"); + } + + PushParen switch_macro_paren(*this, has_fallthrough); + PushBrace switch_label_brace(*this, !has_fallthrough); + + if (has_fallthrough) { + StrCat("match", ToString(stmt->getCond())); + } else { + StrCat(std::format("let __match_cond = {};", ToString(stmt->getCond()))); + StrCat("match __match_cond"); + } + + PushBrace match_brace(*this); clang::SwitchCase *default_case = nullptr; for (auto *sc : GetTopLevelSwitchCases(stmt)) { @@ -2728,8 +2745,6 @@ bool Converter::VisitSwitchStmt(clang::SwitchStmt *stmt) { StrCat(R"( _ => {})"); } - StrCat("}"); - StrCat("}"); return false; } diff --git a/cpp2rust/converter/converter.h b/cpp2rust/converter/converter.h index a948d1a9..1678a643 100644 --- a/cpp2rust/converter/converter.h +++ b/cpp2rust/converter/converter.h @@ -497,7 +497,7 @@ class Converter : public clang::RecursiveASTVisitor { std::stack curr_for_inc_; std::stack curr_init_type_; - enum class BreakTarget { Loop, Switch }; + enum class BreakTarget { Loop, FallthroughSwitch, Switch }; std::stack break_target_; bool isSwitchBreak() const { diff --git a/cpp2rust/converter/converter_lib.cpp b/cpp2rust/converter/converter_lib.cpp index 88a4c9b1..689d583d 100644 --- a/cpp2rust/converter/converter_lib.cpp +++ b/cpp2rust/converter/converter_lib.cpp @@ -744,6 +744,36 @@ std::vector GetSwitchCaseBody(clang::CompoundStmt *body, return out; } +static bool SwitchCaseHasFallthrough(clang::Stmt *stmt) { + if (!stmt) { + return false; + } + if (auto *compound = clang::dyn_cast(stmt)) { + if (compound->body_empty()) { + return true; + } + return SwitchCaseHasFallthrough(compound->body_back()); + } + if (clang::isa(stmt) || + clang::isa(stmt) || + clang::isa(stmt)) { + return false; + } + return true; +} + +bool SwitchHasFallthrough(clang::SwitchStmt *stmt) { + if (auto *body = clang::dyn_cast(stmt->getBody())) { + for (auto top_level_case : GetTopLevelSwitchCases(stmt)) { + auto arm = GetSwitchCaseBody(body, top_level_case); + if (arm.empty() || SwitchCaseHasFallthrough(arm.back())) { + return true; + } + } + } + return false; +} + static std::string_view Trim(std::string_view s) { auto is_space = [](unsigned char c) { return std::isspace(c); }; auto b = std::find_if_not(s.begin(), s.end(), is_space); diff --git a/cpp2rust/converter/converter_lib.h b/cpp2rust/converter/converter_lib.h index c82dbb3c..0ba12b74 100644 --- a/cpp2rust/converter/converter_lib.h +++ b/cpp2rust/converter/converter_lib.h @@ -165,6 +165,8 @@ bool SwitchCaseContainsDefault(clang::SwitchCase *c); std::vector GetSwitchCaseBody(clang::CompoundStmt *body, clang::SwitchCase *head); +bool SwitchHasFallthrough(clang::SwitchStmt *stmt); + void Unwrap(std::string &s, std::string_view prefix, std::string_view suffix); } // namespace cpp2rust diff --git a/libcc2rs-macros/Cargo.toml b/libcc2rs-macros/Cargo.toml new file mode 100644 index 00000000..5a895659 --- /dev/null +++ b/libcc2rs-macros/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "libcc2rs-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] } + +[dev-dependencies] +trybuild = "1" diff --git a/libcc2rs-macros/src/goto.rs b/libcc2rs-macros/src/goto.rs new file mode 100644 index 00000000..916fd211 --- /dev/null +++ b/libcc2rs-macros/src/goto.rs @@ -0,0 +1,48 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +use proc_macro::TokenStream; +use syn::parse::{Parse, ParseStream}; +use syn::{parse_macro_input, Expr, Lifetime, Token}; + +use crate::state_machine::{Arm, GotoStateMachine, StateMachine}; + +pub fn expand(input: TokenStream) -> TokenStream { + let GotoBlockInput { arms } = parse_macro_input!(input as GotoBlockInput); + GotoStateMachine { + arms: arms + .into_iter() + .map(|a| Arm { + label: a.label.ident.to_string(), + body: a.body, + }) + .collect(), + } + .emit() + .into() +} + +struct GotoBlockInput { + arms: Vec, +} + +struct GotoArm { + label: Lifetime, + body: Expr, +} + +impl Parse for GotoBlockInput { + fn parse(input: ParseStream) -> syn::Result { + let mut arms = Vec::new(); + while !input.is_empty() { + let label: Lifetime = input.parse()?; + input.parse::]>()?; + let body: Expr = input.parse()?; + arms.push(GotoArm { label, body }); + if input.peek(Token![,]) { + input.parse::()?; + } + } + Ok(Self { arms }) + } +} diff --git a/libcc2rs-macros/src/lib.rs b/libcc2rs-macros/src/lib.rs new file mode 100644 index 00000000..10317cf4 --- /dev/null +++ b/libcc2rs-macros/src/lib.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +use proc_macro::TokenStream; + +mod goto; +mod state_machine; +mod switch; + +// switch!(match { +// [if ] => { /* body; may contain break or continue */ }, +// ... +// _ => , +// }); +// +// Desugars to a goto_block! with a synthetic dispatch arm prepended. +// +// goto_block! { +// '__dispatch => { +// match { +// => { __s = 1; continue '__sm; } +// ... +// _ => break '__sm, +// } +// }, +// '__c1 => { /* body_1 with `break` rewritten to `break '__sm` */ }, +// ... +// '__cN => { /* body_N with same rewrite */ }, +// }; +// +// __sm is the inner label used to describe the state machine insinde goto_block. See goto_block! +// for more info. + +#[proc_macro] +pub fn switch(input: TokenStream) -> TokenStream { + switch::expand(input) +} + +// goto_block! { +// '