/svarlang.lib/tags/20240227/auto_exe.c |
---|
0,0 → 1,53 |
/* This file is part of the svarlang project and is published under the terms |
* of the MIT license. |
* |
* Copyright (C) 2021-2023 Mateusz Viste |
* |
* Permission is hereby granted, free of charge, to any person obtaining a |
* copy of this software and associated documentation files (the "Software"), |
* to deal in the Software without restriction, including without limitation |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
* and/or sell copies of the Software, and to permit persons to whom the |
* Software is furnished to do so, subject to the following conditions: |
* |
* The above copyright notice and this permission notice shall be included in |
* all copies or substantial portions of the Software. |
* |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
* DEALINGS IN THE SOFTWARE. |
*/ |
#include "svarlang.h" |
int svarlang_autoload_exepath(const char *selfexe, const char *lang) { |
unsigned short selflen; |
unsigned long self_ext_backup; |
unsigned long *self_ext_ptr; |
int res; |
/* validate selfexe: must be at least 5 bytes long and 4th char from end must |
* be a dot (like "a.exe" or "c:\b.com" or "..\..\test\run.exe") */ |
if (!selfexe) return(-200); |
for (selflen = 0; selfexe[selflen] != 0; selflen++); |
if ((selflen < 5) || (selfexe[selflen - 4] != '.')) return(-200); |
self_ext_ptr = (void *)(selfexe + selflen - 3); /* disregard CONST (I revert original content later, so the caller won't notice */ |
/* copy extension to buffer and replace it with "lng" */ |
self_ext_backup = *self_ext_ptr; |
*self_ext_ptr = 0x00474E4Cl; /* "LNG\0" */ |
/* try loading it now */ |
res = svarlang_load(selfexe, lang); |
/* restore the original filename and quit */ |
*self_ext_ptr = self_ext_backup; |
return(res); |
} |
/svarlang.lib/tags/20240227/auto_nls.c |
---|
0,0 → 1,62 |
/* This file is part of the svarlang project and is published under the terms |
* of the MIT license. |
* |
* Copyright (C) 2021-2023 Mateusz Viste |
* |
* Permission is hereby granted, free of charge, to any person obtaining a |
* copy of this software and associated documentation files (the "Software"), |
* to deal in the Software without restriction, including without limitation |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
* and/or sell copies of the Software, and to permit persons to whom the |
* Software is furnished to do so, subject to the following conditions: |
* |
* The above copyright notice and this permission notice shall be included in |
* all copies or substantial portions of the Software. |
* |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
* DEALINGS IN THE SOFTWARE. |
*/ |
#include "svarlang.h" |
int svarlang_autoload_pathlist(const char *progname, const char *pathlist, const char *lang) { |
char buff[128]; |
unsigned short i, ii; |
/* read and validate LANG and pathlist */ |
if ((!lang) || (lang[0] == 0) || (!pathlist)) return(-1); |
/* look into every path in NLSPATH */ |
while (*pathlist != 0) { |
/* skip any leading ';' separators */ |
while (*pathlist == ';') pathlist++; |
if (*pathlist == 0) return(-3); |
/* copy nlspath to buff and remember len */ |
for (i = 0; (pathlist[i] != 0) && (pathlist[i] != ';'); i++) buff[i] = pathlist[i]; |
pathlist += i; |
/* add a trailing backslash if there is none (non-empty paths empty) */ |
if ((i > 0) && (buff[i - 1] != '\\')) buff[i++] = '\\'; |
/* append progname + ".LNG" to the path */ |
for (ii = 0; progname[ii] != 0; ii++) buff[i++] = progname[ii]; |
buff[i++] = '.'; |
buff[i++] = 'L'; |
buff[i++] = 'N'; |
buff[i++] = 'G'; |
buff[i] = 0; |
if (svarlang_load(buff, lang) == 0) return(0); |
} |
/* failed to load anything */ |
return(-4); |
} |
/svarlang.lib/tags/20240227/history.txt |
---|
0,0 → 1,31 |
20240227 |
- replaced inline _asm by pragma aux (more compact code, open watcom only) |
20230730 |
- dropped svarlang_autoload() (replaced by more specialized functions below) |
- added svarlang_autoload_exepath() and svarlang_autoload_nlspath() |
- svarlang_load() simplified so it takes the target filename as an argument |
- file access relies on fopen() when svarlang is compiled with -DWITHSTDIO |
- new file format: sorted dictionary for faster lookup (by Bernd Boeckmann) |
breaking change! See svarlang.txt for file format specification |
20230630 |
- tlumacz.exe warns about empty strings (patch by Bernd Boeckmann) |
- tlumacz.exe does not abort when a malformed line is found |
20230629 |
- deflang.c has each message on a different line so it is nicer to VCSes |
20230628 |
- added support for \e sequences in translation strings |
- implemented svarlang_getver() |
20220314 |
- added support for flagging strings as being "dirty", eg: ?1.1:Hello, World |
20220309 |
- static lib buffer is sized to fit the largest lang block +5% of margin |
(was: twice the size of the reference language) |
20220226 |
- replaced fopen() and friends by direct DOS calls (smaller memory footprint) |
/svarlang.lib/tags/20240227/makefile |
---|
0,0 → 1,64 |
# |
# make instructions to build svarlang and tlumacz.exe with OpenWatcom |
# Copyright (C) 2021-2024 Mateusz Viste |
# |
ALLBIN = svarlngs.lib svarlngc.lib svarlngm.lib svarlngl.lib tlumacz.exe |
all: $(ALLBIN) |
CFLAGS = -0 -wx -we -os -s |
# uncomment this if you prefer SvarLANG to use fopen() and friends to handle |
# file access instead of raw DOS calls. this might make the program larger if |
# it does not use FILE already, but it allows for 100% ANSI C compliancy. |
#CFLAGS += -DWITHSTDIO |
ALLFILES = auto_exe.c auto_nls.c svarlang.c version.c |
svarlngs.lib: $(ALLFILES) |
wcc $(CFLAGS) -ms auto_exe.c |
wcc $(CFLAGS) -ms auto_nls.c |
wcc $(CFLAGS) -ms svarlang.c |
wcc $(CFLAGS) -ms version.c |
if exist svarlngs.lib del svarlngs.lib |
wlib -n svarlngs.lib +auto_exe.obj +auto_nls.obj +svarlang.obj +version.obj |
svarlngc.lib: $(ALLFILES) |
wcc $(CFLAGS) -mc auto_exe.c |
wcc $(CFLAGS) -mc auto_nls.c |
wcc $(CFLAGS) -mc svarlang.c |
wcc $(CFLAGS) -mc version.c |
if exist svarlngc.lib del svarlngc.lib |
wlib -n svarlngc.lib +auto_exe.obj +auto_nls.obj +svarlang.obj +version.obj |
svarlngm.lib: $(ALLFILES) |
wcc $(CFLAGS) -mm auto_exe.c |
wcc $(CFLAGS) -mm auto_nls.c |
wcc $(CFLAGS) -mm svarlang.c |
wcc $(CFLAGS) -mm version.c |
if exist svarlngm.lib del svarlngm.lib |
wlib -n svarlngm.lib +auto_exe.obj +auto_nls.obj +svarlang.obj +version.obj |
svarlngl.lib: $(ALLFILES) |
wcc $(CFLAGS) -ml auto_exe.c |
wcc $(CFLAGS) -ml auto_nls.c |
wcc $(CFLAGS) -ml svarlang.c |
wcc $(CFLAGS) -ml version.c |
if exist svarlngl.lib del svarlngl.lib |
wlib -n svarlngl.lib +auto_exe.obj +auto_nls.obj +svarlang.obj +version.obj |
release: $(ALLBIN) .symbolic |
if exist svarlang.zip del svarlang.zip |
if exist svrl_src.zip del svrl_src.zip |
zip -9rkDX svarlang.zip *.lib *.h *.txt tlumacz.exe |
zip -9rkDX svrl_src.zip *.c *.txt *.h makefile |
tlumacz.exe: tlumacz.c |
wcl -0 -y -cc -wx -mc -lr -we -ox tlumacz.c |
del *.obj |
clean: .symbolic |
del *.exe |
del *.obj |
del *.lib |
del *.zip |
/svarlang.lib/tags/20240227/svarlang.c |
---|
0,0 → 1,184 |
/* This file is part of the svarlang project and is published under the terms |
* of the MIT license. |
* |
* Copyright (C) 2021-2024 Mateusz Viste |
* |
* Permission is hereby granted, free of charge, to any person obtaining a |
* copy of this software and associated documentation files (the "Software"), |
* to deal in the Software without restriction, including without limitation |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
* and/or sell copies of the Software, and to permit persons to whom the |
* Software is furnished to do so, subject to the following conditions: |
* |
* The above copyright notice and this permission notice shall be included in |
* all copies or substantial portions of the Software. |
* |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
* DEALINGS IN THE SOFTWARE. |
*/ |
/* if WITHSTDIO is enabled, then remap file operations to use the standard |
* stdio amenities */ |
#ifdef WITHSTDIO |
#include <stdio.h> /* FILE, fopen(), fseek(), etc */ |
typedef FILE* FHANDLE; |
#define FOPEN(x) fopen(x, "rb") |
#define FCLOSE(x) fclose(x) |
#define FSEEK(f,b) fseek(f,b,SEEK_CUR) |
#define FREAD(f,t,b) fread(t, 1, b, f) |
#else |
#include <i86.h> /* FP_SEG, FP_OFF */ |
typedef unsigned short FHANDLE; |
#endif |
#include "svarlang.h" |
/* supplied through DEFLANG.C */ |
extern char svarlang_mem[]; |
extern unsigned short svarlang_dict[]; |
extern const unsigned short svarlang_memsz; |
extern const unsigned short svarlang_string_count; |
const char *svarlang_strid(unsigned short id) { |
unsigned short left = 0, right = svarlang_string_count - 1, x; |
unsigned short v; |
if (svarlang_string_count == 0) return(""); |
while (left <= right) { |
x = left + ((right - left ) >> 2); |
v = svarlang_dict[x * 2]; |
if (id == v) return(svarlang_mem + svarlang_dict[x * 2 + 1]); |
if (id > v) { |
left = x + 1; |
} else { |
right = x - 1; |
} |
} |
return(""); |
} |
/* routines below are simplified (dos-based) versions of the libc FILE-related |
* functions. Using them avoids a dependency on FILE, hence makes the binary |
* smaller if the application does not need to pull fopen() and friends |
* I use pragma aux directives for more compact size. open-watcom only. */ |
#ifndef WITHSTDIO |
static unsigned short FOPEN(const char far *s); |
#pragma aux FOPEN = \ |
"push ds" \ |
"push es" \ |
"pop ds" \ |
"mov ax, 0x3D00" /* open file, read-only (fname at DS:DX) */ \ |
"int 0x21" \ |
"jnc DONE" \ |
"xor ax, ax" /* return 0 on error */ \ |
"DONE:" \ |
"pop ds" \ |
parm [es dx] \ |
value [ax]; |
static void FCLOSE(unsigned short handle); |
#pragma aux FCLOSE = \ |
"mov ah, 0x3E" \ |
"int 0x21" \ |
modify [ax] /* AX might contain an error code on failure */ \ |
parm [bx] |
static unsigned short FREAD(unsigned short handle, void far *buff, unsigned short bytes); |
#pragma aux FREAD = \ |
"push ds" \ |
"push es" \ |
"pop ds" \ |
"mov ah, 0x3F" /* read cx bytes from file handle bx to DS:DX */ \ |
"int 0x21" \ |
"jnc ERR" \ |
"xor ax, ax" /* return 0 on error */ \ |
"ERR:" \ |
"pop ds" \ |
parm [bx] [es dx] [cx] \ |
value [ax] |
static void FSEEK(unsigned short handle, unsigned short bytes); |
#pragma aux FSEEK = \ |
"mov ax, 0x4201" /* move file pointer from cur pos + CX:DX */ \ |
"xor cx, cx" \ |
"int 0x21" \ |
parm [bx] [dx] \ |
modify [ax cx dx] |
#endif |
int svarlang_load(const char *fname, const char *lang) { |
unsigned short langid; |
unsigned short buff16[2]; |
FHANDLE fd; |
signed char exitcode = 0; |
struct { |
unsigned long sig; |
unsigned short string_count; |
} hdr; |
langid = *((unsigned short *)lang); |
langid &= 0xDFDF; /* make sure lang is upcase */ |
fd = FOPEN(fname); |
if (!fd) return(-1); |
/* read hdr, sig should be "SvL\x1a" (0x1a4c7653) */ |
if ((FREAD(fd, &hdr, 6) != 6) || (hdr.sig != 0x1a4c7653L) || (hdr.string_count != svarlang_string_count)) { |
exitcode = -2; |
goto FCLOSE_AND_EXIT; |
} |
for (;;) { |
/* read next lang id and string table size in file */ |
if (FREAD(fd, buff16, 4) != 4) { |
exitcode = -3; |
goto FCLOSE_AND_EXIT; |
} |
/* is it the lang I am looking for? */ |
if (buff16[0] == langid) break; |
/* skip to next lang (in two steps to avoid a potential uint16 overflow) */ |
FSEEK(fd, svarlang_string_count * 4); |
FSEEK(fd, buff16[1]); |
} |
/* load dictionary & strings, but only if I have enough memory space */ |
if ((buff16[1] >= svarlang_memsz) |
|| (FREAD(fd, svarlang_dict, svarlang_string_count * 4) != svarlang_string_count * 4) |
|| (FREAD(fd, svarlang_mem, buff16[1]) != buff16[1])) { |
exitcode = -4; |
} |
FCLOSE_AND_EXIT: |
FCLOSE(fd); |
return(exitcode); |
} |
/svarlang.lib/tags/20240227/svarlang.h |
---|
0,0 → 1,79 |
/* This file is part of the svarlang project and is published under the terms |
* of the MIT license. |
* |
* Copyright (C) 2021-2024 Mateusz Viste |
* |
* Permission is hereby granted, free of charge, to any person obtaining a |
* copy of this software and associated documentation files (the "Software"), |
* to deal in the Software without restriction, including without limitation |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
* and/or sell copies of the Software, and to permit persons to whom the |
* Software is furnished to do so, subject to the following conditions: |
* |
* The above copyright notice and this permission notice shall be included in |
* all copies or substantial portions of the Software. |
* |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
* DEALINGS IN THE SOFTWARE. |
*/ |
#ifndef SVARLANG_H |
#define SVARLANG_H |
/* library version */ |
#define SVARLANGVER "20240227" |
/* returns a pointer to a string with the SvarLANG's library version, |
* independently of the SVARLANGVER string above. */ |
const char *svarlang_getver(void); |
/* loads lang translations from file fname. |
* |
* only the two first letters of the lang strings are meaningful and they are |
* case insensitive. |
* |
* a typical call would be: svarlang_load("myprog.lng", "PL"); |
* |
* this function returns 0 on success, non-zero otherwise. It is still possible |
* to call svarlang_strid() after a load failure, the previously loaded |
* language will be used then, or the default language if no loading has been |
* done yet. */ |
int svarlang_load(const char *fname, const char *lang); |
/* tries loading lang strings from a file located in the executable's |
* directory that is named like the executable but with an *.LNG extension. |
* this is certainly the most practical way of loading svarlang. |
* selfexe should point to the executable's full filename path (either relative |
* or absolute). You may want to pass argv[0] or __argv[0] there. example: |
* |
* svarlang_autoload_exepath(argv[0], getenv("LANG")); |
*/ |
int svarlang_autoload_exepath(const char *selfexe, const char *lang); |
/* this looks in a list of paths separated by ';' to locate a translation file |
* for progname. this might be called by some FreeDOS programs that rely on the |
* NLSPATH environment variable for locating strings. example: |
* |
* svarlang_autoload_pathlist("myprog", getenv("NLSPATH"), getenv("LANG")); |
*/ |
int svarlang_autoload_pathlist(const char *progname, const char *pathlist, const char *lang); |
/* Returns a pointer to the string "id". Does not require svalang_load() to be |
* executed, but then it will only return the reference language strings. |
* a string id is the concatenation of the CATS-style identifiers, for example |
* string 1,0 becomes 0x0100, string 2.10 is 0x020A, etc. |
* It NEVER returns NULL, if id not found then an empty string is returned */ |
const char *svarlang_strid(unsigned short id); |
/* a convenience definition to fetch strings by their CATS-style pairs instead |
* of the 16-bit id. */ |
#define svarlang_str(x, y) svarlang_strid((x << 8) | y) |
#endif |
/svarlang.lib/tags/20240227/svarlang.txt |
---|
0,0 → 1,123 |
SVARLANG.LIB - THE SVARDOS TRANSLATION C LIBRARY |
Copyright (C) 2021-2024 Mateusz Viste |
SvarLANG is a library and toolset for enabling SvarDOS applications to easily |
support multiple languages. It is part of the SvarDOS project. |
Homepage: http://svardos.org/svarlang/ |
### PREPARING TRANSLATION FILES ############################################### |
The translation files must be CATS-style text files in the usual format: |
1.1:Hello, World! |
1.2:Help screen |
2.0:Type /? for more options |
The files must be named as EN.TXT, DE.TXT, FR.TXT, etc. Then, they must be |
converted into SvarLANG's binary format using the TLUMACZ tool: |
tlumacz en fr pl (...) |
The first language provided in the command line is the reference language and |
is used both as the default (embedded in the application) language, as well as |
to substitute messages missing in other languages. |
TLUMACZ computes two files: |
* OUT.LNG - the binary file that contains all translations |
* DEFLANG.C - the default translations that will be embedded into the program |
Then, DEFLANG.C must be compiled and linked to your program along with |
SVARLNGx.LIB. From there you will be able to use SvarLANG calls, like this |
very basic example: |
svarlang_load("myprogram.lng", "pl"); /* load PL lang from myprogram.lng */ |
puts(svarlang_str(2, 0)); /* display the string with id 2.0 */ |
A more practical, real-world example would probably be this one: |
svarlang_autoload_exepath(argv[0], getenv("LANG")); |
puts(svarlang_str(2, 0)); |
Read svarlang.h for more information about available functions. |
### ESCAPED CHARACTERS ######################################################## |
Translation strings may contain some escaped characters. At this time only the |
following escaped characters are supported: \e \r \n \t and \\ |
### DIRTY STRINGS ############################################################# |
In the CATS-style source translation, lines may be prefixed with a '?' sign: |
?1.1:Hello, World! |
Such string is used by tlumacz like any other, but it is also reported on the |
command-line with a warning about the line being "dirty" (that is, requiring |
to be reviewed by a translator). |
### ENVIRONMENT ############################################################### |
The program translation file should be named "PROGNAME.LNG", where PROGNAME |
is the program's name. This file should be placed in a well-known location, |
typically the program's own directory. |
The %LANG% environment variable usually defines what language should be loaded, |
albeit the program can just as well provide its own controls for language |
selection and pass this information to svarlang_load() accordingly. |
### WHY IS IT BETTER THAN CATS? ############################################### |
The CATS library is heavier and slower, as it embeds a text-file parser. |
Translations also take more disk space since each language is stored in a |
separate file, leading to cluster waste. Finally, CATS requires default strings |
to be part of the application's source code, while SvarLANG keeps all strings |
in TXT files and embedds the default one inside the application in an automated |
way at compile time. |
There is also a licensing issue: CATS/Kitten libraries are published under the |
terms of a viral, corrosive license. SvarLANG, on the other hand, is published |
under a truly free, liberal MIT license. |
### FILE FORMAT ############################################################### |
File = |
magic : Char[4] := "SvL\x1a" (ie. "SvL" followed with a 0x1a char) |
; 0x1a is an end-of-file marker that prevents TYPE garbage |
num_strings : U16 |
languages : array[num_languages] of Language |
Language = |
lang_id : Char[2] |
len_strings : U16 := SizeOf(strings) |
dictionary : StringDict |
strings : array[File.num_strings] of StringZ |
StringDict = |
elements : array[File.num_strings] of DictEntry |
; sorted by DictEntry.Id |
DictEntry = |
id : U16 |
offset : U16 |
; relative to Language.strings[0] |
StringZ = array[?] of Char ; zero-terminated string |
NOTE: All numeric values are stored in x86 (little endian) order. |
####################################################################### EOF ### |
/svarlang.lib/tags/20240227/tlumacz.c |
---|
0,0 → 1,483 |
/* |
* Copyright (C) 2021-2023 Mateusz Viste |
* |
* Dictionary-based lookups contributed by Bernd Boeckmann, 2023 |
* |
* usage: tlumacz en fr pl etc |
* |
* computes an out.lng file that contains all language resources. |
* |
*/ |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
#include <ctype.h> |
#include "svarlang.h" |
#define STRINGS_CAP 65000 /* string storage size in characters */ |
#define DICT_CAP 10000 /* dictionary size in elements */ |
/* read a single line from fd and fills it into dst, returns line length |
* ending CR/LF is trimmed, as well as any trailing spaces */ |
static unsigned short readl(char *dst, size_t dstsz, FILE *fd) { |
unsigned short l, lastnonspace = 0; |
if (fgets(dst, (int)dstsz, fd) == NULL) return(0xffff); /* EOF */ |
/* trim at first CR or LF and return len */ |
for (l = 0; (dst[l] != 0) && (dst[l] != '\r') && (dst[l] != '\n'); l++) { |
if (dst[l] != ' ') lastnonspace = l; |
} |
if (lastnonspace < l) l = lastnonspace + 1; /* rtrim */ |
dst[l] = 0; |
return(l); |
} |
/* parse a line in format "[?]1.50:somestring". fills id and returns a pointer to |
* the actual string part on success, or NULL on error */ |
static const char *parseline(unsigned short *id, const char *s) { |
int i; |
int dotpos = 0, colpos = 0, gotdigits = 0; |
/* strings prefixed by '?' are flagged as "dirty": ignore this flag here */ |
if (*s == '?') s++; |
/* I must have a . and a : in the first 9 bytes */ |
for (i = 0;; i++) { |
if (s[i] == '.') { |
if ((dotpos != 0) || (gotdigits == 0)) break; |
dotpos = i; |
gotdigits = 0; |
} else if (s[i] == ':') { |
if (gotdigits != 0) colpos = i; |
break; |
} else if ((s[i] < '0') || (s[i] > '9')) { |
break; |
} |
gotdigits++; |
} |
/* did I collect everything? */ |
if ((dotpos == 0) || (colpos == 0)) return(NULL); |
*id = atoi(s); |
*id <<= 8; |
*id |= atoi(s + dotpos + 1); |
/* printf("parseline(): %04X = '%s'\r\n", *id, s + colpos + 1); */ |
return(s + colpos + 1); |
} |
/* converts escape sequences like "\n" or "\t" into actual bytes, returns |
* the new length of the string. */ |
static unsigned short unesc_string(char *linebuff) { |
unsigned short i; |
for (i = 0; linebuff[i] != 0; i++) { |
if (linebuff[i] != '\\') continue; |
memmove(linebuff + i, linebuff + i + 1, strlen(linebuff + i)); |
if (linebuff[i] == 0) break; |
switch (linebuff[i]) { |
case 'e': |
linebuff[i] = 0x1B; /* ESC code, using hex because '\e' is not ANSI C */ |
break; |
case 'n': |
linebuff[i] = '\n'; |
break; |
case 'r': |
linebuff[i] = '\r'; |
break; |
case 't': |
linebuff[i] = '\t'; |
break; |
} |
} |
return(i); |
} |
#pragma pack(1) |
struct dict_entry { |
unsigned short id; |
unsigned short offset; |
}; |
#pragma pack() |
struct svl_lang { |
char id[2]; |
unsigned short num_strings; |
struct dict_entry *dict; |
size_t dict_cap; |
char *strings; |
char *strings_end; |
size_t strings_cap; |
}; |
static struct svl_lang *svl_lang_new(const char langid[2], size_t dict_cap, size_t strings_cap) { |
struct svl_lang *l; |
l = malloc(sizeof(struct svl_lang)); |
if (!l) return(NULL); |
l->id[0] = (char)toupper(langid[0]); |
l->id[1] = (char)toupper(langid[1]); |
l->dict = malloc(dict_cap * sizeof(struct dict_entry)); |
if (!l->dict) return(NULL); |
l->dict_cap = dict_cap; |
l->num_strings = 0; |
l->strings = l->strings_end = malloc(strings_cap); |
if (!l->strings) { |
free(l->dict); |
return(NULL); |
} |
l->strings_cap = strings_cap; |
return(l); |
} |
/* compacts the dict and string buffer */ |
static void svl_compact_lang(struct svl_lang *l) { |
size_t bytes; |
bytes = l->strings_end - l->strings; |
if (bytes < l->strings_cap) { |
l->strings = l->strings_end = realloc(l->strings, bytes); |
l->strings_end += bytes; |
l->strings_cap = bytes; |
} |
l->dict_cap = l->num_strings; |
l->dict = realloc(l->dict, l->dict_cap * sizeof(struct dict_entry)); |
} |
static void svl_lang_free(struct svl_lang *l) { |
l->num_strings = 0; |
if (l->dict) { |
free(l->dict); |
l->dict = NULL; |
} |
if (l->strings) { |
free(l->strings); |
l->strings = l->strings_end = NULL; |
} |
l->dict_cap = 0; |
l->strings_cap = 0; |
} |
static size_t svl_strings_bytes(const struct svl_lang *l) { |
return(l->strings_end - l->strings); |
} |
static size_t svl_dict_bytes(const struct svl_lang *l) { |
return(l->num_strings * sizeof(struct dict_entry)); |
} |
static int svl_add_str(struct svl_lang *l, unsigned short id, const char *s) { |
size_t len = strlen(s) + 1; |
size_t cursor; |
if ((l->strings_cap < svl_strings_bytes(l) + len) || (l->dict_cap < (l->num_strings + 1) * sizeof(struct dict_entry))) { |
return(0); |
} |
/* find dictionary insert position, search backwards in assumption |
that in translation files, strings are generally ordered ascending */ |
for (cursor = l->num_strings; cursor > 0 && l->dict[cursor-1].id > id; cursor--); |
memmove(&(l->dict[cursor+1]), &(l->dict[cursor]), sizeof(struct dict_entry) * (l->num_strings - cursor)); |
l->dict[cursor].id = id; |
l->dict[cursor].offset = l->strings_end - l->strings; |
memcpy(l->strings_end, s, len); |
l->strings_end += len; |
l->num_strings++; |
return(1); |
} |
static int svl_find(const struct svl_lang *l, unsigned short id) { |
size_t left = 0, right = l->num_strings - 1, x; |
unsigned short v; |
if (l->num_strings == 0) return(0); |
while (left <= right ) { |
x = left + ( (right - left ) >> 2 ); |
v = l->dict[x].id; |
if ( id == v ) return(1); /* found! */ |
if (id > v) { |
left = x + 1; |
} else { |
right = x - 1; |
} |
} |
return(0); |
} |
/* opens a CATS-style file and compiles it into a ressources lang block |
* returns 0 on error, or the size of the generated data block otherwise */ |
static unsigned short svl_lang_from_cats_file(struct svl_lang *l, struct svl_lang *refl) { |
unsigned short linelen; |
FILE *fd; |
char fname[] = "xx.txt"; |
static char linebuf[8192]; |
const char *ptr; |
unsigned short id, maxid=0, maxid_line, linecount; |
int i; |
fname[strlen(fname) - 6] = (char)tolower( l->id[0] ); |
fname[strlen(fname) - 5] = (char)tolower( l->id[1] ); |
fd = fopen(fname, "rb"); |
if (fd == NULL) { |
printf("ERROR: FAILED TO OPEN '%s'\r\n", fname); |
return(0); |
} |
for (linecount = 1;; linecount++) { |
linelen = readl(linebuf, sizeof(linebuf), fd); |
if (linelen == 0xffff) break; /* EOF */ |
if ((linelen == 0) || (linebuf[0] == '#')) continue; |
/* convert escaped chars to actual bytes (\n -> newline, etc) */ |
linelen = unesc_string(linebuf); |
/* read id and get ptr to actual string ("1.15:string") */ |
ptr = parseline(&id, linebuf); |
/* handle malformed lines */ |
if (ptr == NULL) { |
printf("WARNING: %s[#%u] is malformed (linelen = %u):\r\n", fname, linecount, linelen); |
puts(linebuf); |
continue; |
} |
/* ignore empty strings (but emit a warning) */ |
if (ptr[0] == 0) { |
printf("WARNING: %s[#%u] ignoring empty string %u.%u\r\n", fname, linecount, id >> 8, id & 0xff); |
continue; |
} |
/* warn about dirty lines */ |
if (linebuf[0] == '?') { |
printf("WARNING: %s[#%u] string id %u.%u is flagged as 'dirty'\r\n", fname, linecount, id >> 8, id & 0xff); |
} |
/* add the string contained in current line, if conditions are met */ |
if (!svl_find(l, id)) { |
if ((refl == NULL) || (svl_find(refl, id))) { |
if (!svl_add_str(l, id, ptr)) { |
printf("ERROR: %s[#%u] output size limit exceeded\r\n", fname, linecount); |
fclose(fd); |
return(0); |
} |
if (id >= maxid) { |
maxid = id; |
maxid_line = linecount; |
} else { |
printf("WARNING:%s[#%u] file unsorted - line %u has higher id %u.%u\r\n", fname, linecount, maxid_line, maxid >> 8, maxid & 0xff); |
} |
} else { |
printf("WARNING: %s[#%u] has an invalid id (%u.%u not present in ref lang)\r\n", fname, linecount, id >> 8, id & 0xff); |
} |
} else { |
printf("WARNING: %s[#%u] has a duplicated id (%u.%u)\r\n", fname, linecount, id >> 8, id & 0xff); |
} |
} |
fclose(fd); |
/* if reflang provided, pull missing strings from it */ |
if (refl != NULL) { |
for (i = 0; i < refl->num_strings; i++) { |
id = refl->dict[i].id; |
if (!svl_find(l, id)) { |
printf("WARNING: %s is missing string %u.%u (pulled from ref lang)\r\n", fname, id >> 8, id & 0xff); |
if (!svl_add_str(l, id, refl->strings + refl->dict[i].offset)) { |
printf("ERROR: %s[#%u] output size limit exceeded\r\n", fname, linecount); |
return(0); |
} |
} |
} |
} |
return(svl_strings_bytes(l)); |
} |
static int svl_write_header(unsigned short num_strings, FILE *fd) { |
return((fwrite("SvL\x1a", 1, 4, fd) == 4) && (fwrite(&num_strings, 1, 2, fd) == 2)); |
} |
static int svl_write_lang(const struct svl_lang *l, FILE *fd) { |
unsigned short strings_bytes = svl_strings_bytes(l); |
return((fwrite(&l->id, 1, 2, fd) == 2) && |
(fwrite(&strings_bytes, 1, 2, fd) == 2) && |
(fwrite(l->dict, 1, svl_dict_bytes(l), fd) == svl_dict_bytes(l)) && |
(fwrite(l->strings, 1, svl_strings_bytes(l), fd) == svl_strings_bytes(l))); |
} |
static int svl_write_c_source(const struct svl_lang *l, const char *fn, unsigned short biggest_langsz) { |
FILE *fd; |
int i; |
unsigned short strings_bytes = svl_strings_bytes(l); |
unsigned short nextnlat = 0; |
unsigned short allocsz; |
fd = fopen(fn, "wb"); |
if (fd == NULL) { |
puts("ERROR: FAILED TO OPEN OR CREATE DEFLANG.C"); |
return(0); |
} |
allocsz = biggest_langsz + (biggest_langsz / 20); |
printf("biggest lang block is %u bytes -> allocating a %u bytes buffer (5%% safety margin)\n", biggest_langsz, allocsz); |
fprintf(fd, "/* THIS FILE HAS BEEN GENERATED BY TLUMACZ (PART OF THE SVARLANG LIBRARY) */\r\n"); |
fprintf(fd, "const unsigned short svarlang_memsz = %uu;\r\n", allocsz); |
fprintf(fd, "const unsigned short svarlang_string_count = %uu;\r\n\r\n", l->num_strings); |
fprintf(fd, "char svarlang_mem[%u] = {\r\n", allocsz); |
for (i = 0; i < strings_bytes; i++) { |
if (!fprintf(fd, "0x%02x", l->strings[i])) { |
fclose(fd); |
return(0); |
} |
if (i + 1 < strings_bytes) fprintf(fd, ","); |
nextnlat++; |
if (l->strings[i] == '\0' || nextnlat == 16) { |
fprintf(fd, "\r\n"); |
nextnlat = 0; |
} |
} |
fprintf(fd, "};\r\n\r\n"); |
fprintf(fd, "unsigned short svarlang_dict[%u] = {\r\n", l->num_strings * 2); |
for (i = 0; i < l->num_strings; i++) { |
if (!fprintf(fd, "0x%04x,0x%04x", l->dict[i].id, l->dict[i].offset)) { |
fclose(fd); |
return(0); |
} |
if (i + 1 < l->num_strings) fprintf(fd, ","); |
fprintf(fd, "\r\n"); |
} |
fprintf(fd, "};\r\n"); |
fclose(fd); |
return(1); |
} |
int main(int argc, char **argv) { |
FILE *fd; |
int ecode = 0; |
int i; |
unsigned short biggest_langsz = 0; |
struct svl_lang *lang, *reflang = NULL; |
if (argc < 2) { |
puts("tlumacz ver " SVARLANGVER " - this tool is part of the SvarLANG project."); |
puts("converts a set of CATS-style translations in files EN.TXT, PL.TXT, etc"); |
puts("into a single resource file (OUT.LNG)."); |
puts(""); |
puts("usage: tlumacz en fr pl ..."); |
return(1); |
} |
fd = fopen("out.lng", "wb"); |
if (fd == NULL) { |
puts("ERR: failed to open or create OUT.LNG"); |
return(1); |
} |
/* write lang blocks */ |
for (i = 1; i < argc; i++) { |
unsigned short sz; |
char id[3]; |
if (strlen(argv[i]) != 2) { |
printf("INVALID LANG SPECIFIED: %s\r\n", argv[i]); |
ecode = 1; |
break; |
} |
id[0] = argv[i][0]; |
id[1] = argv[i][1]; |
id[2] = 0; |
if ((lang = svl_lang_new(id, DICT_CAP, STRINGS_CAP)) == NULL) { |
printf("OUT OF MEMORY\r\n"); |
return(1); |
} |
sz = svl_lang_from_cats_file(lang, reflang); |
if (sz == 0) { |
printf("ERROR COMPUTING LANG '%s'\r\n", id); |
ecode = 1; |
break; |
} else { |
printf("computed %s lang block of %u bytes\r\n", id, sz); |
if (sz > biggest_langsz) biggest_langsz = sz; |
} |
svl_compact_lang(lang); |
/* write header if first (reference) language */ |
if (i == 1) { |
if (!svl_write_header(lang->num_strings, fd)) { |
printf("ERROR WRITING TO OUTPUT FILE\r\n"); |
ecode = 1; |
break; |
} |
} |
/* write lang ID to file, followed string table size, and then |
the dictionary and string table for current language */ |
if (!svl_write_lang(lang, fd)) { |
printf("ERROR WRITING TO OUTPUT FILE\r\n"); |
ecode = 1; |
break; |
} |
/* remember reference data for other languages */ |
if (i == 1) { |
reflang = lang; |
} else { |
svl_lang_free(lang); |
lang = NULL; |
} |
} |
/* compute the deflang.c file containing a dump of the reference block */ |
if (!svl_write_c_source(reflang, "deflang.c", biggest_langsz)) { |
puts("ERROR: FAILED TO OPEN OR CREATE DEFLANG.C"); |
ecode = 1; |
} |
/* clean up */ |
if (reflang) { |
svl_lang_free(reflang); |
reflang = NULL; |
} |
return(ecode); |
} |
/svarlang.lib/tags/20240227/version.c |
---|
0,0 → 1,29 |
/* This file is part of the svarlang project and is published under the terms |
* of the MIT license. |
* |
* Copyright (C) 2021-2023 Mateusz Viste |
* |
* Permission is hereby granted, free of charge, to any person obtaining a |
* copy of this software and associated documentation files (the "Software"), |
* to deal in the Software without restriction, including without limitation |
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
* and/or sell copies of the Software, and to permit persons to whom the |
* Software is furnished to do so, subject to the following conditions: |
* |
* The above copyright notice and this permission notice shall be included in |
* all copies or substantial portions of the Software. |
* |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
* DEALINGS IN THE SOFTWARE. |
*/ |
#include "svarlang.h" |
const char *svarlang_getver(void) { |
return SVARLANGVER; |
} |