1#if ALIB_CAMP_RESOURCE_COMPILATION
10 std::errc errc= rcFile.
Open(rcFilePath.Terminate());
11 if(errc != std::errc()) {
14 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
23 integer lineStartRemaining= mfc.Remaining();
28 auto isWS= [](
char c) ->
bool {
29 return c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\f' || c ==
'\v';
32 auto isCommentStart= [&](
char c) ->
bool {
34 || (c ==
'/' && mfc.Remaining() > 0 && *mfc ==
'/');
37 auto consumeEOL= [&]() {
39 char c= mfc.Next<NC>();
41 lineStartRemaining= mfc.Remaining();
47 auto hexValue= [](
char c) ->
int {
48 if(c >=
'0' && c <=
'9')
return c -
'0';
49 if(c >=
'a' && c <=
'f')
return 10 + (c -
'a');
50 if(c >=
'A' && c <=
'F')
return 10 + (c -
'A');
54 auto appendUtf8= [&](uint32_t codePoint) {
56 if( codePoint > 0x10FFFFu
57 || (codePoint >= 0xD800u && codePoint <= 0xDFFFu) )
60 if(codePoint <= 0x7Fu) {
61 rcVal << char(codePoint);
64 if(codePoint <= 0x7FFu) {
65 rcVal << char(0xC0u | ((codePoint >> 6) & 0x1Fu));
66 rcVal << char(0x80u | ( codePoint & 0x3Fu));
69 if(codePoint <= 0xFFFFu) {
70 rcVal << char(0xE0u | ((codePoint >> 12) & 0x0Fu));
71 rcVal << char(0x80u | ((codePoint >> 6) & 0x3Fu));
72 rcVal << char(0x80u | ( codePoint & 0x3Fu));
75 rcVal << char(0xF0u | ((codePoint >> 18) & 0x07u));
76 rcVal << char(0x80u | ((codePoint >> 12) & 0x3Fu));
77 rcVal << char(0x80u | ((codePoint >> 6) & 0x3Fu));
78 rcVal << char(0x80u | ( codePoint & 0x3Fu));
81 auto appendUnicodeEscapeFromLineBuf= [&](
const NString4K& lineBuf,
integer& idx) ->
bool {
82 if(idx + 4 >= lineBuf.Length())
85 uint32_t codePoint= 0;
86 for(
integer d= 1; d <= 4; ++d) {
87 int hv= hexValue(lineBuf.CharAt(idx + d));
90 codePoint= (codePoint << 4) | uint32_t(hv);
93 appendUtf8(codePoint);
98 auto appendUnicodeEscapeFromMappedFile= [&]() ->
bool {
100 uint32_t codePoint= 0;
101 for(
int d= 0; d < 4; ++d) {
104 int hv= hexValue(view.Next<NC>());
107 codePoint= (codePoint << 4) | uint32_t(hv);
110 appendUtf8(codePoint);
115 auto appendEscape= [&](
char esc,
bool& hadBackslashEscape) {
118 case ' ': rcVal <<
' ';
break;
119 case 'n': rcVal <<
'\n';
break;
120 case 'r': rcVal <<
'\r';
break;
121 case 't': rcVal <<
'\t';
break;
122 case 'a': rcVal <<
'\a';
break;
123 case 'b': rcVal <<
'\b';
break;
124 case 'f': rcVal <<
'\f';
break;
125 case 'v': rcVal <<
'\v';
break;
126 case '\\': rcVal <<
'\\';
break;
127 case '"': rcVal <<
'"' ;
break;
128 default: rcVal << esc ;
break;
130 hadBackslashEscape=
false;
136 while(!view.IsEOF()) {
138 if(c ==
' ' || c ==
'\t') { ++cnt; view.Next<NC>();
continue; }
149 auto readBlockContent= [&](
int keyIndent,
bool folded,
bool compact) {
153 while(!mfc.IsEOF()) {
154 char c= mfc.Next<NC>();
156 lineStartRemaining= mfc.Remaining();
164 bool stripIndentKnown =
false;
165 bool prevWasEmpty =
true;
166 bool inQuotes =
false;
167 bool hadBackslashEscape=
false;
169 while(!mfc.IsEOF()) {
172 int indent= readIndentCount(view);
175 char first= view.IsEOF() ?
'\0' : *view;
176 bool isEmptyLine= (first ==
'\n') || (first ==
'\r' && view.Remaining() > 0 && *view ==
'\n');
177 if(!isEmptyLine && indent <= keyIndent)
186 while(!mfc.IsEOF()) {
187 char c= mfc.Next<NC>();
188 if(c ==
'\r')
continue;
194 if(!mfc.IsEOF() || (mfc.IsEOF() && (lineBuf.IsNotEmpty()))) {
195 lineStartRemaining= mfc.Remaining();
200 if(lineBuf.IsEmpty()) {
224 if(!stripIndentKnown) {
225 stripIndent = indent;
226 stripIndentKnown=
true;
230 int relativeIndent= indent - stripIndent;
236 for(
integer i= 0; i < lineBuf.Length(); ++i) {
237 char c= lineBuf.CharAt(i);
238 if(hadBackslashEscape) {
239 if(c ==
'u' && appendUnicodeEscapeFromLineBuf(lineBuf, i)) {
240 hadBackslashEscape=
false;
243 appendEscape(c, hadBackslashEscape);
246 if(c ==
'\\') { hadBackslashEscape=
true;
continue; }
247 if(c ==
'"') { inQuotes= !inQuotes;
continue; }
249 if(!inQuotes && (c ==
' ' || c ==
'\t'))
263 if(!prevWasEmpty && !(rcVal.IsNotEmpty() && rcVal.CharAtEnd() ==
'\n'))
268 for(
int i= 0; i < relativeIndent; ++i)
271 for(
integer i= 0; i < lineBuf.Length(); ++i) {
272 char c= lineBuf.CharAt(i);
273 if(hadBackslashEscape) {
274 if(c ==
'u' && appendUnicodeEscapeFromLineBuf(lineBuf, i)) {
275 hadBackslashEscape=
false;
278 appendEscape(c, hadBackslashEscape);
281 if(c ==
'\\') { hadBackslashEscape=
true;
continue; }
293 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
298 if(hadBackslashEscape)
303 while(rcVal.IsNotEmpty() && rcVal.CharAtEnd() ==
'\n')
307 auto nextResourcePair= [&]() ->
bool
309 while( !mfc.IsEOF() ) {
314 keyIndent= readIndentCount(view);
317 char c= mfc.Next<NC>();
320 while( c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\f' || c ==
'\v' || c ==
'\n' ) {
322 lineStartRemaining= mfc.Remaining();
327 keyIndent= readIndentCount(view);
335 if( isCommentStart(c) ) {
345 unsigned char uc=
static_cast<unsigned char>(c);
352 if( uc < 33 || uc > 127 || uc == 34 )
364 if( rcName.Length() >= 512 - 1 ) {
366 lineNo, lineStartRemaining - mfc.Remaining(), 0,
367 NString(errors.get_allocator().GetAllocator(), rcName));
380 if( c ==
'=' || isWS(c) || c ==
'\n' )
385 if( rcName.IsEmpty() ) {
416 lineStartRemaining= mfc.Remaining();
422 if( c ==
'|' || c ==
'>' ) {
423 bool folded= (c ==
'>');
424 readBlockContent(keyIndent, folded,
false);
434 char next= mfc.IsEOF() ?
'\0' : mfc.Next<NC>();
436 readBlockContent(keyIndent,
false,
true);
441 bool inQuotes=
false;
442 bool hadBackslashEscape=
false;
452 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
454 lineStartRemaining= mfc.Remaining();
459 if( hadBackslashEscape ) {
460 if(c ==
'u' && appendUnicodeEscapeFromMappedFile()) {
461 hadBackslashEscape=
false;
463 appendEscape(c, hadBackslashEscape);
465 else if( c ==
'\\' ) { hadBackslashEscape=
true; }
466 else if( c ==
'"' ) { inQuotes= !inQuotes; }
467 else if( !inQuotes && (c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\f' || c ==
'\v') ) {
474 if(hadBackslashEscape)
480 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
489 bool hadBackslashEscape=
false;
492 if(hadBackslashEscape)
496 PathString(errors.get_allocator().GetAllocator(), rcFilePath) );
505 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
506 lineStartRemaining= mfc.Remaining();
511 if( hadBackslashEscape ) {
512 if(c ==
'u' && appendUnicodeEscapeFromMappedFile()) {
513 hadBackslashEscape=
false;
516 appendEscape(c, hadBackslashEscape);
519 if( c ==
'\\' ) { hadBackslashEscape=
true;
continue; }
520 if( c ==
'"' )
break;
526 while(!mfc.IsEOF()) {
528 if(t ==
'\r') { mfc.Next<NC>();
continue; }
529 if(isWS(t)) { mfc.Next<NC>();
continue; }
530 if(t ==
'\n') { mfc.Next<NC>(); lineStartRemaining= mfc.Remaining(); ++lineNo;
break; }
539 bool hadBackslashEscape=
false;
561 while(lineBuf.IsNotEmpty()) {
562 char last= lineBuf.CharAtEnd();
563 if(last ==
' ' || last ==
'\t' || last ==
'\r' || last ==
'\f' || last ==
'\v')
564 lineBuf.ShortenBy(1);
570 for(
integer i= 0; i < lineBuf.Length(); ++i) {
571 char ch= lineBuf.CharAt(i);
572 if(hadBackslashEscape) {
573 if(ch ==
'u' && appendUnicodeEscapeFromLineBuf(lineBuf, i)) {
574 hadBackslashEscape=
false;
577 appendEscape(ch, hadBackslashEscape);
580 if(ch ==
'\\') { hadBackslashEscape=
true;
continue; }
583 if(hadBackslashEscape)
587 lineStartRemaining= mfc.Remaining();
598 while( nextResourcePair() ) {
606 if( !dedupSet.InsertIfNotExistent(name).second ) {
608 int firstOccurrence= -1;
609 for(
const auto& entry : destination )
610 if( entry.Key == name ) { firstOccurrence= entry.LineNo;
break; }
612 lineNo-1, 0, firstOccurrence, name );
620 MonoAllocator::Resetter resetter(ma);
622 std::errc errc= file.Read(cppFilePath);
623 if(errc != std::errc()) {
626 PathString(errors.get_allocator().GetAllocator(), cppFilePath));
632 for( ; startLine < file.Size(); ++startLine ) {
633 if( file.At(startLine).IndexOf(
"ALIB-RESOURCE-COMPILER-REPLACEMENT-START") > 0)
638 for( ; endLine < file.Size(); ++endLine ) {
639 if( file.At(endLine).IndexOf(
"ALIB-RESOURCE-COMPILER-REPLACEMENT-END") > 0)
642 if( startLine >= file.Size() ) {
645 PathString(errors.get_allocator().GetAllocator(), cppFilePath));
648 if( endLine >= file.Size() ) {
651 PathString(errors.get_allocator().GetAllocator(), cppFilePath));
656 integer lineDiff=
integer(resources.size()) - (endLine - startLine);
657 if( lineDiff < 0 ) file.erase(file.begin() + endLine + lineDiff, file.begin() + endLine);
658 else if( lineDiff > 0 ) file.insert(file.begin()+ endLine,
size_t(lineDiff),
664 for(
const auto& entry : resources )
665 maxNameLen= (std::max)( maxNameLen, entry.Key.Length() );
666 maxNameLen= (std::min)( maxNameLen + 4 + 2,
integer(30) );
668 auto hexDigit= [](uint32_t value) ->
char {
670 return value < 10u ? char(
'0' + value) : char(
'A' + (value - 10u));
673 auto appendHexEscape= [&](
NString1K& target, uint32_t codePoint) {
674 if(codePoint <= 0xFFFFu) {
676 for(
int shift= 12; shift >= 0; shift-= 4)
677 target << hexDigit(codePoint >> uint32_t(shift));
681 for(
int shift= 28; shift >= 0; shift-= 4)
682 target << hexDigit(codePoint >> uint32_t(shift));
685 auto decodeUtf8CodePoint= [&](
const String& src,
integer& idx) -> uint32_t {
686 auto readByteAt= [&](
integer pos) ->
unsigned char {
687 return static_cast<unsigned char>(src.CharAt(pos));
690 unsigned char b0= readByteAt(idx);
694 auto isCont= [](
unsigned char b) ->
bool {
return (b & 0xC0u) == 0x80u; };
695 integer remaining= src.Length() - idx;
697 if((b0 & 0xE0u) == 0xC0u) {
700 unsigned char b1= readByteAt(idx + 1);
703 uint32_t cp= (uint32_t(b0 & 0x1Fu) << 6) | uint32_t(b1 & 0x3Fu);
709 if((b0 & 0xF0u) == 0xE0u) {
712 unsigned char b1= readByteAt(idx + 1);
713 unsigned char b2= readByteAt(idx + 2);
714 if(!isCont(b1) || !isCont(b2))
716 uint32_t cp= (uint32_t(b0 & 0x0Fu) << 12)
717 | (uint32_t(b1 & 0x3Fu) << 6)
718 | uint32_t(b2 & 0x3Fu);
719 if(cp < 0x800u || (cp >= 0xD800u && cp <= 0xDFFFu))
724 if((b0 & 0xF8u) == 0xF0u) {
727 unsigned char b1= readByteAt(idx + 1);
728 unsigned char b2= readByteAt(idx + 2);
729 unsigned char b3= readByteAt(idx + 3);
730 if(!isCont(b1) || !isCont(b2) || !isCont(b3))
732 uint32_t cp= (uint32_t(b0 & 0x07u) << 18)
733 | (uint32_t(b1 & 0x3Fu) << 12)
734 | (uint32_t(b2 & 0x3Fu) << 6)
735 | uint32_t(b3 & 0x3Fu);
736 if(cp < 0x10000u || cp > 0x10FFFFu)
746 for(
const auto& entry : resources ) {
749 line._<NC>(entry.Key);
751 line._<NC>(
NFill(
' ',
int(maxNameLen - line.Length())));
754 line._<NC>(
", A_CHAR(\"");
755 for(
integer i= 0; i < entry.Value.Length(); ++i ) {
756 uint32_t cp= decodeUtf8CodePoint(entry.Value, i);
758 case '\\': line._<NC>(
"\\\\");
break;
759 case '"' : line._<NC>(
"\\\"" );
break;
760 case '\r': line._<NC>(
"\\r" );
break;
761 case '\t': line._<NC>(
"\\t" );
break;
762 case '\a': line._<NC>(
"\\a" );
break;
763 case '\b': line._<NC>(
"\\b" );
break;
764 case '\f': line._<NC>(
"\\f" );
break;
765 case '\v': line._<NC>(
"\\v" );
break;
766 case '\n': { line._<NC>(
"\\n" );
767 if( line.Length() > maxNameLen + 20
768 && i < entry.Value.Length() - 1 ) {
770 ._<NC>(
NFill(
' ',
int(maxNameLen + 2)))
776 if(cp >= 0x20u && cp <= 0x7Eu)
779 appendHexEscape(line, cp);
784 file.At(actLine++).Allocate( file.GetAllocator(), line);
789 errc= file.Write(cppFilePath);
790 if(errc != std::errc()) {
793 PathString(errors.get_allocator().GetAllocator(),
799 for(
const auto& err : errors ) {
801 output.
Add(
"Duplicate resource name {}\n"
802 " First occurrence: {}:{}\n"
803 " Additional occurrence: {}:{}",
804 err.AdditonalInfo.Unbox<
NString>(),
805 fileName, err.LineNo2,
806 fileName, err.LineNo );
819 ALIB_ASSERT_ERROR(msg.IsNotEmpty(),
"CAMP/RESCMP",
"Unhandled Error: ",
int(err.ErrorCode))
820 output.Add( msg, err.AdditonalInfo, err.LineNo, err.ColNo );
826 const
NString& resourceCategory,
827 bool allowReplacements,
831 Path rcFilePath=
Path(alibrcFileName);
832 if( !rcFilePath.IsAbsolute() ) {
833 rcFilePath.Reset(callingFile);
834 rcFilePath.ChangeToParent();
836 rcFilePath.MakeCanonical();
841 ALIB_ERROR(
"CAMP/RESCMP",
"Could not find resource file \"{}\"", rcFilePath )
846 Path cppFilePath =
Path(cppFileName);
847 if(cppFilePath.IsNotEmpty()) {
848 if( !cppFilePath.IsAbsolute() ) {
849 cppFilePath.Reset(callingFile);
850 cppFilePath.ChangeToParent();
852 cppFilePath.MakeCanonical();
857 ALIB_ERROR(
"CAMP/RESCMP",
"Could not find cpp file to write resource-bulkload \"{}\"",
865 if( cppFileStat.MDate() - DateTime::Duration::FromMilliseconds(10)
866 > rcFileStat.MDate() )
872 ResourceList* orderedList = (*ma)().New<ResourceList>(*ma);
873 RCErrorList* errors = (*ma)().New<RCErrorList>(*ma);
875 if( !errors->empty() ) {
878 ALIB_ERROR(
"CAMP/RESCMP",
"Errors while reading resource file \"{}\":\n{}", rcFilePath,
885 for( auto& entry : *orderedList ) {
887 pool.BootstrapAddOrReplace( resourceCategory, entry.Key, entry.Value );
889 "Doubly defined resource in {}:{}", rcFilePath, entry.LineNo )
893 if( cppFilePath.IsNotEmpty() ) {
895 if( !errors->empty() ) {
898 ALIB_ERROR(
"CAMP/RESCMP",
"Errors while patching cpp file \"{}\":\n{}", cppFilePath,
906#include "ALib.Lang.CIMethods.H"
#define ALIB_ERROR(domain,...)
#define ALIB_ASSERT_ERROR(cond, domain,...)
@ STATS
Only stats (size, date, owner, etc.) read.
std::errc Open(const CPathString &path, std::size_t knownSize=(std::numeric_limits< std::size_t >::max)(), bool disableMMap=false, bool willNeed=true)
void LoadResourceFile(Path &rcFileName, ResourceList &destination, RCErrorList &errors)
void ListErrors(RCErrorList &errors, Paragraphs &output, const PathString &fileName)
StdVectorMA< ResourceListEntry > ResourceList
A collection of resource entries, typically loaded from an .alibrc file.
void PatchCPPFile(const Path &cppFilePath, const ResourceList &resources, RCErrorList &errors, bool dryRun=false)
@ ErrLineEndWhileInQuotes
Line ends while inside quotes.
@ CPPFileMissingEndMarker
Missing end marker in the cpp file to patch.
@ CPPFileNotFoundOrAccessible
Error opening the cpp file to patch.
@ CPPFileMissingStartMarker
Missing start marker in the cpp file to patch.
@ RCFileNotFoundOrAccessible
Error opening the resource file.
@ CPPFileNotWritable
Error writing the cpp file to patch.
@ DuplicateResourceName
A duplicate resource name was found.
StdVectorMA< ResourceFileError > RCErrorList
A list of errors that occurred with #"LoadResourceFile".
TMonoAllocator< lang::HeapAllocator > GLOBAL_ALLOCATOR
monomem::TMonoAllocator< lang::HeapAllocator > MonoAllocator
strings::TString< nchar > NString
Type alias in namespace #"%alib".
containers::HashSet< TAllocator, T, THash, TEqual, THashCaching, TRecycling > HashSet
Type alias in namespace #"%alib". See type definition #"alib::containers::HashSet".
constexpr NString NULL_NSTRING
A nulled string of the narrow character type.
resources::ResourcePool ResourcePool
Type alias in namespace #"%alib".
strings::TFill< nchar > NFill
Type alias in namespace #"%alib".
NLocalString< 1024 > NString1K
Type alias name for #"TLocalString;TLocalString<nchar,1024>".
lang::integer integer
Type alias in namespace #"%alib".
system::TTextFile< NString, TEXTFILE_DEFAULT_ALLOCATOR, TLocalBufferSize > TextFile
LocalString< 4096 > String4K
Type alias name for #"TLocalString;TLocalString<character,4096>".
strings::TString< character > String
Type alias in namespace #"%alib".
system::Path Path
Type alias in namespace #"%alib".
system::MappedFile MappedFile
Type alias in namespace #"%alib".
strings::TString< PathCharType > PathString
The string-type used with this ALib Module.
NLocalString< 4096 > NString4K
Type alias name for #"TLocalString;TLocalString<nchar,8192>".
system::FileStatus FileStatus
Type alias in namespace #"%alib".
constexpr PathCharType DIRECTORY_SEPARATOR
The standard path separator character. Defaults to '\' on Windows OS, '/' else.
NLocalString< 512 > NString512
Type alias name for #"TLocalString;TLocalString<nchar,512>".
format::Paragraphs Paragraphs
Type alias in namespace #"%alib".
TAllocator & GetAllocator() const noexcept