1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
30 // ////////////////////////////////////////////////////////////////////////// //
34 //==========================================================================
36 // VLanguage::VLanguage
38 //==========================================================================
39 VLanguage::VLanguage () noexcept
{
43 //==========================================================================
45 // VLanguage::~VLanguage
47 //==========================================================================
48 VLanguage::~VLanguage () noexcept
{
52 //==========================================================================
54 // VLanguage::FreeNonDehackedStrings
56 //==========================================================================
57 void VLanguage::FreeNonDehackedStrings () {
58 for (auto &&it
: table
.first()) {
59 if (it
.getValue().PassNum
!= 0) it
.removeCurrentNoAdvance();
65 //==========================================================================
67 // VLanguage::LoadStrings
69 //==========================================================================
70 void VLanguage::LoadStrings (const char *LangId
) {
71 FreeNonDehackedStrings();
72 for (auto &&it
: WadNSNameIterator(NAME_language
, WADNS_Global
)) {
73 const int Lump
= it
.lump
;
75 if (VStr::Cmp(LangId
, "**") != 0) {
76 ParseLanguageScript(Lump
, "*", true, j
++);
77 ParseLanguageScript(Lump
, LangId
, true, j
++);
78 ParseLanguageScript(Lump
, LangId
, false, j
++);
80 ParseLanguageScript(Lump
, "**", true, j
++);
85 //==========================================================================
87 // VLanguage::ParseLanguageScript
89 //==========================================================================
90 void VLanguage::ParseLanguageScript (vint32 Lump
, const char *InCode
, bool ExactMatch
, vint32 PassNum
) {
92 Code
[0] = VStr::ToLower(InCode
[0]);
93 Code
[1] = VStr::ToLower(InCode
[0] ? InCode
[1] : 0);
94 Code
[2] = (ExactMatch
&& InCode
[0] && InCode
[1] ? VStr::ToLower(InCode
[2]) : 0);
97 VScriptParser
*sc
= VScriptParser::NewWithLump(Lump
);
100 bool GotLanguageCode
= false;
102 bool Finished
= false;
104 while (!sc
->AtEnd()) {
105 if (sc
->Check("[")) {
106 // language identifiers
108 while (!sc
->Check("]")) {
110 size_t Len
= sc
->String
.Length();
112 if (Len
!= 2 && Len
!= 3) {
113 if (Len
== 1 && sc
->String
[0] == '*') {
117 } else if (Len
== 7 && !sc
->String
.ICmp("default")) {
122 sc
->Error(va("Language code must be 2 or 3 characters long, %s is %u characters long", *sc
->String
, (unsigned)Len
));
129 CurCode
[0] = VStr::ToLower(sc
->String
[0]);
130 CurCode
[1] = VStr::ToLower(sc
->String
[1]);
131 CurCode
[2] = (ExactMatch
? VStr::ToLower(sc
->String
[2]) : 0);
134 if (Code
[0] == CurCode
[0] && Code
[1] == CurCode
[1] && Code
[2] == CurCode
[2]) {
137 if (!GotLanguageCode
&& !Skip
) {
138 GCon
->Logf(NAME_Dev
, "parsing language script '%s' for language '%s'", *W_FullLumpName(Lump
), Code
);
140 GotLanguageCode
= true;
143 if (!GotLanguageCode
) {
144 // skip old binary LANGUAGE lumps
146 if (!Finished
) GCon
->Logf("Skipping binary LANGUAGE lump");
150 sc
->Error("Found a string without language specified");
153 // parse string definitions
155 // we are skipping this language
158 //sc->ExpectString();
159 while (!sc
->Check(";")) sc
->ExpectString();
165 if (sc
->String
== "$") {
166 //GCon->Logf(NAME_Warning, "%s: conditionals in language script is not supported yet", *sc->GetLoc().toStringNoCol());
167 sc
->Expect("ifgame");
170 if (!sc
->Check(")")) {
172 gmname
= sc
->String
.xstrip();
173 if (!sc
->Check(")")) {
174 GCon
->Logf(NAME_Error
, "%s: invalid conditional in language script", *sc
->GetLoc().toStringNoCol());
175 while (!sc
->Check(")")) sc
->ExpectString();
178 GCon
->Logf(NAME_Warning
, "%s: empty conditionals in language script", *sc
->GetLoc().toStringNoCol());
180 if (!gmname
.isEmpty() && !game_name
.asStr().startsWithCI(gmname
)) {
181 //if (!gmname.isEmpty()) GCon->Logf(NAME_Init, "%s: skipped conditional for game '%s'", *sc->GetLoc().toStringNoCol(), *gmname);
184 while (!sc
->Check(";")) sc
->ExpectString();
190 VName
Key(*sc
->String
, VName::AddLower
);
193 VStr Value
= HandleEscapes(sc
->String
);
194 while (!sc
->Check(";")) {
196 Value
+= HandleEscapes(sc
->String
);
199 // check for replacement
200 VLangEntry
*vep
= table
.get(Key
);
201 if (!vep
|| vep
->PassNum
>= PassNum
) {
204 Entry
.PassNum
= PassNum
;
205 table
.put(Key
, Entry
);
206 //GCon->Logf(NAME_Dev, " [%s]=[%s]", *VStr(Key).quote(), *Value.quote());
215 //==========================================================================
217 // VLanguage::HandleEscapes
219 //==========================================================================
220 VStr
VLanguage::HandleEscapes (VStr Src
) {
221 bool hasWork
= false;
222 for (size_t i
= Src
.Length(); i
> 0; --i
) if (Src
[i
-1] == '\\') { hasWork
= true; break; }
223 if (!hasWork
) return VStr(Src
);
225 for (int i
= 0; i
< Src
.Length(); ++i
) {
230 if (c
== 'n') c
= '\n';
231 else if (c
== 'r') c
= '\r';
232 else if (c
== 't') c
= '\t';
233 else if (c
== 'c') c
= -127;
234 else if (c
== '\n') continue;
242 //==========================================================================
246 //==========================================================================
247 VStr
VLanguage::Find (VName Key
, bool *found
) const {
248 if (Key
== NAME_None
) {
249 if (found
) *found
= true;
252 const VLangEntry
*vep
= table
.get(Key
);
254 if (found
) *found
= true;
258 for (const char *s
= *Key
; *s
; ++s
) {
259 if (*s
>= 'A' && *s
<= 'Z') {
260 // found uppercase letter, try lowercase name
261 VName loname
= VName(*Key
, VName::FindLower
);
262 if (loname
!= NAME_None
) {
263 vep
= table
.get(loname
);
265 if (found
) *found
= true;
272 if (found
) *found
= false;
277 //==========================================================================
281 //==========================================================================
282 VStr
VLanguage::Find (const char *s
, bool *found
) const {
285 if (found
) *found
= true;
288 VName loname
= VName(s
, VName::FindLower
);
289 if (loname
!= NAME_None
) {
290 const VLangEntry
*vep
= table
.get(loname
);
292 if (found
) *found
= true;
296 if (found
) *found
= false;
301 //==========================================================================
303 // VLanguage::operator[]
305 //==========================================================================
306 VStr
VLanguage::operator [] (VName Key
) const {
308 VStr res
= Find(Key
, &found
);
309 if (found
) return res
;
314 //==========================================================================
316 // VLanguage::operator[]
318 //==========================================================================
319 VStr
VLanguage::operator [] (const char *s
) const {
321 VStr res
= Find(s
, &found
);
322 if (found
) return res
;
323 return (s
? VStr(s
) : VStr::EmptyString
);
327 //==========================================================================
329 // VLanguage::Translate
333 //==========================================================================
334 VStr
VLanguage::Translate (const VStr
&s
) const {
335 if (s
.length() < 2 || s
[0] != '$') return s
;
337 VStr res
= Find(*s
+1, &found
);
343 //==========================================================================
345 // VLanguage::HasTranslation
347 //==========================================================================
348 bool VLanguage::HasTranslation (VName s
) const {
350 (void)Find(s
, &found
);
355 //==========================================================================
357 // VLanguage::HasTranslation
359 //==========================================================================
360 bool VLanguage::HasTranslation (const char *s
) const {
362 (void)Find(s
, &found
);
367 //==========================================================================
369 // VLanguage::GetStringId
371 //==========================================================================
372 VName
VLanguage::GetStringId (VStr Str
) {
373 for (auto it
= table
.first(); it
; ++it
) {
374 if (it
.getValue().Value
== Str
) return it
.GetKey();
380 //==========================================================================
382 // VLanguage::GetStringIdCI
384 //==========================================================================
385 VName
VLanguage::GetStringIdCI (VStr Str
) {
386 for (auto it
= table
.first(); it
; ++it
) {
387 if (it
.getValue().Value
.strEquCI(Str
)) return it
.GetKey();
393 //==========================================================================
395 // VLanguage::ReplaceString
397 //==========================================================================
398 void VLanguage::ReplaceString (VName Key
, VStr Value
) {
402 table
.put(Key
, Entry
);