Subversion Repositories SvarDOS

Compare Revisions

Ignore whitespace Rev 1381 → Rev 1382

/svarlang.lib/tags/20230730/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/20230730/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/20230730/history.txt
0,0 → 1,28
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/20230730/makefile
0,0 → 1,64
#
# make instructions to build svarlang and tlumacz.exe with OpenWatcom
# Copyright (C) 2021-2023 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/20230730/svarlang.c
0,0 → 1,208
/* 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.
*/
 
/* 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 */
#ifndef WITHSTDIO
static unsigned short FOPEN(const char *s) {
unsigned short fname_seg = FP_SEG(s);
unsigned short fname_off = FP_OFF(s);
unsigned short res = 0; /* fd 0 is already used by stdout so it's a good error value */
_asm {
push dx
push ds
 
mov ax, fname_seg
mov dx, fname_off
mov ds, ax
mov ax, 0x3d00 /* open file, read-only (fname at DS:DX) */
int 0x21
pop ds
jc ERR
mov res, ax
 
ERR:
pop dx
}
 
return(res);
}
 
 
static void FCLOSE(unsigned short handle) {
_asm {
mov ah, 0x3e
mov bx, handle
int 0x21
}
}
 
 
static unsigned short FREAD(unsigned short handle, void *buff, unsigned short bytes) {
unsigned short buff_seg = FP_SEG(buff);
unsigned short buff_off = FP_OFF(buff);
unsigned short res = 0;
 
_asm {
push bx
push cx
push dx
 
mov bx, handle
mov cx, bytes
mov dx, buff_off
mov ax, buff_seg
push ds
mov ds, ax
mov ah, 0x3f /* read cx bytes from file handle bx to DS:DX */
int 0x21
pop ds
jc ERR
 
mov res, ax
ERR:
 
pop dx
pop cx
pop bx
}
 
return(res);
}
 
 
static void FSEEK(unsigned short handle, unsigned short bytes) {
_asm {
mov ax, 0x4201 /* move file pointer from cur pos + CX:DX */
mov bx, handle
xor cx, cx
mov dx, bytes
int 0x21
}
}
#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/20230730/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-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.
*/
 
#ifndef SVARLANG_H
#define SVARLANG_H
 
/* library version */
#define SVARLANGVER "20230730"
 
/* 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/20230730/svarlang.txt
0,0 → 1,123
 
 
SVARLANG.LIB - THE SVARDOS TRANSLATION C LIBRARY
 
Copyright (C) 2021-2023 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/20230730/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/20230730/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;
}