Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,30 @@ serialization/deserialization libraries. See:
What you need to know right now is that `gen/cpp` is where our MusicXML types are coming from. Run
`make gen-cpp` to regenerate the C++ types.

## C++ coding rules

Do not use anonymous namespaces (`namespace { }`) anywhere in the codebase. All symbols must be in
a named namespace. Anonymous namespaces give internal linkage, which is correct for a normal
one-TU-per-file build but causes redefinition errors in unity builds (where multiple `.cpp` files
are compiled as a single translation unit). Use a named helper function or per-type name instead:

- For file-local helper functions: name them after the type or file, e.g. `tokenIsNameChar`,
`clampedTenths`.
- For file-local constants (arrays, string_views): use a per-type prefix, e.g. `kYesNoWire`,
`kSmuflAccidentalGlyphNamePrefix`.
- In code generator templates (`gen/cpp/templates/`): use `{{ident}}` to make names unique, e.g.
`k{{ident}}Wire`, `isNameChar{{ident}}`.

Unity builds can be tested with CMake's built-in support — no changes to `CMakeLists.txt` are
needed by contributors. To verify, configure with:

```
cmake -S . -B build/unity -DCMAKE_UNITY_BUILD=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=0
cmake --build build/unity --target mx
```

`BATCH_SIZE=0` puts all files in a target into one translation unit, which is the strictest test.

## Quality gates

Run `make fmt` to format. `make check` is the clang-format gate **only** — it builds and tests nothing.
Expand Down
11 changes: 3 additions & 8 deletions gen/cpp/templates/nmtoken_string.cpp.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@
namespace {{vars.namespace}}
{

namespace
{

// The ASCII subset of XML name characters (matches the schema's \c).
bool isNameChar(char c) noexcept
bool isNameChar{{ident}}(char c) noexcept
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' ||
c == '.' || c == ':' || c == '_';
}

} // namespace

{{ident}}::{{ident}}()
{
repair();
Expand All @@ -41,7 +36,7 @@ void {{ident}}::repair()
cleaned.reserve(m_value.size());
for (const char c : m_value)
{
if (isNameChar(c))
if (isNameChar{{ident}}(c))
{
cleaned.push_back(c);
}
Expand All @@ -61,7 +56,7 @@ bool {{ident}}::tryParse(std::string_view text, {{ident}} &out)
}
for (const char c : text)
{
if (!isNameChar(c))
if (!isNameChar{{ident}}(c))
{
return false;
}
Expand Down
11 changes: 3 additions & 8 deletions gen/cpp/templates/smufl_wavy.cpp.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,17 @@
namespace {{vars.namespace}}
{

namespace
{

constexpr std::string_view kWiggle = "wiggle";
constexpr std::string_view kGuitar = "guitar";
constexpr std::string_view kVibratoStroke = "VibratoStroke";

// The ASCII subset of XML name characters the schema's \c denotes.
bool isNameChar(char c) noexcept
bool isNameChar{{ident}}(char c) noexcept
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' ||
c == '.' || c == ':' || c == '_';
}

} // namespace

{{ident}}::{{ident}}()
{
repair();
Expand Down Expand Up @@ -55,7 +50,7 @@ void {{ident}}::repair()
cleaned.reserve(m_part.size());
for (const char c : m_part)
{
if (isNameChar(c))
if (isNameChar{{ident}}(c))
{
cleaned.push_back(c);
}
Expand Down Expand Up @@ -86,7 +81,7 @@ bool {{ident}}::tryParse(std::string_view text, {{ident}} &out)
const auto allNameChars = [](std::string_view s) {
for (const char c : s)
{
if (!isNameChar(c))
if (!isNameChar{{ident}}(c))
{
return false;
}
Expand Down
11 changes: 3 additions & 8 deletions src/private/mx/core/NameToken.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@
namespace mx::core
{

namespace
{

bool isNameChar(char c) noexcept
bool nameTokenIsNameChar(char c) noexcept
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
c == ':' || c == '_';
}

} // namespace

NameToken::NameToken()
{
repair();
Expand All @@ -42,7 +37,7 @@ void NameToken::repair()
cleaned.reserve(m_value.size());
for (const char c : m_value)
{
if (isNameChar(c))
if (nameTokenIsNameChar(c))
{
cleaned.push_back(c);
}
Expand All @@ -62,7 +57,7 @@ bool NameToken::tryParse(std::string_view text, NameToken &out)
}
for (const char c : text)
{
if (!isNameChar(c))
if (!nameTokenIsNameChar(c))
{
return false;
}
Expand Down
19 changes: 7 additions & 12 deletions src/private/mx/core/Token.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,19 @@
namespace mx::core
{

namespace
{

// The ASCII subset of the XML NCName character classes (the schema's
// vocabularies are ASCII; the strict parse is the only consumer, so the
// approximation can only under-accept).
bool isNameStart(char c) noexcept
bool tokenIsNameStart(char c) noexcept
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}

bool isNameChar(char c) noexcept
bool tokenIsNameChar(char c) noexcept
{
return isNameStart(c) || (c >= '0' && c <= '9') || c == '-' || c == '.';
return tokenIsNameStart(c) || (c >= '0' && c <= '9') || c == '-' || c == '.';
}

} // namespace

Token::Token()
{
repair();
Expand All @@ -49,14 +44,14 @@ void Token::repair()
cleaned.reserve(m_value.size());
for (const char c : m_value)
{
if (isNameChar(c))
if (tokenIsNameChar(c))
{
cleaned.push_back(c);
}
}
// An NCName cannot begin with a digit, '-', or '.'.
std::size_t start = 0;
while (start < cleaned.size() && !isNameStart(cleaned[start]))
while (start < cleaned.size() && !tokenIsNameStart(cleaned[start]))
{
++start;
}
Expand All @@ -70,13 +65,13 @@ void Token::repair()

bool Token::tryParse(std::string_view text, Token &out)
{
if (text.empty() || !isNameStart(text.front()))
if (text.empty() || !tokenIsNameStart(text.front()))
{
return false;
}
for (const char c : text)
{
if (!isNameChar(c))
if (!tokenIsNameChar(c))
{
return false;
}
Expand Down
11 changes: 3 additions & 8 deletions src/private/mx/core/generated/SmuflGlyphName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@
namespace mx::core
{

namespace
{

// The ASCII subset of XML name characters (matches the schema's \c).
bool isNameChar(char c) noexcept
bool isNameCharSmuflGlyphName(char c) noexcept
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
c == ':' || c == '_';
}

} // namespace

SmuflGlyphName::SmuflGlyphName()
{
repair();
Expand All @@ -41,7 +36,7 @@ void SmuflGlyphName::repair()
cleaned.reserve(m_value.size());
for (const char c : m_value)
{
if (isNameChar(c))
if (isNameCharSmuflGlyphName(c))
{
cleaned.push_back(c);
}
Expand All @@ -61,7 +56,7 @@ bool SmuflGlyphName::tryParse(std::string_view text, SmuflGlyphName &out)
}
for (const char c : text)
{
if (!isNameChar(c))
if (!isNameCharSmuflGlyphName(c))
{
return false;
}
Expand Down
11 changes: 3 additions & 8 deletions src/private/mx/core/generated/SmuflWavyLineGlyphName.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,17 @@
namespace mx::core
{

namespace
{

constexpr std::string_view kWiggle = "wiggle";
constexpr std::string_view kGuitar = "guitar";
constexpr std::string_view kVibratoStroke = "VibratoStroke";

// The ASCII subset of XML name characters the schema's \c denotes.
bool isNameChar(char c) noexcept
bool isNameCharSmuflWavyLineGlyphName(char c) noexcept
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
c == ':' || c == '_';
}

} // namespace

SmuflWavyLineGlyphName::SmuflWavyLineGlyphName()
{
repair();
Expand Down Expand Up @@ -55,7 +50,7 @@ void SmuflWavyLineGlyphName::repair()
cleaned.reserve(m_part.size());
for (const char c : m_part)
{
if (isNameChar(c))
if (isNameCharSmuflWavyLineGlyphName(c))
{
cleaned.push_back(c);
}
Expand Down Expand Up @@ -86,7 +81,7 @@ bool SmuflWavyLineGlyphName::tryParse(std::string_view text, SmuflWavyLineGlyphN
const auto allNameChars = [](std::string_view s) {
for (const char c : s)
{
if (!isNameChar(c))
if (!isNameCharSmuflWavyLineGlyphName(c))
{
return false;
}
Expand Down
Loading