Add Qbs syntax highlighting (SpartanJ/ecode#553).

Add the possibility from inherit patterns and repositories from other syntax definition.
This commit is contained in:
Martín Lucas Golini
2025-07-11 01:02:46 -03:00
parent ec4f122519
commit fa5aa4d006
9 changed files with 412 additions and 25 deletions

View File

@@ -98,7 +98,7 @@ class EE_API RegEx : public PatternMatcher {
bool mCached : 1 { false };
bool mFilterOutCaptures : 1 { false };
bool initWithOnigmo( std::string_view pattern, bool useCache );
bool initWithOnigumura( std::string_view pattern, bool useCache );
};
}} // namespace EE::System

View File

@@ -113,8 +113,9 @@ struct EE_API SyntaxPattern {
}
std::string_view getRepositoryName() const {
eeASSERT( isRepositoryInclude() );
return std::string_view{ patterns[1] }.substr( 1 );
eeASSERT( isRepositoryInclude() || isSourceInclude() );
return isSourceInclude() ? std::string_view{ patterns[1] }
: std::string_view{ patterns[1] }.substr( 1 );
}
inline bool checkIsIncludePattern() const {
@@ -144,13 +145,10 @@ struct EE_API SyntaxPattern {
struct EE_API SyntaxRepository {
std::vector<SyntaxPattern> patterns;
std::string syntax;
SyntaxRepository() {}
SyntaxRepository( std::vector<SyntaxPattern>&& patterns ) : patterns( std::move( patterns ) ) {}
SyntaxRepository( const std::string& syntax ) : syntax( syntax ) {}
};
struct EE_API SyntaxPreDefinition {

View File

@@ -74,8 +74,8 @@ RegEx::RegEx( std::string_view pattern, Uint32 options, bool useCache ) :
return;
}
if ( useCache && ( mOptions & Options::AllowFallback ) &&
!( mOptions & Options::UseOniguruma ) && RegExCache::instance()->isEnabled() &&
if ( useCache && RegExCache::instance()->isEnabled() && ( mOptions & Options::AllowFallback ) &&
!( mOptions & Options::UseOniguruma ) &&
( mCompiledPattern =
RegExCache::instance()->find( pattern, mOptions | Options::UseOniguruma ) ) ) {
mValid = true;
@@ -85,7 +85,7 @@ RegEx::RegEx( std::string_view pattern, Uint32 options, bool useCache ) :
}
if ( mOptions & Options::UseOniguruma ) {
initWithOnigmo( pattern, useCache );
initWithOnigumura( pattern, useCache );
return;
}
@@ -111,7 +111,7 @@ RegEx::RegEx( std::string_view pattern, Uint32 options, bool useCache ) :
pcre2_get_error_message( errornumber, buffer, sizeof( buffer ) );
mValid = false;
if ( mOptions & Options::AllowFallback ) {
initWithOnigmo( pattern, useCache );
initWithOnigumura( pattern, useCache );
} else {
Log::debug( "PCRE2 compilation failed at offset " + std::to_string( erroroffset ) +
": " + reinterpret_cast<const char*>( buffer ) );
@@ -150,7 +150,7 @@ bool RegEx::matches( const char* stringSearch, int stringStartOffset,
if ( mOptions & Options::UseOniguruma ) {
OnigRegion* region = onig_region_new();
if ( !region ) {
Log::error( "Onigmo: onig_region_new() failed." );
Log::error( "Onigumura: onig_region_new() failed." );
mMatchNum = 0;
return false;
}
@@ -206,7 +206,7 @@ bool RegEx::matches( const char* stringSearch, int stringStartOffset,
} else { // Error
UChar errBuf[ONIG_MAX_ERROR_MESSAGE_LEN];
onig_error_code_to_str( errBuf, ret );
Log::debug( "Onigmo search error: %s", reinterpret_cast<const char*>( errBuf ) );
Log::debug( "Onigumura search error: %s", reinterpret_cast<const char*>( errBuf ) );
onig_region_free( region, 1 );
mMatchNum = 0;
return false;
@@ -270,7 +270,7 @@ const size_t& RegEx::getNumMatches() const {
return mMatchNum;
}
bool RegEx::initWithOnigmo( std::string_view pattern, bool useCache ) {
bool RegEx::initWithOnigumura( std::string_view pattern, bool useCache ) {
OnigOptionType opt = ONIG_OPTION_NONE;
if ( mOptions & Options::Caseless )
@@ -290,7 +290,7 @@ bool RegEx::initWithOnigmo( std::string_view pattern, bool useCache ) {
if ( ret != ONIG_NORMAL ) {
UChar errBuf[ONIG_MAX_ERROR_MESSAGE_LEN];
onig_error_code_to_str( errBuf, ret, &err );
Log::info( "Onigmo compilation failed: %s", reinterpret_cast<const char*>( errBuf ) );
Log::info( "Onigumura compilation failed: %s", reinterpret_cast<const char*>( errBuf ) );
mValid = false;
if ( mCompiledPattern ) {
onig_free( regex );

View File

@@ -73,10 +73,38 @@ static void updatePatternState( SyntaxDefinition& def, SyntaxPattern& ptrn ) {
ptrn.flags |= SyntaxPattern::IsRootSelfInclude;
} else if ( ptrn.checkIsSourceInclude() && ptrn.patterns[1].size() > 7 ) {
ptrn.flags |= SyntaxPattern::IsSourceInclude;
ptrn.syntax = SyntaxDefinitionManager::instance()
->getByLanguageName(
std::string_view{ ptrn.patterns[1] }.substr( 7 /* "source." */ ) )
.getLanguageName();
if ( def.getRepositoryName( String::hash( ptrn.patterns[1] ) ).empty() ) {
const auto& lang = SyntaxDefinitionManager::instance()->getByLanguageName(
std::string_view{ ptrn.patterns[1] }.substr( 7 /* "source." */ ) );
auto syntaxRepoName = ptrn.patterns[1];
auto patterns = lang.getPatterns();
for ( auto& iptrn : patterns ) {
if ( iptrn.isRepositoryInclude() ) {
iptrn.flags = SyntaxPattern::IsInclude | SyntaxPattern::IsSourceInclude;
iptrn.patterns[1] = String::format( "%s.%s", syntaxRepoName,
iptrn.patterns[1].substr( 1 ) );
}
}
def.addRepository( std::move( syntaxRepoName ), { std::move( patterns ) } );
const auto& repos = lang.getRepositories();
std::vector<std::pair<std::string, std::vector<SyntaxPattern>>> nrepos;
nrepos.reserve( repos.size() );
for ( const auto& repo : repos ) {
const auto& repoName = lang.getRepositoryName( repo.first );
auto newRepoName = String::format( "%s.%s", ptrn.patterns[1], repoName );
auto npatterns = repo.second.patterns;
for ( auto& iptrn : npatterns ) {
if ( iptrn.isRepositoryInclude() ) {
iptrn.flags = SyntaxPattern::IsInclude | SyntaxPattern::IsSourceInclude;
iptrn.patterns[1] = String::format( "%s.%s", ptrn.patterns[1],
iptrn.patterns[1].substr( 1 ) );
}
}
nrepos.emplace_back( std::move( newRepoName ), npatterns );
}
}
} else {
Log::warning( "updatePatternState unknown include directive: %s", ptrn.patterns[1] );
ptrn.flags &= ~SyntaxPattern::IsInclude;
@@ -438,8 +466,7 @@ SyntaxPattern::SyntaxPattern( std::vector<std::string>&& _patterns,
SyntaxDefinition& SyntaxDefinition::addRepository( std::string&& name,
SyntaxRepository&& repository ) {
eeASSERT( repository.patterns.size() < std::numeric_limits<Uint8>::max() - 1 &&
repository.syntax.empty() );
eeASSERT( repository.patterns.size() < std::numeric_limits<SyntaxSyateHolderType>::max() - 1 );
auto hash = String::hash( name );
mRepositoryIndex[hash] = ++mRepositoryIndexCounter;
mRepositoryIndexInvert[mRepositoryIndexCounter] = hash;
@@ -465,7 +492,7 @@ SyntaxDefinition& SyntaxDefinition::addRepositories(
}
const SyntaxRepository& SyntaxDefinition::getRepository( String::HashType hash ) const {
static SyntaxRepository EMPTY = SyntaxRepository( "" );
static SyntaxRepository EMPTY = SyntaxRepository();
auto found = mRepository.find( hash );
return found != mRepository.end() ? found->second : EMPTY;
}

View File

@@ -306,7 +306,7 @@ static json toJson( const SyntaxDefinition& def ) {
for ( const auto& [hash, patterns] : def.getRepositories() ) {
std::string name = def.getRepositoryName( hash );
if ( name.starts_with( "$CONTENT_" ) )
if ( name.starts_with( "$CONTENT_" ) || name.starts_with( "source." ) )
continue;
nlohmann::json repo;
@@ -513,7 +513,7 @@ namespace EE { namespace UI { namespace Doc { namespace Language {
buf += ".addRepositories( {\n";
for ( const auto& repo : def.getRepositories() ) {
std::string name = def.getRepositoryName( repo.first );
if ( name.starts_with( "$CONTENT_" ) )
if ( name.starts_with( "$CONTENT_" ) || name.starts_with( "source." ) )
continue;
buf += "\n{ \"" + name + "\", ";
patternsToCPP( buf, repo.second.patterns );

View File

@@ -386,6 +386,13 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax
int fullMatchEnd = matches[0].end;
if ( pattern.matchType == SyntaxPatternMatchType::RegEx ) {
/* Should we do this?
for ( size_t i = 1; i < numMatches; i++ ) {
fullMatchStart = eemin( matches[i].start, fullMatchStart );
fullMatchEnd = eemax( matches[i].end, fullMatchEnd );
}
*/
priorityMap.clear();
priorityMap.resize( fullMatchEnd - fullMatchStart, 0 );
@@ -653,13 +660,26 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax
#endif
patternStack.push_back(
{ &targetRepo.patterns, 0,
static_cast<Uint8>( innerPtrn->repositoryIdx ) } );
static_cast<SyntaxSyateHolderType>( innerPtrn->repositoryIdx ) } );
continue;
} else if ( innerPtrn->isRootSelfInclude() ) {
if ( patternStack.size() + 1 >= MAX_PATTERN_STACK_SIZE )
break;
patternStack.push_back( { &curState.currentSyntax->getPatterns(), 0, 0 } );
continue;
} else if ( innerPtrn->isSourceInclude() ) {
if ( patternStack.size() + 1 >= MAX_PATTERN_STACK_SIZE )
break;
const auto& targetRepo =
curState.currentSyntax->getRepository( innerPtrn->getRepositoryName() );
const auto repoIndex = curState.currentSyntax->getRepositoryIndex(
innerPtrn->getRepositoryName() );
patternStack.push_back(
{ &targetRepo.patterns, 0,
static_cast<SyntaxSyateHolderType>( repoIndex ) } );
continue;
}
if ( startIdx != 0 &&
@@ -762,12 +782,25 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax
const auto& repo =
curState.currentSyntax->getRepository( pattern->getRepositoryName() );
patternStack.push_back(
{ &repo.patterns, 0, static_cast<Uint8>( pattern->repositoryIdx ) } );
{ &repo.patterns, 0,
static_cast<SyntaxSyateHolderType>( pattern->repositoryIdx ) } );
} else if ( pattern->isRootSelfInclude() ) {
if ( patternStack.size() + 1 >= MAX_PATTERN_STACK_SIZE )
break;
patternStack.push_back( { &curState.currentSyntax->getPatterns(), 0, 0 } );
} else if ( pattern->isSourceInclude() ) {
if ( patternStack.size() + 1 >= MAX_PATTERN_STACK_SIZE )
break;
const auto& repo =
curState.currentSyntax->getRepository( pattern->getRepositoryName() );
auto repoIndex =
curState.currentSyntax->getRepositoryIndex( pattern->getRepositoryName() );
patternStack.push_back(
{ &repo.patterns, 0, static_cast<SyntaxSyateHolderType>( repoIndex ) } );
} else {
if ( startIdx != 0 && pattern->matchType == SyntaxPatternMatchType::LuaPattern &&
pattern->patterns[0][0] == '^' )

View File

@@ -0,0 +1,311 @@
#include <eepp/ui/doc/languages/qbs.hpp>
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
namespace EE { namespace UI { namespace Doc { namespace Language {
SyntaxDefinition& addQbs() {
return SyntaxDefinitionManager::instance()
->add(
{ "Qbs",
{ "%.qbs$" },
{
{ { "include", "#import" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#object" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#comment" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
},
{
},
"",
{}
} )
.addRepositories( {
{ "obj-declaration",
{
{ { "\\b([A-Z][a-z]*([A-Z][a-z]*)*)\\s*\\{", "\\}" },
{ "normal", "type" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "obj-attributes" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#comment" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "attr-array",
{
{ { "\\b([\\w\\.]*)\\s*:\\s*\\[\\s*", "\\]" },
{ "normal", "parameter" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "#object" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "source.js" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "comment-contents",
{
{ { "\\b(NOTE|TODO|DEBUG|XXX)\\b" },
"literal",
"",
SyntaxPatternMatchType::RegEx },
{ { "\\b(BUG|FIXME|WARNING)\\b" }, "error", "", SyntaxPatternMatchType::RegEx },
} },
{ "attr-block",
{
{ { "\\b([\\w\\.]*)\\s*:\\s*\\{\\s*", "\\}" },
{ "normal", "parameter" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "source.js" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "attr-expr",
{
{ { "\\b([\\w\\.]*)\\s*:\\s*(?=[^\\s]+)", ";|$" },
{ "normal", "parameter" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "source.js" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "comment",
{
{ { "(\\/\\/)", "$" },
{ "comment" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "#comment-contents" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
{ { "(\\/\\*)", "(\\*\\/)" },
{ "comment" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "#comment-contents" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "string",
{
{ { "'", "'" }, { "string" }, {}, "", SyntaxPatternMatchType::RegEx },
{ { "\"", "\"" }, { "string" }, {}, "", SyntaxPatternMatchType::RegEx },
} },
{ "attr-prop",
{
{ { "\\b([\\w\\.]*)\\s*:\\s*(?=[A-Z]\\w*\\s*\\{)", "(?=\\})" },
{ "normal", "parameter" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "#object" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "import",
{
{ { "\\b(import)\\b", "$" },
{ "normal", "keyword" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "\\b([\\w\\.]+)\\s+(\\d+\\.\\d+)?\\s" },
{ "normal", "normal", "number" },
{},
"",
SyntaxPatternMatchType::RegEx },
{ { "\\b(as)\\s+(\\w*)" },
{ "normal", "keyword", "type" },
{},
"",
SyntaxPatternMatchType::RegEx },
{ { "include", "#string" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#comment" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "obj-attributes",
{
{ { "include", "#attr-prop" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#attr-array" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#attr-block" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#attr-expr" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
{ "obj-method",
{
{ { "\\b(?=function)\\b", "(?<=\\})" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "source.js" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "object",
{
{ { "\\b([A-Z][\\w\\.]*)\\s*(\\{|$)", "\\}" },
{ "normal", "type" },
{},
"",
SyntaxPatternMatchType::RegEx,
{
{ { "include", "$self" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#obj-property" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#obj-method" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#obj-declaration" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
{ { "include", "#obj-attributes" },
{ "normal" },
{},
"",
SyntaxPatternMatchType::LuaPattern },
} },
} },
{ "obj-property",
{
{ { "\\b(readonly)\\s+(?=property)" },
"keyword",
"",
SyntaxPatternMatchType::RegEx },
{ { "\\b(property)\\s+([\\w<>]+)(?=\\s+\\w+\\s*:)" },
{ "normal", "keyword", "keyword" },
{},
"",
SyntaxPatternMatchType::RegEx },
{ { "\\b(property)\\s+([\\w<>]+)\\s+(\\w+)\\s*$" },
{ "normal", "keyword", "keyword", "parameter" },
{},
"",
SyntaxPatternMatchType::RegEx },
} },
} );
}
}}}} // namespace EE::UI::Doc::Language

View File

@@ -0,0 +1,11 @@
#ifndef EE_UI_DOC_Qbs
#define EE_UI_DOC_Qbs
#include <eepp/ui/doc/syntaxdefinition.hpp>
namespace EE { namespace UI { namespace Doc { namespace Language {
extern SyntaxDefinition& addQbs();
}}}} // namespace EE::UI::Doc::Language
#endif

View File

@@ -80,6 +80,7 @@
#include <eepp/ui/doc/languages/pony.hpp>
#include <eepp/ui/doc/languages/postgresql.hpp>
#include <eepp/ui/doc/languages/powershell.hpp>
#include <eepp/ui/doc/languages/qbs.hpp>
#include <eepp/ui/doc/languages/qmake.hpp>
#include <eepp/ui/doc/languages/r.hpp>
#include <eepp/ui/doc/languages/racket.hpp>
@@ -555,6 +556,12 @@ void LanguagesSyntaxHighlighting::load() {
{ "%.ps1$", "%.psm1$", "%.psd1$", "%.ps1xml$", "%.pssc$", "%.psrc$", "%.cdxml$" },
} );
sdm->addPreDefinition( {
"Qbs",
[]() -> SyntaxDefinition& { return addQbs(); },
{ "%.qbs$" },
} );
sdm->addPreDefinition( {
"QMake",
[]() -> SyntaxDefinition& { return addQmake(); },