@@ -1609,6 +1609,204 @@ bool path_is_under_any(const std::filesystem::path& p,
16091609 return false ;
16101610}
16111611
1612+ // A preprocessor condition's value as far as a source's text decides it: true,
1613+ // false, or unknown (nullopt). A name the compiler or the platform predefines
1614+ // (one beginning with `_`) is unknown; every other name is not defined, as no
1615+ // command defines the switches a library guards its own tools with (zlib's
1616+ // MAKECRCH, QuickJS's TEST).
1617+ namespace main_scan {
1618+
1619+ struct Expr {
1620+ std::string_view text;
1621+ std::size_t at = 0 ;
1622+ bool failed = false ;
1623+
1624+ void blank () { while (at < text.size () && (text[at] == ' ' || text[at] == ' \t ' )) ++at; }
1625+ bool take (std::string_view op) {
1626+ blank ();
1627+ if (text.substr (at, op.size ()) != op) return false ;
1628+ at += op.size ();
1629+ return true ;
1630+ }
1631+ std::string name () {
1632+ blank ();
1633+ std::size_t from = at;
1634+ while (at < text.size () && (std::isalnum (static_cast <unsigned char >(text[at])) || text[at] == ' _' )) ++at;
1635+ return std::string (text.substr (from, at - from));
1636+ }
1637+ static bool reserved (std::string_view n) { return !n.empty () && n.front () == ' _' ; }
1638+
1639+ std::optional<long long > primary () {
1640+ blank ();
1641+ if (take (" (" )) {
1642+ auto v = ternary ();
1643+ if (!take (" )" )) failed = true ;
1644+ return v;
1645+ }
1646+ if (take (" !" )) {
1647+ auto v = primary ();
1648+ return v ? std::optional<long long >(!*v) : std::nullopt ;
1649+ }
1650+ if (take (" -" )) {
1651+ auto v = primary ();
1652+ return v ? std::optional<long long >(-*v) : std::nullopt ;
1653+ }
1654+ if (at < text.size () && std::isdigit (static_cast <unsigned char >(text[at]))) {
1655+ long long v = 0 ;
1656+ const bool hex = text.substr (at, 2 ) == " 0x" || text.substr (at, 2 ) == " 0X" ;
1657+ if (hex) at += 2 ;
1658+ const auto from = at;
1659+ while (at < text.size () && std::isxdigit (static_cast <unsigned char >(text[at]))) ++at;
1660+ std::from_chars (text.data () + from, text.data () + at, v, hex ? 16 : 10 );
1661+ while (at < text.size () && std::isalpha (static_cast <unsigned char >(text[at]))) ++at; // 1L, 2u
1662+ return v;
1663+ }
1664+ const auto n = name ();
1665+ if (n.empty ()) { failed = true ; return std::nullopt ; }
1666+ if (n == " defined" ) {
1667+ const bool paren = take (" (" );
1668+ const auto m = name ();
1669+ if (paren && !take (" )" )) failed = true ;
1670+ return reserved (m) ? std::nullopt : std::optional<long long >(0 );
1671+ }
1672+ blank ();
1673+ if (at < text.size () && text[at] == ' (' ) { // a function-like macro: its value is the macro's
1674+ int depth = 0 ;
1675+ do {
1676+ depth += text[at] == ' (' ? 1 : text[at] == ' )' ? -1 : 0 ;
1677+ ++at;
1678+ } while (at < text.size () && depth > 0 );
1679+ return std::nullopt ;
1680+ }
1681+ return reserved (n) ? std::nullopt : std::optional<long long >(0 );
1682+ }
1683+ template <class Next , class Apply >
1684+ std::optional<long long > binary (Next next, std::initializer_list<std::string_view> ops, Apply apply) {
1685+ auto lhs = next ();
1686+ for (bool more = true ; more && !failed;) {
1687+ more = false ;
1688+ for (auto op : ops) {
1689+ blank ();
1690+ // `&&` is not `&`, `<=` is not `<`: the longer operators are listed first.
1691+ if (!take (op)) continue ;
1692+ lhs = apply (op, lhs, next ());
1693+ more = true ;
1694+ break ;
1695+ }
1696+ }
1697+ return lhs;
1698+ }
1699+ static std::optional<long long > arith (std::string_view op, std::optional<long long > a, std::optional<long long > b) {
1700+ if (!a || !b) return std::nullopt ;
1701+ if (op == " *" ) return *a * *b;
1702+ if (op == " /" ) return *b ? std::optional<long long >(*a / *b) : std::nullopt ;
1703+ if (op == " +" ) return *a + *b;
1704+ if (op == " -" ) return *a - *b;
1705+ if (op == " <=" ) return *a <= *b;
1706+ if (op == " >=" ) return *a >= *b;
1707+ if (op == " <" ) return *a < *b;
1708+ if (op == " >" ) return *a > *b;
1709+ if (op == " ==" ) return *a == *b;
1710+ return *a != *b;
1711+ }
1712+ std::optional<long long > product () { return binary ([&] { return primary (); }, {" *" , " /" }, arith); }
1713+ std::optional<long long > sum () { return binary ([&] { return product (); }, {" +" , " -" }, arith); }
1714+ std::optional<long long > relation () { return binary ([&] { return sum (); }, {" <=" , " >=" , " <" , " >" }, arith); }
1715+ std::optional<long long > equality () { return binary ([&] { return relation (); }, {" ==" , " !=" }, arith); }
1716+ std::optional<long long > conjunction () {
1717+ return binary ([&] { return equality (); }, {" &&" }, [](auto , auto a, auto b) -> std::optional<long long > {
1718+ if ((a && !*a) || (b && !*b)) return 0 ;
1719+ if (a && b) return 1 ;
1720+ return std::nullopt ;
1721+ });
1722+ }
1723+ std::optional<long long > disjunction () {
1724+ return binary ([&] { return conjunction (); }, {" ||" }, [](auto , auto a, auto b) -> std::optional<long long > {
1725+ if ((a && *a) || (b && *b)) return 1 ;
1726+ if (a && b) return 0 ;
1727+ return std::nullopt ;
1728+ });
1729+ }
1730+ std::optional<long long > ternary () {
1731+ auto c = disjunction ();
1732+ if (!take (" ?" )) return c;
1733+ auto t = ternary ();
1734+ if (!take (" :" )) failed = true ;
1735+ auto f = ternary ();
1736+ if (!c) return t == f ? t : std::nullopt ;
1737+ return *c ? t : f;
1738+ }
1739+ };
1740+
1741+ // The value of `#if <text>`; unknown when the text is not an expression this reads.
1742+ std::optional<bool > condition (std::string_view text) {
1743+ Expr e{text};
1744+ auto v = e.ternary ();
1745+ e.blank ();
1746+ if (e.failed || e.at != text.size () || !v) return std::nullopt ;
1747+ return *v != 0 ;
1748+ }
1749+
1750+ // One `#if` group: whether its enclosing region may be compiled, whether a
1751+ // branch of it was taken for certain, and whether one may have been.
1752+ struct Group {
1753+ bool parent = true ;
1754+ bool certainlyTaken = false ;
1755+ bool current = true ; // the current branch may be compiled
1756+ };
1757+
1758+ // The code of a source the preprocessor may compile: the lines of branches
1759+ // whose conditions are not false, directives dropped.
1760+ std::string compiled_lines (std::string_view code) {
1761+ std::string out;
1762+ std::vector<Group> groups;
1763+ auto active = [&] { return groups.empty () || (groups.back ().parent && groups.back ().current ); };
1764+ auto open = [&](std::optional<bool > c) {
1765+ const bool parent = active ();
1766+ groups.push_back ({parent, c.value_or (false ), c.value_or (true )});
1767+ };
1768+ auto next_branch = [&](std::optional<bool > c) {
1769+ if (groups.empty ()) return ;
1770+ auto & g = groups.back ();
1771+ g.current = !g.certainlyTaken && c.value_or (true );
1772+ g.certainlyTaken = g.certainlyTaken || c.value_or (false );
1773+ };
1774+ std::size_t at = 0 ;
1775+ while (at < code.size ()) {
1776+ auto end = code.find (' \n ' , at);
1777+ if (end == std::string_view::npos) end = code.size ();
1778+ std::string line (code.substr (at, end - at));
1779+ at = end + 1 ;
1780+ while (!line.empty () && line.back () == ' \\ ' && at < code.size ()) { // a continued directive
1781+ line.pop_back ();
1782+ end = code.find (' \n ' , at);
1783+ if (end == std::string_view::npos) end = code.size ();
1784+ line += code.substr (at, end - at);
1785+ at = end + 1 ;
1786+ }
1787+ const auto first = line.find_first_not_of (" \t " );
1788+ if (first == std::string::npos || line[first] != ' #' ) {
1789+ if (active ()) out += line + ' \n ' ;
1790+ continue ;
1791+ }
1792+ Expr d{std::string_view (line).substr (first + 1 )};
1793+ const auto word = d.name ();
1794+ const auto rest = std::string_view (line).substr (first + 1 + d.at );
1795+ if (word == " if" ) open (condition (rest));
1796+ else if (word == " ifdef" || word == " ifndef" ) {
1797+ Expr n{rest};
1798+ const auto m = n.name ();
1799+ std::optional<bool > isDefined = Expr::reserved (m) ? std::nullopt : std::optional<bool >(false );
1800+ open (word == " ifdef" ? isDefined : (isDefined ? std::optional<bool >(!*isDefined) : std::nullopt ));
1801+ } else if (word == " elif" ) next_branch (condition (rest));
1802+ else if (word == " else" ) next_branch (true );
1803+ else if (word == " endif" && !groups.empty ()) groups.pop_back ();
1804+ }
1805+ return out;
1806+ }
1807+
1808+ } // namespace main_scan
1809+
16121810bool source_defines_main (const std::filesystem::path& src) {
16131811 std::ifstream is (src);
16141812 if (!is) return false ;
@@ -1648,6 +1846,7 @@ bool source_defines_main(const std::filesystem::path& src) {
16481846 break ;
16491847 }
16501848 }
1849+ code = main_scan::compiled_lines (code);
16511850 auto isws = [](char c) {
16521851 return c == ' ' || c == ' \t ' || c == ' \n ' || c == ' \r ' || c == ' \f ' || c == ' \v ' ;
16531852 };
0 commit comments