ALib C++ Framework
by
Library Version: 2605 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
resourcecompiler.cpp
1#if ALIB_CAMP_RESOURCE_COMPILATION
2
3namespace alib::camp {
4
6
7void LoadResourceFile(Path& rcFilePath, ResourceList& destination, RCErrorList& errors){
8 //-------------------------------------- open file input file ------------------------------------
9 MappedFile rcFile;
10 std::errc errc= rcFile.Open(rcFilePath.Terminate());
11 if(errc != std::errc()) {
13 0,0,0,
14 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
15 return;
16 }
17 MappedFile::Data<char> mfc= rcFile.GetData<char>();
18
19 MonoAllocator ma(ALIB_DBG("CampRCTemp",) 16);
21
22 //-------------------------------------------- helpers -------------------------------------------
23 integer lineStartRemaining= mfc.Remaining();
24 int lineNo= 1;
25 NString512 rcName;
26 String4K rcVal;
27
28 auto isWS= [](char c) -> bool {
29 return c == ' ' || c == '\t' || c == '\r' || c == '\f' || c == '\v';
30 };
31
32 auto isCommentStart= [&](char c) -> bool {
33 return c == '#'
34 || (c == '/' && mfc.Remaining() > 0 && *mfc == '/');
35 };
36
37 auto consumeEOL= [&]() {
38 while(!mfc.IsEOF()) {
39 char c= mfc.Next<NC>();
40 if(c == '\n') {
41 lineStartRemaining= mfc.Remaining();
42 ++lineNo;
43 return;
44 } }
45 };
46
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');
51 return -1;
52 };
53
54 auto appendUtf8= [&](uint32_t codePoint) {
55 // Replace invalid code points (including UTF-16 surrogates) with U+FFFD.
56 if( codePoint > 0x10FFFFu
57 || (codePoint >= 0xD800u && codePoint <= 0xDFFFu) )
58 codePoint= 0xFFFDu;
59
60 if(codePoint <= 0x7Fu) {
61 rcVal << char(codePoint);
62 return;
63 }
64 if(codePoint <= 0x7FFu) {
65 rcVal << char(0xC0u | ((codePoint >> 6) & 0x1Fu));
66 rcVal << char(0x80u | ( codePoint & 0x3Fu));
67 return;
68 }
69 if(codePoint <= 0xFFFFu) {
70 rcVal << char(0xE0u | ((codePoint >> 12) & 0x0Fu));
71 rcVal << char(0x80u | ((codePoint >> 6) & 0x3Fu));
72 rcVal << char(0x80u | ( codePoint & 0x3Fu));
73 return;
74 }
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));
79 };
80
81 auto appendUnicodeEscapeFromLineBuf= [&](const NString4K& lineBuf, integer& idx) -> bool {
82 if(idx + 4 >= lineBuf.Length())
83 return false;
84
85 uint32_t codePoint= 0;
86 for(integer d= 1; d <= 4; ++d) {
87 int hv= hexValue(lineBuf.CharAt(idx + d));
88 if(hv < 0)
89 return false;
90 codePoint= (codePoint << 4) | uint32_t(hv);
91 }
92
93 appendUtf8(codePoint);
94 idx += 4;
95 return true;
96 };
97
98 auto appendUnicodeEscapeFromMappedFile= [&]() -> bool {
99 auto view= mfc;
100 uint32_t codePoint= 0;
101 for(int d= 0; d < 4; ++d) {
102 if(view.IsEOF())
103 return false;
104 int hv= hexValue(view.Next<NC>());
105 if(hv < 0)
106 return false;
107 codePoint= (codePoint << 4) | uint32_t(hv);
108 }
109
110 appendUtf8(codePoint);
111 mfc.Skip(4);
112 return true;
113 };
114
115 auto appendEscape= [&](char esc, bool& hadBackslashEscape) {
116 // Processes one escape sequence character 'esc' (the char after '\').
117 switch( esc ) {
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;
129 }
130 hadBackslashEscape= false;
131 };
132
133 auto readIndentCount= [&](MappedFile::Data<char>& view) -> int {
134 // counts leading spaces/tabs from current position up to first non-(space/tab) or newline/EOF
135 int cnt= 0;
136 while(!view.IsEOF()) {
137 char c= *view;
138 if(c == ' ' || c == '\t') { ++cnt; view.Next<NC>(); continue; }
139 break;
140 }
141 return cnt;
142 };
143
144 // Read a logical "block" content, using indentation rules described in the dox above.
145 // Parameters:
146 // - keyIndent: indentation (spaces/tabs count) of the key line
147 // - folded: true for '>' folded, false for '|' literal
148 // - compact: true to feed text into compact lexer (whitespace ignored outside quotes)
149 auto readBlockContent= [&](int keyIndent, bool folded, bool compact) {
150 rcVal.Reset();
151
152 // move to next line (consume remainder of key line up to '\n')
153 while(!mfc.IsEOF()) {
154 char c= mfc.Next<NC>();
155 if(c == '\n') {
156 lineStartRemaining= mfc.Remaining();
157 ++lineNo;
158 break;
159 } }
160 if(mfc.IsEOF())
161 return;
162
163 int stripIndent = 0;
164 bool stripIndentKnown = false;
165 bool prevWasEmpty = true; // for folded blocks and compact blocks (space insertion)
166 bool inQuotes = false;
167 bool hadBackslashEscape= false;
168
169 while(!mfc.IsEOF()) {
170 // snapshot view to measure indentation without losing data if block ends
171 auto view= mfc;
172 int indent= readIndentCount(view);
173
174 // check end-of-block: first non-empty line with indentation <= keyIndent
175 char first= view.IsEOF() ? '\0' : *view;
176 bool isEmptyLine= (first == '\n') || (first == '\r' && view.Remaining() > 0 && *view == '\n');
177 if(!isEmptyLine && indent <= keyIndent)
178 break;
179
180 // consume indentation from real stream (same as measured)
181 mfc.Skip(indent);
182
183 // normalize CRLF: if we see '\r' before '\n', ignore '\r'
184 // now read the line into a temporary buffer (up to '\n' or EOF)
185 NString4K lineBuf;
186 while(!mfc.IsEOF()) {
187 char c= mfc.Next<NC>();
188 if(c == '\r') continue;
189 if(c == '\n') break;
190 lineBuf << c;
191 }
192
193 // line finished -> update line counters (we consumed '\n' unless EOF)
194 if(!mfc.IsEOF() || (mfc.IsEOF() && (lineBuf.IsNotEmpty()))) {
195 lineStartRemaining= mfc.Remaining();
196 ++lineNo;
197 }
198
199 // empty line handling
200 if(lineBuf.IsEmpty()) {
201 if(compact) {
202 // In compact blocks, newline is just whitespace unless inside quotes.
203 if(inQuotes)
204 rcVal << '\n';
205 }
206 else if(folded) {
207 // paragraph break
208 if(!prevWasEmpty)
209 rcVal << '\n';
210 rcVal << '\n';
211 }
212 else {
213 // literal: keep empty line
214 rcVal << '\n';
215 }
216
217 prevWasEmpty= true;
218 continue;
219 }
220
221 // Determine indent-to-strip based on the first non-empty line.
222 // We've already consumed 'indent' spaces from the file, so lineBuf has no leading spaces.
223 // But we want to preserve relative indentation: lines more indented than the first should keep their extra indent.
224 if(!stripIndentKnown) {
225 stripIndent = indent;
226 stripIndentKnown= true;
227 }
228
229 // Calculate relative indentation: how many extra spaces does this line have?
230 int relativeIndent= indent - stripIndent;
231
232 // feed line content
233 if(compact) {
234 // Compact lexer: ignore whitespace outside quotes; allow quoted islands; process escapes.
235 // In a block, we treat end-of-line as whitespace (ignored outside quotes).
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;
241 continue;
242 }
243 appendEscape(c, hadBackslashEscape);
244 continue;
245 }
246 if(c == '\\') { hadBackslashEscape= true; continue; }
247 if(c == '"') { inQuotes= !inQuotes; continue; }
248
249 if(!inQuotes && (c == ' ' || c == '\t')) // ignore outside quotes
250 continue;
251
252 rcVal << c;
253 }
254 // line end => whitespace outside quotes, newline inside quotes
255 if(inQuotes)
256 rcVal << '\n';
257 prevWasEmpty= false;
258 continue;
259 }
260
261 // Non-compact blocks: process escapes and apply literal/folded joining.
262 if(folded) {
263 if(!prevWasEmpty && !(rcVal.IsNotEmpty() && rcVal.CharAtEnd() == '\n'))
264 rcVal << ' ';
265 }
266
267 // Add relative indentation
268 for(int i= 0; i < relativeIndent; ++i)
269 rcVal << ' ';
270
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;
276 continue;
277 }
278 appendEscape(c, hadBackslashEscape);
279 continue;
280 }
281 if(c == '\\') { hadBackslashEscape= true; continue; }
282 rcVal << c;
283 }
284 if(!folded) // literal keeps newline
285 rcVal << '\n';
286
287 prevWasEmpty= false;
288 }
289
290 if(inQuotes) {
292 lineNo, 0, 0,
293 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
294 }
295
296 // If the last char was a dangling backslash escape marker, treat it as a literal '\'
297 // (Same behavior as "unknown escape" case already yields literal; here it's just missing the next char.)
298 if(hadBackslashEscape)
299 rcVal << '\\';
300
301 // Strip trailing newlines from any block scalar. If users want trailing newlines,
302 // they can add them explicitly using escape sequences (e.g. "\n") at the end of the last line.
303 while(rcVal.IsNotEmpty() && rcVal.CharAtEnd() == '\n')
304 rcVal.ShortenBy(1);
305 };
306
307 auto nextResourcePair= [&]() -> bool
308 {
309 while( !mfc.IsEOF() ) {
310 // --- determine indentation of this (potential) key line ---
311 int keyIndent= 0;
312 {
313 auto view= mfc;
314 keyIndent= readIndentCount(view);
315 }
316
317 char c= mfc.Next<NC>();
318
319 // Skip leading whitespace and empty lines.
320 while( c == ' ' || c == '\t' || c == '\r' || c == '\f' || c == '\v' || c == '\n' ) {
321 if( c == '\n' ) {
322 lineStartRemaining= mfc.Remaining();
323 ++lineNo;
324
325 // update keyIndent for new line
326 auto view= mfc;
327 keyIndent= readIndentCount(view);
328 }
329 if( mfc.IsEOF() )
330 return false;
331 c= mfc.Next<NC>();
332 }
333
334 // skip comment line
335 if( isCommentStart(c) ) {
336 consumeEOL();
337 continue;
338 }
339
340 rcName.Reset();
341 rcVal .Reset();
342
343 //---------------------------- Read and validate resource name ---------------------------
344 while( c != 0 ) {
345 unsigned char uc= static_cast<unsigned char>(c);
346 if( uc == '\n' ) {
347 if(mfc.IsEOF())
348 break;
349 c= mfc.Next<NC>();
350 continue;
351 }
352 if( uc < 33 || uc > 127 || uc == 34 )
353 { // printable characters, excluding '"'
355 lineNo, 0, 0, uc);
356 if(mfc.IsEOF())
357 break;
358 c= mfc.Next<NC>();
359 continue;
360 }
361 if( c == '=' )
362 break;
363
364 if( rcName.Length() >= 512 - 1 ) {
365 errors.emplace_back( ResourceFileErrors::ErrNameTooLong,
366 lineNo, lineStartRemaining - mfc.Remaining(), 0,
367 NString(errors.get_allocator().GetAllocator(), rcName));
368 if(mfc.IsEOF())
369 break;
370 c= mfc.Next<NC>();
371 continue;
372 }
373
374 rcName << c;
375
376 if( mfc.IsEOF() )
377 break;
378
379 c= mfc.Next<NC>();
380 if( c == '=' || isWS(c) || c == '\n' )
381 break;
382 }
383
384 // empty line?
385 if( rcName.IsEmpty() ) {
386 consumeEOL();
387 continue;
388 }
389
390 // Skip separator whitespace between name and value.
391 while( isWS(c) ) {
392 if( mfc.IsEOF() )
393 break;
394 c= mfc.Next<NC>();
395 }
396
397 // Optional '=' separator
398 if( c == '=' ) {
399 if(!mfc.IsEOF())
400 c= mfc.Next<NC>();
401
402 while( isWS(c) ) {
403 if( mfc.IsEOF() )
404 break;
405 c= mfc.Next<NC>();
406 } }
407
408 // value may be empty (end of line / EOF)
409 if( mfc.IsEOF() )
410 return true;
411
412 //------------------------------------- Value parsing ------------------------------------
413 // Note: c is the first non-whitespace character after optional '=' (or it may be '\n').
414 if( c == '\n' ) {
415 // empty value
416 lineStartRemaining= mfc.Remaining();
417 ++lineNo;
418 return true;
419 }
420
421 // 3) Block scalars
422 if( c == '|' || c == '>' ) {
423 bool folded= (c == '>');
424 readBlockContent(keyIndent, folded, /*compact*/ false);
425 return true;
426 }
427
428 // 4) Compact scalars
429 if( c == '~' ) {
430 if(mfc.IsEOF())
431 return true;
432
433 // check for block compact "~|"
434 char next= mfc.IsEOF() ? '\0' : mfc.Next<NC>();
435 if(next == '|' ) {
436 readBlockContent(keyIndent, /*folded*/ false, /*compact*/ true);
437 return true;
438 }
439
440 // inline compact: parse until end-of-line
441 bool inQuotes= false;
442 bool hadBackslashEscape= false;
443
444 // We consumed one char after '~' into 'next' already; start with that.
445 c= next;
446
447 while( true ) {
448 if( c == '\n' ) {
449 if( inQuotes )
451 lineNo, 0, 0,
452 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
453
454 lineStartRemaining= mfc.Remaining();
455 ++lineNo;
456 return true;
457 }
458
459 if( hadBackslashEscape ) {
460 if(c == 'u' && appendUnicodeEscapeFromMappedFile()) {
461 hadBackslashEscape= false;
462 } else {
463 appendEscape(c, hadBackslashEscape);
464 } }
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') ) {
468 // ignore
469 } else {
470 rcVal << c;
471 }
472
473 if( mfc.IsEOF() ) {
474 if(hadBackslashEscape)
475 rcVal << '\\';
476 if(inQuotes)
478 lineNo,
479 0,0,
480 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
481 return true;
482 }
483
484 c= mfc.Next<NC>();
485 } }
486
487 // 1) Quoted scalar
488 if( c == '"' ) {
489 bool hadBackslashEscape= false;
490 while(true) {
491 if(mfc.IsEOF()) {
492 if(hadBackslashEscape)
493 rcVal << '\\';
494 errors.emplace_back(
496 PathString(errors.get_allocator().GetAllocator(), rcFilePath) );
497 return true;
498 }
499
500 c= mfc.Next<NC>();
501
502 if( c == '\n' ) {
503 errors.emplace_back(
505 PathString(errors.get_allocator().GetAllocator(), rcFilePath));
506 lineStartRemaining= mfc.Remaining();
507 ++lineNo;
508 return true;
509 }
510
511 if( hadBackslashEscape ) {
512 if(c == 'u' && appendUnicodeEscapeFromMappedFile()) {
513 hadBackslashEscape= false;
514 continue;
515 }
516 appendEscape(c, hadBackslashEscape);
517 continue;
518 }
519 if( c == '\\' ) { hadBackslashEscape= true; continue; }
520 if( c == '"' ) break; // end quote
521
522 rcVal << c;
523 }
524
525 // ignore trailing whitespace, then consume end of line (or EOF)
526 while(!mfc.IsEOF()) {
527 char t= *mfc;
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; }
531 break; // any other character: treat as part of nothing (strict), but we just stop here
532 }
533
534 return true;
535 }
536
537 // 2) Plain scalar: rest-of-line (trim trailing whitespace), process escapes
538 {
539 bool hadBackslashEscape= false;
540 NString4K lineBuf;
541 while(true) {
542 if( c == '\r' ) {
543 if(mfc.IsEOF())
544 break;
545 c= mfc.Next<NC>();
546 continue;
547 }
548
549 if( c == '\n' )
550 break;
551
552 lineBuf << c;
553
554 if( mfc.IsEOF() )
555 break;
556
557 c= mfc.Next<NC>();
558 }
559
560 // trim trailing whitespace of the plain scalar (only spaces/tabs/etc., not newline)
561 while(lineBuf.IsNotEmpty()) {
562 char last= lineBuf.CharAtEnd();
563 if(last == ' ' || last == '\t' || last == '\r' || last == '\f' || last == '\v')
564 lineBuf.ShortenBy(1);
565 else
566 break;
567 }
568
569 // process escapes
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;
575 continue;
576 }
577 appendEscape(ch, hadBackslashEscape);
578 continue;
579 }
580 if(ch == '\\') { hadBackslashEscape= true; continue; }
581 rcVal << ch;
582 }
583 if(hadBackslashEscape)
584 rcVal << '\\';
585
586 if(c == '\n') {
587 lineStartRemaining= mfc.Remaining();
588 ++lineNo;
589 }
590
591 return true;
592 } }
593
594 return false;
595 }; // lambda function nextResourcePair
596
597 //------------------------------------------- main loop ------------------------------------------
598 while( nextResourcePair() ) {
599 // allocate name and value in the global allocator
602
603 // add to resources and to ordered list
604 destination.emplace_back(ResourceListEntry{name, value, lineNo-1});
605
606 if( !dedupSet.InsertIfNotExistent(name).second ) {
607 // search first occurrence
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 );
613} } }
614
615
616void PatchCPPFile( const Path& cppFilePath, const ResourceList& resources, RCErrorList& errors,
617 bool dryrun ) {
618
619 MonoAllocator& ma= resources.get_allocator().GetAllocator();
620 MonoAllocator::Resetter resetter(ma);
621 TextFile<1024> file(ma);
622 std::errc errc= file.Read(cppFilePath);
623 if(errc != std::errc()) {
625 0,0,0,
626 PathString(errors.get_allocator().GetAllocator(), cppFilePath));
627 return;
628 }
629
630 // search start/end of replacement lines
631 integer startLine= 0;
632 for( ; startLine < file.Size(); ++startLine ) {
633 if( file.At(startLine).IndexOf("ALIB-RESOURCE-COMPILER-REPLACEMENT-START") > 0)
634 break;
635 }
636 ++startLine;
637 integer endLine= startLine;
638 for( ; endLine < file.Size(); ++endLine ) {
639 if( file.At(endLine).IndexOf("ALIB-RESOURCE-COMPILER-REPLACEMENT-END") > 0)
640 break;
641 }
642 if( startLine >= file.Size() ) {
644 0,0,0,
645 PathString(errors.get_allocator().GetAllocator(), cppFilePath));
646 return;
647 }
648 if( endLine >= file.Size() ) {
650 0,0,0,
651 PathString(errors.get_allocator().GetAllocator(), cppFilePath));
652 return;
653 }
654
655 // adjust file length
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),
659 TextFile<1024>::StoredType(file.GetAllocator(), "") );
660
661
662 // loop over resources and replace
663 integer maxNameLen= 0;
664 for( const auto& entry : resources )
665 maxNameLen= (std::max)( maxNameLen, entry.Key.Length() );
666 maxNameLen= (std::min)( maxNameLen + 4 + 2, integer(30) );
667
668 auto hexDigit= [](uint32_t value) -> char {
669 value&= 0xFu;
670 return value < 10u ? char('0' + value) : char('A' + (value - 10u));
671 };
672
673 auto appendHexEscape= [&](NString1K& target, uint32_t codePoint) {
674 if(codePoint <= 0xFFFFu) {
675 target._<NC>("\\u");
676 for(int shift= 12; shift >= 0; shift-= 4)
677 target << hexDigit(codePoint >> uint32_t(shift));
678 return;
679 }
680 target._<NC>("\\U");
681 for(int shift= 28; shift >= 0; shift-= 4)
682 target << hexDigit(codePoint >> uint32_t(shift));
683 };
684
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));
688 };
689
690 unsigned char b0= readByteAt(idx);
691 if(b0 < 0x80u)
692 return uint32_t(b0);
693
694 auto isCont= [](unsigned char b) -> bool { return (b & 0xC0u) == 0x80u; };
695 integer remaining= src.Length() - idx;
696
697 if((b0 & 0xE0u) == 0xC0u) {
698 if(remaining < 2)
699 return 0xFFFDu;
700 unsigned char b1= readByteAt(idx + 1);
701 if(!isCont(b1))
702 return 0xFFFDu;
703 uint32_t cp= (uint32_t(b0 & 0x1Fu) << 6) | uint32_t(b1 & 0x3Fu);
704 if(cp < 0x80u)
705 return 0xFFFDu; // overlong
706 idx += 1;
707 return cp;
708 }
709 if((b0 & 0xF0u) == 0xE0u) {
710 if(remaining < 3)
711 return 0xFFFDu;
712 unsigned char b1= readByteAt(idx + 1);
713 unsigned char b2= readByteAt(idx + 2);
714 if(!isCont(b1) || !isCont(b2))
715 return 0xFFFDu;
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))
720 return 0xFFFDu; // overlong/surrogate
721 idx += 2;
722 return cp;
723 }
724 if((b0 & 0xF8u) == 0xF0u) {
725 if(remaining < 4)
726 return 0xFFFDu;
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))
731 return 0xFFFDu;
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)
737 return 0xFFFDu; // overlong/out of Unicode range
738 idx += 3;
739 return cp;
740 }
741
742 return 0xFFFDu;
743 };
744
745 integer actLine= startLine;
746 for( const auto& entry : resources ) {
747 //file << "// ";
748 NString1K line(" \"");
749 line._<NC>(entry.Key);
750 line._<NC>('"');
751 line._<NC>(NFill(' ', int(maxNameLen - line.Length())));
752
753 // write value: We break it into newlines when we find a ’\n'
754 line._<NC>(", A_CHAR(\"");
755 for( integer i= 0; i < entry.Value.Length(); ++i ) {
756 uint32_t cp= decodeUtf8CodePoint(entry.Value, i);
757 switch( cp ) {
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 ) {
769 line._<NC>("\")\n")
770 ._<NC>(NFill(' ', int(maxNameLen + 2)))
771 ._<NC>("A_CHAR(\"");
772 }
773 break;
774 }
775 default:
776 if(cp >= 0x20u && cp <= 0x7Eu)
777 line << char(cp);
778 else
779 appendHexEscape(line, cp);
780 break;
781 } }
782 line._<NC>("\"),");
783
784 file.At(actLine++).Allocate( file.GetAllocator(), line);
785 }
786
787 // write out
788 if(!dryrun) {
789 errc= file.Write(cppFilePath);
790 if(errc != std::errc()) {
791 errors.emplace_back( ResourceFileErrors::CPPFileNotWritable,
792 0,0,0,
793 PathString(errors.get_allocator().GetAllocator(),
794 cppFilePath));
795 } }
796}
797
798void ListErrors( RCErrorList& errors, alib::Paragraphs& output, const PathString& fileName ) {
799 for( const auto& err : errors ) {
800 if( err.ErrorCode == ResourceFileErrors::DuplicateResourceName ) {
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 );
807 continue;
808 }
809 NString msg= err.ErrorCode== ResourceFileErrors::ErrIllegalResourceName ? "Illegal resource name. In {}:{}:{}"
810 : err.ErrorCode== ResourceFileErrors::ErrNameTooLong ? "Resource name too long. In {}:{}:{}"
811 : err.ErrorCode== ResourceFileErrors::ErrLineEndWhileInQuotes ? "Line ended while in quotes. In {}:{}:{}"
812 : err.ErrorCode== ResourceFileErrors::RCFileNotFoundOrAccessible ? "Resource file not found or not accessible: {}{!X}{!X}"
813 : err.ErrorCode== ResourceFileErrors::CPPFileNotFoundOrAccessible? "CPP file not found or not accessible: {}{!X}{!X}"
814 : err.ErrorCode== ResourceFileErrors::CPPFileNotWritable ? "CPP file not writeable: {}{!X}{!X}"
815 : err.ErrorCode== ResourceFileErrors::CPPFileMissingStartMarker ? "Missing start marker in the cpp file {}{!X}{!X}"
816 : err.ErrorCode== ResourceFileErrors::CPPFileMissingEndMarker ? "Missing end marker in the cpp file {}{!X}{!X}"
817 : err.ErrorCode== ResourceFileErrors::CPPFileNotWritable ? "Error writing the cpp file {}{!X}{!X}"
818 : NULL_NSTRING;
819 ALIB_ASSERT_ERROR(msg.IsNotEmpty(), "CAMP/RESCMP", "Unhandled Error: ", int(err.ErrorCode))
820 output.Add( msg, err.AdditonalInfo, err.LineNo, err.ColNo );
821} }
822
823bool DevtimeResourceCompiler::Do( const NString& alibrcFileName,
824 const NString& callingFile,
825 Camp& campInstance,
826 const NString& resourceCategory,
827 bool allowReplacements,
828 const NString& cppFileName ) {
829
830 // RC-File: Get absolute path and stats
831 Path rcFilePath= Path(alibrcFileName);
832 if( !rcFilePath.IsAbsolute() ) {
833 rcFilePath.Reset(callingFile);
834 rcFilePath.ChangeToParent();
835 rcFilePath << DIRECTORY_SEPARATOR << alibrcFileName;
836 rcFilePath.MakeCanonical();
837 }
838
839 FileStatus rcFileStat (rcFilePath);
840 if( rcFileStat.ScanState() != FileStatus::ScanStates::STATS ) {
841 ALIB_ERROR( "CAMP/RESCMP", "Could not find resource file \"{}\"", rcFilePath )
842 return false;
843 }
844
845 // CPP-File: Get an absolute path and stats - only if provided
846 Path cppFilePath = Path(cppFileName);
847 if(cppFilePath.IsNotEmpty()) {
848 if( !cppFilePath.IsAbsolute() ) {
849 cppFilePath.Reset(callingFile);
850 cppFilePath.ChangeToParent();
851 cppFilePath << DIRECTORY_SEPARATOR << cppFileName;
852 cppFilePath.MakeCanonical();
853 }
854
855 FileStatus cppFileStat (cppFilePath);
856 if( cppFileStat.ScanState() != FileStatus::ScanStates::STATS ) {
857 ALIB_ERROR( "CAMP/RESCMP", "Could not find cpp file to write resource-bulkload \"{}\"",
858 cppFilePath )
859 return false;
860 }
861
862 // not to update? Note: we add 10ms tolerance. This allows copying/downloading/checking out
863 // source files (in the wrong order) to still not re-generate the cpp file. Only if the
864 // rc-file seems to have been edited, we continue.
865 if( cppFileStat.MDate() - DateTime::Duration::FromMilliseconds(10)
866 > rcFileStat.MDate() )
867 return false;
868 }
869
870 // Load resources
871 if(!ma) ma= new MonoAllocator(ALIB_DBG("CampRC",) 16);
872 ResourceList* orderedList = (*ma)().New<ResourceList>(*ma);
873 RCErrorList* errors = (*ma)().New<RCErrorList>(*ma);
874 LoadResourceFile(rcFilePath, *orderedList, *errors);
875 if( !errors->empty() ) {
876 Paragraphs output;
877 ListErrors(*errors, output, rcFilePath);
878 ALIB_ERROR( "CAMP/RESCMP", "Errors while reading resource file \"{}\":\n{}", rcFilePath,
879 output.Buffer )
880 return false;
881 }
882
883 // add resources to the given pool
884 ResourcePool& pool= campInstance.GetResourcePool();
885 for( auto& entry : *orderedList ) {
886 ALIB_DBG(bool wasExisting=)
887 pool.BootstrapAddOrReplace( resourceCategory, entry.Key, entry.Value );
888 ALIB_ASSERT_ERROR(!wasExisting || allowReplacements, "CAMP/RESCMP",
889 "Doubly defined resource in {}:{}", rcFilePath, entry.LineNo )
890 }
891
892 // write cpp file
893 if( cppFilePath.IsNotEmpty() ) {
894 PatchCPPFile(cppFilePath, *orderedList, *errors);
895 if( !errors->empty() ) {
896 Paragraphs output;
897 ListErrors(*errors, output, cppFilePath);
898 ALIB_ERROR( "CAMP/RESCMP", "Errors while patching cpp file \"{}\":\n{}", cppFilePath,
899 output.Buffer )
900 return false;
901 } }
902 return true;
903}
904
905
906#include "ALib.Lang.CIMethods.H"
907
908
909} // namespace [alib::camp]
910
911
912#endif // ALIB_CAMP_RESOURCE_COMPILATION
#define ALIB_ERROR(domain,...)
#define ALIB_DBG(...)
#define ALIB_ASSERT_ERROR(cond, domain,...)
void Add(boxing::TBoxes< TAllocatorArgs > &args)
@ 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)
Definition mappedfile.cpp:9
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".
Definition string.hpp:2174
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.
Definition string.hpp:2256
resources::ResourcePool ResourcePool
Type alias in namespace #"%alib".
strings::TFill< nchar > NFill
Type alias in namespace #"%alib".
Definition format.hpp:579
NLocalString< 1024 > NString1K
Type alias name for #"TLocalString;TLocalString<nchar,1024>".
lang::integer integer
Type alias in namespace #"%alib".
Definition integers.hpp:149
system::TTextFile< NString, TEXTFILE_DEFAULT_ALLOCATOR, TLocalBufferSize > TextFile
Definition textfile.hpp:207
LocalString< 4096 > String4K
Type alias name for #"TLocalString;TLocalString<character,4096>".
strings::TString< character > String
Type alias in namespace #"%alib".
Definition string.hpp:2165
system::Path Path
Type alias in namespace #"%alib".
Definition path.hpp:417
system::MappedFile MappedFile
Type alias in namespace #"%alib".
strings::TString< PathCharType > PathString
The string-type used with this ALib Module.
Definition path.hpp:34
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.
Definition path.hpp:63
NLocalString< 512 > NString512
Type alias name for #"TLocalString;TLocalString<nchar,512>".
format::Paragraphs Paragraphs
Type alias in namespace #"%alib".
TAllocator & GetAllocator() const noexcept