1188 lines
26 KiB
C
1188 lines
26 KiB
C
#include <hawk-po.h>
|
|
#include "hawk-prv.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#define HAWK_POCTX_SEP '\004'
|
|
|
|
typedef struct hawk_pocat_strbuf_t hawk_pocat_strbuf_t;
|
|
typedef struct hawk_pocat_expr_parser_t hawk_pocat_expr_parser_t;
|
|
typedef struct hawk_poparser_t hawk_poparser_t;
|
|
|
|
enum hawk_pofld_t
|
|
{
|
|
HAWK_POFLD_NONE,
|
|
HAWK_POFLD_MSGCTXT,
|
|
HAWK_POFLD_MSGID,
|
|
HAWK_POFLD_MSGID_PLURAL,
|
|
HAWK_POFLD_MSGSTR
|
|
};
|
|
typedef enum hawk_pofld_t hawk_pofld_t;
|
|
|
|
struct hawk_pocat_strbuf_t
|
|
{
|
|
hawk_gem_t* gem;
|
|
char* data;
|
|
hawk_oow_t len;
|
|
hawk_oow_t capa;
|
|
};
|
|
|
|
struct hawk_pocat_expr_parser_t
|
|
{
|
|
const char* s;
|
|
unsigned long n;
|
|
};
|
|
|
|
struct hawk_poparser_t
|
|
{
|
|
hawk_poent_t cur;
|
|
hawk_pofld_t active_field;
|
|
int active_msgstr_index;
|
|
};
|
|
|
|
static void strbuf_init (hawk_pocat_strbuf_t* sb, hawk_gem_t* gem)
|
|
{
|
|
sb->gem = gem;
|
|
sb->data = HAWK_NULL;
|
|
sb->len = 0;
|
|
sb->capa = 0;
|
|
}
|
|
|
|
static int strbuf_reserve (hawk_pocat_strbuf_t* sb, hawk_oow_t extra)
|
|
{
|
|
hawk_oow_t req;
|
|
hawk_oow_t capa;
|
|
char* tmp;
|
|
|
|
req = sb->len + extra + 1;
|
|
if (req <= sb->capa) return 0;
|
|
|
|
capa = (sb->capa > 0)? sb->capa: 32;
|
|
while (capa < req) capa *= 2;
|
|
|
|
tmp = (char*)hawk_gem_reallocmem(sb->gem, sb->data, capa);
|
|
if (!tmp) return -1;
|
|
|
|
sb->data = tmp;
|
|
sb->capa = capa;
|
|
return 0;
|
|
}
|
|
|
|
static int strbuf_append_char (hawk_pocat_strbuf_t* sb, char c)
|
|
{
|
|
if (strbuf_reserve(sb, 1) <= -1) return -1;
|
|
sb->data[sb->len++] = c;
|
|
sb->data[sb->len] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
static char* strbuf_yield (hawk_pocat_strbuf_t* sb)
|
|
{
|
|
char* ptr;
|
|
|
|
if (!sb->data) return hawk_gem_dupbcstr(sb->gem, "", HAWK_NULL);
|
|
|
|
ptr = sb->data;
|
|
sb->data = HAWK_NULL;
|
|
sb->len = 0;
|
|
sb->capa = 0;
|
|
return ptr;
|
|
}
|
|
|
|
static void strbuf_fini (hawk_pocat_strbuf_t* sb)
|
|
{
|
|
if (sb->data) hawk_gem_freemem(sb->gem, sb->data);
|
|
sb->data = HAWK_NULL;
|
|
sb->len = 0;
|
|
sb->capa = 0;
|
|
}
|
|
|
|
static int read_line (hawk_gem_t* gem, FILE* fp, char** line)
|
|
{
|
|
hawk_pocat_strbuf_t sb;
|
|
int ch;
|
|
int got = 0;
|
|
|
|
strbuf_init(&sb, gem);
|
|
|
|
while ((ch = fgetc(fp)) != EOF)
|
|
{
|
|
got = 1;
|
|
if (strbuf_append_char(&sb, (char)ch) <= -1)
|
|
{
|
|
strbuf_fini(&sb);
|
|
return -1;
|
|
}
|
|
if (ch == '\n') break;
|
|
}
|
|
|
|
if (!got)
|
|
{
|
|
strbuf_fini(&sb);
|
|
return 0;
|
|
}
|
|
|
|
*line = strbuf_yield(&sb);
|
|
if (!*line)
|
|
{
|
|
strbuf_fini(&sb);
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static const char* skip_ws (const char* s)
|
|
{
|
|
while (*s && hawk_is_bch_space((unsigned char)*s)) s++;
|
|
return s;
|
|
}
|
|
|
|
static int is_blank_line (const char* s)
|
|
{
|
|
s = skip_ws(s);
|
|
return (*s == '\0');
|
|
}
|
|
|
|
static void trim_eol (char* s)
|
|
{
|
|
hawk_oow_t n = hawk_count_bcstr(s);
|
|
while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r')) s[--n] = '\0';
|
|
}
|
|
|
|
static int starts_with_kw (const char* s, const char* kw)
|
|
{
|
|
hawk_oow_t n = hawk_count_bcstr(kw);
|
|
if (hawk_comp_bchars_bcstr(s, n, kw, 0) != 0) return 0;
|
|
return (s[n] == '\0' || hawk_is_bch_space((unsigned char)s[n]));
|
|
}
|
|
|
|
static void hawk_poent_init (hawk_poent_t* poent)
|
|
{
|
|
HAWK_MEMSET(poent, 0, HAWK_SIZEOF(*poent));
|
|
}
|
|
|
|
static void hawk_poent_fini (hawk_poent_t* poent, hawk_gem_t* gem)
|
|
{
|
|
int i;
|
|
|
|
if (poent->msgctx) hawk_gem_freemem(gem, poent->msgctx);
|
|
if (poent->msgid) hawk_gem_freemem(gem, poent->msgid);
|
|
if (poent->msgid_plural) hawk_gem_freemem(gem, poent->msgid_plural);
|
|
|
|
for (i = 0; i < poent->msgstr_count; i++)
|
|
{
|
|
if (poent->msgstr[i]) hawk_gem_freemem(gem, poent->msgstr[i]);
|
|
}
|
|
if (poent->msgstr) hawk_gem_freemem(gem, poent->msgstr);
|
|
|
|
HAWK_MEMSET(poent, 0, HAWK_SIZEOF(*poent));
|
|
}
|
|
|
|
static int hawk_poent_ensure_msgstr (hawk_poent_t* poent, hawk_gem_t* gem, int index)
|
|
{
|
|
int old_count;
|
|
char** tmp;
|
|
|
|
if (index < 0) return -1;
|
|
if (index < poent->msgstr_count) return 0;
|
|
|
|
old_count = poent->msgstr_count;
|
|
tmp = (char**)hawk_gem_reallocmem(gem, poent->msgstr, HAWK_SIZEOF(*tmp) * (index + 1));
|
|
if (!tmp) return -1;
|
|
|
|
poent->msgstr = tmp;
|
|
poent->msgstr_count = index + 1;
|
|
while (old_count < poent->msgstr_count) poent->msgstr[old_count++] = HAWK_NULL;
|
|
return 0;
|
|
}
|
|
|
|
static char** hawk_poent_target_msgstr (hawk_poent_t* poent, hawk_gem_t* gem, int index)
|
|
{
|
|
if (hawk_poent_ensure_msgstr(poent, gem, index) <= -1) return HAWK_NULL;
|
|
return &poent->msgstr[index];
|
|
}
|
|
|
|
static const char* find_header_line_value (const char* header, const char* key)
|
|
{
|
|
hawk_oow_t key_len = hawk_count_bcstr(key);
|
|
const char* p = header;
|
|
|
|
while (*p)
|
|
{
|
|
const char* line = p;
|
|
const char* nl = hawk_find_bchar_in_bcstr(p, '\n');
|
|
hawk_oow_t len = nl? (hawk_oow_t)(nl - line): hawk_count_bcstr(line);
|
|
|
|
if (len >= key_len && hawk_comp_bchars_bcstr(line, key_len, key, 0) == 0) return line + key_len;
|
|
|
|
p = nl? (nl + 1): (line + len);
|
|
if (!nl) break;
|
|
}
|
|
|
|
return HAWK_NULL;
|
|
}
|
|
|
|
static char* dup_header_value_line (hawk_gem_t* gem, const char* header, const char* key)
|
|
{
|
|
const char* v;
|
|
const char* end;
|
|
|
|
v = find_header_line_value(header, key);
|
|
if (!v) return HAWK_NULL;
|
|
|
|
while (*v == ' ' || *v == '\t') v++;
|
|
end = hawk_find_bchar_in_bcstr(v, '\n');
|
|
if (!end) end = v + hawk_count_bcstr(v);
|
|
|
|
return hawk_gem_dupbchars(gem, v, end - v);
|
|
}
|
|
|
|
static int parse_nplurals (const char* s)
|
|
{
|
|
const char* p;
|
|
int n = 2;
|
|
|
|
p = hawk_find_bchars_in_bcstr(s, "nplurals=", 9, 0);
|
|
if (!p) return 2;
|
|
|
|
p += 9;
|
|
while (*p && hawk_is_bch_space((unsigned char)*p)) p++;
|
|
|
|
if (hawk_is_bch_digit((unsigned char)*p))
|
|
{
|
|
n = 0;
|
|
while (hawk_is_bch_digit((unsigned char)*p))
|
|
{
|
|
n = n * 10 + (*p - '0');
|
|
p++;
|
|
}
|
|
}
|
|
|
|
return (n > 0)? n: 2;
|
|
}
|
|
|
|
static char* parse_plural_expr (hawk_gem_t* gem, const char* s)
|
|
{
|
|
const char* p;
|
|
const char* end;
|
|
|
|
p = hawk_find_bchars_in_bcstr(s, "plural=", 7, 0);
|
|
if (!p) return hawk_gem_dupbcstr(gem, "(n != 1)", HAWK_NULL);
|
|
|
|
p += 7;
|
|
while (*p && hawk_is_bch_space((unsigned char)*p)) p++;
|
|
end = hawk_find_bchar_in_bcstr(p, ';');
|
|
if (!end) end = p + hawk_count_bcstr(p);
|
|
|
|
return hawk_gem_dupbchars(gem, p, end - p);
|
|
}
|
|
|
|
static int parse_header (hawk_pocat_t* pocat, const hawk_poent_t* poent)
|
|
{
|
|
char* content_type;
|
|
char* charset = HAWK_NULL;
|
|
char* plural_forms_raw = HAWK_NULL;
|
|
char* plural_expr = HAWK_NULL;
|
|
|
|
if (!poent->msgid || poent->msgid[0] != '\0') return 0;
|
|
if (poent->msgstr_count < 1 || !poent->msgstr || !poent->msgstr[0]) return 0;
|
|
|
|
content_type = dup_header_value_line(pocat->gem, poent->msgstr[0], "Content-Type:");
|
|
if (content_type)
|
|
{
|
|
const char* p = hawk_find_bchars_in_bcstr(content_type, "charset=", 8, 0);
|
|
if (p)
|
|
{
|
|
p += 8;
|
|
charset = hawk_gem_dupbcstr(pocat->gem, p, HAWK_NULL);
|
|
if (!charset)
|
|
{
|
|
hawk_gem_freemem(pocat->gem, content_type);
|
|
return -1;
|
|
}
|
|
}
|
|
hawk_gem_freemem(pocat->gem, content_type);
|
|
}
|
|
|
|
plural_forms_raw = dup_header_value_line(pocat->gem, poent->msgstr[0], "Plural-Forms:");
|
|
if (plural_forms_raw)
|
|
{
|
|
plural_expr = parse_plural_expr(pocat->gem, plural_forms_raw);
|
|
if (!plural_expr)
|
|
{
|
|
if (charset) hawk_gem_freemem(pocat->gem, charset);
|
|
hawk_gem_freemem(pocat->gem, plural_forms_raw);
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
plural_expr = hawk_gem_dupbcstr(pocat->gem, "(n != 1)", HAWK_NULL);
|
|
if (!plural_expr)
|
|
{
|
|
if (charset) hawk_gem_freemem(pocat->gem, charset);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (pocat->charset) hawk_gem_freemem(pocat->gem, pocat->charset);
|
|
if (pocat->plural_forms_raw) hawk_gem_freemem(pocat->gem, pocat->plural_forms_raw);
|
|
if (pocat->plural_expr) hawk_gem_freemem(pocat->gem, pocat->plural_expr);
|
|
|
|
pocat->charset = charset;
|
|
pocat->plural_forms_raw = plural_forms_raw;
|
|
pocat->plural_expr = plural_expr;
|
|
pocat->nplurals = plural_forms_raw? parse_nplurals(plural_forms_raw): 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_entry (hawk_pocat_t* pocat, const hawk_poent_t* src)
|
|
{
|
|
hawk_poent_t* dst;
|
|
hawk_poent_t tmp;
|
|
hawk_poent_t* entries;
|
|
int i;
|
|
|
|
if (pocat->count >= pocat->capa)
|
|
{
|
|
hawk_oow_t new_capa = (pocat->capa > 0)? (pocat->capa * 2): 32;
|
|
entries = (hawk_poent_t*)hawk_gem_reallocmem(pocat->gem, pocat->entries, HAWK_SIZEOF(*entries) * new_capa);
|
|
if (!entries) return -1;
|
|
pocat->entries = entries;
|
|
pocat->capa = new_capa;
|
|
}
|
|
|
|
hawk_poent_init(&tmp);
|
|
|
|
if (src->msgctx)
|
|
{
|
|
tmp.msgctx = hawk_gem_dupbcstr(pocat->gem, src->msgctx, HAWK_NULL);
|
|
if (!tmp.msgctx) goto oops;
|
|
}
|
|
|
|
if (src->msgid)
|
|
{
|
|
tmp.msgid = hawk_gem_dupbcstr(pocat->gem, src->msgid, HAWK_NULL);
|
|
if (!tmp.msgid) goto oops;
|
|
}
|
|
|
|
if (src->msgid_plural)
|
|
{
|
|
tmp.msgid_plural = hawk_gem_dupbcstr(pocat->gem, src->msgid_plural, HAWK_NULL);
|
|
if (!tmp.msgid_plural) goto oops;
|
|
}
|
|
|
|
tmp.fuzzy = src->fuzzy;
|
|
tmp.obsolete = src->obsolete;
|
|
tmp.msgstr_count = src->msgstr_count;
|
|
|
|
if (src->msgstr_count > 0)
|
|
{
|
|
tmp.msgstr = (char**)hawk_gem_callocmem(pocat->gem, HAWK_SIZEOF(*tmp.msgstr) * src->msgstr_count);
|
|
if (!tmp.msgstr) goto oops;
|
|
|
|
for (i = 0; i < src->msgstr_count; i++)
|
|
{
|
|
if (src->msgstr[i])
|
|
{
|
|
tmp.msgstr[i] = hawk_gem_dupbcstr(pocat->gem, src->msgstr[i], HAWK_NULL);
|
|
if (!tmp.msgstr[i]) goto oops;
|
|
}
|
|
}
|
|
}
|
|
|
|
dst = &pocat->entries[pocat->count++];
|
|
*dst = tmp;
|
|
return 0;
|
|
|
|
oops:
|
|
hawk_poent_fini(&tmp, pocat->gem);
|
|
return -1;
|
|
}
|
|
|
|
static int parse_quoted (hawk_gem_t* gem, const char* s, char** out)
|
|
{
|
|
hawk_pocat_strbuf_t sb;
|
|
|
|
*out = HAWK_NULL;
|
|
s = skip_ws(s);
|
|
if (*s != '"') return 0;
|
|
s++;
|
|
|
|
strbuf_init(&sb, gem);
|
|
|
|
while (*s && *s != '"')
|
|
{
|
|
unsigned char c = (unsigned char)*s++;
|
|
if (c == '\\')
|
|
{
|
|
unsigned char e = (unsigned char)*s++;
|
|
switch (e)
|
|
{
|
|
case 'a': if (strbuf_append_char(&sb, '\a') <= -1) goto oops; break;
|
|
case 'b': if (strbuf_append_char(&sb, '\b') <= -1) goto oops; break;
|
|
case 'f': if (strbuf_append_char(&sb, '\f') <= -1) goto oops; break;
|
|
case 'n': if (strbuf_append_char(&sb, '\n') <= -1) goto oops; break;
|
|
case 'r': if (strbuf_append_char(&sb, '\r') <= -1) goto oops; break;
|
|
case 't': if (strbuf_append_char(&sb, '\t') <= -1) goto oops; break;
|
|
case 'v': if (strbuf_append_char(&sb, '\v') <= -1) goto oops; break;
|
|
case '\\': if (strbuf_append_char(&sb, '\\') <= -1) goto oops; break;
|
|
case '"': if (strbuf_append_char(&sb, '"') <= -1) goto oops; break;
|
|
case '\'': if (strbuf_append_char(&sb, '\'') <= -1) goto oops; break;
|
|
case '?': if (strbuf_append_char(&sb, '?') <= -1) goto oops; break;
|
|
|
|
case 'x':
|
|
{
|
|
int val = 0;
|
|
int digits = 0;
|
|
while (hawk_is_bch_xdigit((unsigned char)*s))
|
|
{
|
|
int d;
|
|
unsigned char h = (unsigned char)*s++;
|
|
if (hawk_is_bch_digit(h)) d = h - '0';
|
|
else if (h >= 'a' && h <= 'f') d = 10 + (h - 'a');
|
|
else d = 10 + (h - 'A');
|
|
val = (val << 4) | d;
|
|
digits++;
|
|
}
|
|
if (digits <= 0) goto syntax_error;
|
|
if (strbuf_append_char(&sb, (char)val) <= -1) goto oops;
|
|
break;
|
|
}
|
|
|
|
case '0': case '1': case '2': case '3':
|
|
case '4': case '5': case '6': case '7':
|
|
{
|
|
int val = e - '0';
|
|
int count = 1;
|
|
while (count < 3 && *s >= '0' && *s <= '7')
|
|
{
|
|
val = (val * 8) + (*s - '0');
|
|
s++;
|
|
count++;
|
|
}
|
|
if (strbuf_append_char(&sb, (char)val) <= -1) goto oops;
|
|
break;
|
|
}
|
|
|
|
case '\0':
|
|
goto syntax_error;
|
|
|
|
default:
|
|
if (strbuf_append_char(&sb, (char)e) <= -1) goto oops;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (strbuf_append_char(&sb, (char)c) <= -1) goto oops;
|
|
}
|
|
}
|
|
|
|
if (*s != '"') goto syntax_error;
|
|
s++;
|
|
s = skip_ws(s);
|
|
if (*s != '\0') goto syntax_error;
|
|
|
|
*out = strbuf_yield(&sb);
|
|
if (!*out) goto oops;
|
|
return 1;
|
|
|
|
syntax_error:
|
|
strbuf_fini(&sb);
|
|
return 0;
|
|
|
|
oops:
|
|
strbuf_fini(&sb);
|
|
return -1;
|
|
}
|
|
|
|
static void expr_skip_ws (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
while (*p->s && hawk_is_bch_space((unsigned char)*p->s)) p->s++;
|
|
}
|
|
|
|
static long expr_parse_ternary (hawk_pocat_expr_parser_t* p);
|
|
|
|
static long expr_parse_primary (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long v = 0;
|
|
|
|
expr_skip_ws(p);
|
|
|
|
if (*p->s == '(')
|
|
{
|
|
p->s++;
|
|
v = expr_parse_ternary(p);
|
|
expr_skip_ws(p);
|
|
if (*p->s == ')') p->s++;
|
|
return v;
|
|
}
|
|
|
|
if (*p->s == 'n')
|
|
{
|
|
p->s++;
|
|
return (long)p->n;
|
|
}
|
|
|
|
if (*p->s == '-' || hawk_is_bch_digit((unsigned char)*p->s))
|
|
{
|
|
const char* end;
|
|
|
|
v = hawk_bchars_to_int(p->s, hawk_count_bcstr(p->s), HAWK_OOCHARS_TO_INT_MAKE_OPTION(0, 0, 10), &end, HAWK_NULL);
|
|
if (end != p->s)
|
|
{
|
|
p->s = end;
|
|
return v;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long expr_parse_unary (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
expr_skip_ws(p);
|
|
if (*p->s == '!')
|
|
{
|
|
p->s++;
|
|
return !expr_parse_unary(p);
|
|
}
|
|
if (*p->s == '+')
|
|
{
|
|
p->s++;
|
|
return +expr_parse_unary(p);
|
|
}
|
|
if (*p->s == '-')
|
|
{
|
|
p->s++;
|
|
return -expr_parse_unary(p);
|
|
}
|
|
return expr_parse_primary(p);
|
|
}
|
|
|
|
static long expr_parse_mul (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long v = expr_parse_unary(p);
|
|
|
|
for (;;)
|
|
{
|
|
long rhs;
|
|
|
|
expr_skip_ws(p);
|
|
if (*p->s == '*')
|
|
{
|
|
p->s++;
|
|
rhs = expr_parse_unary(p);
|
|
v = v * rhs;
|
|
}
|
|
else if (*p->s == '/')
|
|
{
|
|
p->s++;
|
|
rhs = expr_parse_unary(p);
|
|
v = (rhs == 0)? 0: (v / rhs);
|
|
}
|
|
else if (*p->s == '%')
|
|
{
|
|
p->s++;
|
|
rhs = expr_parse_unary(p);
|
|
v = (rhs == 0)? 0: (v % rhs);
|
|
}
|
|
else break;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
static long expr_parse_add (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long v = expr_parse_mul(p);
|
|
|
|
for (;;)
|
|
{
|
|
long rhs;
|
|
|
|
expr_skip_ws(p);
|
|
if (*p->s == '+')
|
|
{
|
|
p->s++;
|
|
rhs = expr_parse_mul(p);
|
|
v = v + rhs;
|
|
}
|
|
else if (*p->s == '-')
|
|
{
|
|
p->s++;
|
|
rhs = expr_parse_mul(p);
|
|
v = v - rhs;
|
|
}
|
|
else break;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
static long expr_parse_rel (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long v = expr_parse_add(p);
|
|
|
|
for (;;)
|
|
{
|
|
long rhs;
|
|
|
|
expr_skip_ws(p);
|
|
if (hawk_comp_bcstr_limited(p->s, "<=", 2, 0) == 0)
|
|
{
|
|
p->s += 2;
|
|
rhs = expr_parse_add(p);
|
|
v = (v <= rhs);
|
|
}
|
|
else if (hawk_comp_bcstr_limited(p->s, ">=", 2, 0) == 0)
|
|
{
|
|
p->s += 2;
|
|
rhs = expr_parse_add(p);
|
|
v = (v >= rhs);
|
|
}
|
|
else if (*p->s == '<')
|
|
{
|
|
p->s++;
|
|
rhs = expr_parse_add(p);
|
|
v = (v < rhs);
|
|
}
|
|
else if (*p->s == '>')
|
|
{
|
|
p->s++;
|
|
rhs = expr_parse_add(p);
|
|
v = (v > rhs);
|
|
}
|
|
else break;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
static long expr_parse_eq (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long v = expr_parse_rel(p);
|
|
|
|
for (;;)
|
|
{
|
|
long rhs;
|
|
|
|
expr_skip_ws(p);
|
|
if (hawk_comp_bcstr_limited(p->s, "==", 2, 0) == 0)
|
|
{
|
|
p->s += 2;
|
|
rhs = expr_parse_rel(p);
|
|
v = (v == rhs);
|
|
}
|
|
else if (hawk_comp_bcstr_limited(p->s, "!=", 2, 0) == 0)
|
|
{
|
|
p->s += 2;
|
|
rhs = expr_parse_rel(p);
|
|
v = (v != rhs);
|
|
}
|
|
else break;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
static long expr_parse_and (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long v = expr_parse_eq(p);
|
|
|
|
for (;;)
|
|
{
|
|
long rhs;
|
|
|
|
expr_skip_ws(p);
|
|
if (hawk_comp_bcstr_limited(p->s, "&&", 2, 0) != 0) break;
|
|
p->s += 2;
|
|
rhs = expr_parse_eq(p);
|
|
v = (v && rhs);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
static long expr_parse_or (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long v = expr_parse_and(p);
|
|
|
|
for (;;)
|
|
{
|
|
long rhs;
|
|
|
|
expr_skip_ws(p);
|
|
if (hawk_comp_bcstr_limited(p->s, "||", 2, 0) != 0) break;
|
|
p->s += 2;
|
|
rhs = expr_parse_and(p);
|
|
v = (v || rhs);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
static long expr_parse_ternary (hawk_pocat_expr_parser_t* p)
|
|
{
|
|
long cond = expr_parse_or(p);
|
|
|
|
expr_skip_ws(p);
|
|
if (*p->s == '?')
|
|
{
|
|
long a;
|
|
long b;
|
|
|
|
p->s++;
|
|
a = expr_parse_ternary(p);
|
|
expr_skip_ws(p);
|
|
if (*p->s == ':') p->s++;
|
|
b = expr_parse_ternary(p);
|
|
return cond? a: b;
|
|
}
|
|
|
|
return cond;
|
|
}
|
|
|
|
static int eval_plural_index (const hawk_pocat_t* pocat, unsigned long n)
|
|
{
|
|
hawk_pocat_expr_parser_t p;
|
|
long index;
|
|
|
|
if (!pocat->plural_expr || !pocat->plural_expr[0]) return (n != 1)? 1: 0;
|
|
|
|
p.s = pocat->plural_expr;
|
|
p.n = n;
|
|
index = expr_parse_ternary(&p);
|
|
|
|
if (index < 0) index = 0;
|
|
if (pocat->nplurals > 0 && index >= pocat->nplurals) index = pocat->nplurals - 1;
|
|
|
|
return (int)index;
|
|
}
|
|
|
|
static void hawk_poparser_init (hawk_poparser_t* parser)
|
|
{
|
|
hawk_poent_init(&parser->cur);
|
|
parser->active_field = HAWK_POFLD_NONE;
|
|
parser->active_msgstr_index = 0;
|
|
}
|
|
|
|
static void hawk_poparser_reset_current (hawk_poparser_t* parser, hawk_gem_t* gem)
|
|
{
|
|
hawk_poent_fini(&parser->cur, gem);
|
|
hawk_poent_init(&parser->cur);
|
|
parser->active_field = HAWK_POFLD_NONE;
|
|
parser->active_msgstr_index = 0;
|
|
}
|
|
|
|
static int hawk_poparser_has_current_content (const hawk_poparser_t* parser)
|
|
{
|
|
return !!(parser->cur.msgctx || parser->cur.msgid || parser->cur.msgid_plural ||
|
|
parser->cur.msgstr_count > 0 || parser->cur.fuzzy || parser->cur.obsolete);
|
|
}
|
|
|
|
static int hawk_poparser_append_to_string (hawk_gem_t* gem, char** dst, const char* frag)
|
|
{
|
|
hawk_oow_t old_len = (*dst)? hawk_count_bcstr(*dst): 0;
|
|
hawk_oow_t add_len = hawk_count_bcstr(frag);
|
|
char* tmp;
|
|
|
|
tmp = (char*)hawk_gem_reallocmem(gem, *dst, old_len + add_len + 1);
|
|
if (!tmp) return -1;
|
|
|
|
*dst = tmp;
|
|
HAWK_MEMCPY(&(*dst)[old_len], frag, add_len + 1);
|
|
return 0;
|
|
}
|
|
|
|
static int hawk_poparser_append_fragment (hawk_poparser_t* parser, hawk_gem_t* gem, const char* frag)
|
|
{
|
|
switch (parser->active_field)
|
|
{
|
|
case HAWK_POFLD_MSGCTXT:
|
|
if (!parser->cur.msgctx)
|
|
{
|
|
parser->cur.msgctx = hawk_gem_dupbcstr(gem, "", HAWK_NULL);
|
|
if (!parser->cur.msgctx) return -1;
|
|
}
|
|
return hawk_poparser_append_to_string(gem, &parser->cur.msgctx, frag);
|
|
|
|
case HAWK_POFLD_MSGID:
|
|
if (!parser->cur.msgid)
|
|
{
|
|
parser->cur.msgid = hawk_gem_dupbcstr(gem, "", HAWK_NULL);
|
|
if (!parser->cur.msgid) return -1;
|
|
}
|
|
return hawk_poparser_append_to_string(gem, &parser->cur.msgid, frag);
|
|
|
|
case HAWK_POFLD_MSGID_PLURAL:
|
|
if (!parser->cur.msgid_plural)
|
|
{
|
|
parser->cur.msgid_plural = hawk_gem_dupbcstr(gem, "", HAWK_NULL);
|
|
if (!parser->cur.msgid_plural) return -1;
|
|
}
|
|
return hawk_poparser_append_to_string(gem, &parser->cur.msgid_plural, frag);
|
|
|
|
case HAWK_POFLD_MSGSTR:
|
|
{
|
|
char** slot = hawk_poent_target_msgstr(&parser->cur, gem, parser->active_msgstr_index);
|
|
if (!slot) return -1;
|
|
|
|
if (!*slot)
|
|
{
|
|
*slot = hawk_gem_dupbcstr(gem, "", HAWK_NULL);
|
|
if (!*slot) return -1;
|
|
}
|
|
return hawk_poparser_append_to_string(gem, slot, frag);
|
|
}
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int hawk_poparser_parse_msgstr_index_line (const char* s, int* index, const char** after)
|
|
{
|
|
int n = 0;
|
|
|
|
if (hawk_comp_bcstr_limited(s, "msgstr[", 7, 0) != 0) return 0;
|
|
|
|
s += 7;
|
|
if (!hawk_is_bch_digit((unsigned char)*s)) return 0;
|
|
|
|
while (hawk_is_bch_digit((unsigned char)*s))
|
|
{
|
|
n = n * 10 + (*s - '0');
|
|
s++;
|
|
}
|
|
|
|
if (*s != ']') return 0;
|
|
s++;
|
|
|
|
*index = n;
|
|
*after = s;
|
|
return 1;
|
|
}
|
|
|
|
static int hawk_poparser_finalize_entry (hawk_pocat_t* pocat, hawk_poparser_t* parser)
|
|
{
|
|
if (!hawk_poparser_has_current_content(parser)) return 0;
|
|
|
|
if (!parser->cur.msgid)
|
|
{
|
|
parser->cur.msgid = hawk_gem_dupbcstr(pocat->gem, "", HAWK_NULL);
|
|
if (!parser->cur.msgid) return -1;
|
|
}
|
|
|
|
if (add_entry(pocat, &parser->cur) <= -1) return -1;
|
|
if (parser->cur.msgid[0] == '\0' && parse_header(pocat, &parser->cur) <= -1) return -1;
|
|
|
|
hawk_poparser_reset_current(parser, pocat->gem);
|
|
return 0;
|
|
}
|
|
|
|
hawk_pocat_t* hawk_pocat_open (hawk_gem_t* gem, hawk_oow_t xtnsize)
|
|
{
|
|
hawk_pocat_t* pocat;
|
|
|
|
pocat = (hawk_pocat_t*)hawk_gem_allocmem(gem, HAWK_SIZEOF(*pocat) + xtnsize);
|
|
if (!pocat) return HAWK_NULL;
|
|
|
|
if (hawk_pocat_init(pocat, gem) <= -1)
|
|
{
|
|
hawk_gem_freemem(gem, pocat);
|
|
return HAWK_NULL;
|
|
}
|
|
|
|
HAWK_MEMSET(pocat + 1, 0, xtnsize);
|
|
return pocat;
|
|
}
|
|
|
|
void hawk_pocat_close (hawk_pocat_t* pocat)
|
|
{
|
|
hawk_gem_t* gem;
|
|
|
|
gem = pocat->gem;
|
|
hawk_pocat_fini(pocat);
|
|
hawk_gem_freemem(gem, pocat);
|
|
}
|
|
|
|
int hawk_pocat_init (hawk_pocat_t* pocat, hawk_gem_t* gem)
|
|
{
|
|
if (!pocat || !gem) return -1;
|
|
HAWK_MEMSET(pocat, 0, HAWK_SIZEOF(*pocat));
|
|
pocat->gem = gem;
|
|
pocat->nplurals = 2;
|
|
return 0;
|
|
}
|
|
|
|
void hawk_pocat_fini (hawk_pocat_t* pocat)
|
|
{
|
|
hawk_oow_t i;
|
|
|
|
if (!pocat) return;
|
|
|
|
for (i = 0; i < pocat->count; i++) hawk_poent_fini(&pocat->entries[i], pocat->gem);
|
|
if (pocat->entries) hawk_gem_freemem(pocat->gem, pocat->entries);
|
|
if (pocat->charset) hawk_gem_freemem(pocat->gem, pocat->charset);
|
|
if (pocat->plural_forms_raw) hawk_gem_freemem(pocat->gem, pocat->plural_forms_raw);
|
|
if (pocat->plural_expr) hawk_gem_freemem(pocat->gem, pocat->plural_expr);
|
|
|
|
HAWK_MEMSET(pocat, 0, HAWK_SIZEOF(*pocat));
|
|
}
|
|
|
|
int hawk_pocat_load_file (hawk_pocat_t* pocat, const char* path, char* errbuf, hawk_oow_t errbuf_sz)
|
|
{
|
|
FILE* fp;
|
|
hawk_poparser_t parser;
|
|
char* line = HAWK_NULL;
|
|
unsigned long line_no = 0;
|
|
int status = 0;
|
|
|
|
if (!pocat || !path)
|
|
{
|
|
if (errbuf && errbuf_sz > 0) snprintf(errbuf, errbuf_sz, "invalid arguments");
|
|
return -1;
|
|
}
|
|
|
|
fp = fopen(path, "rb");
|
|
if (!fp)
|
|
{
|
|
if (errbuf && errbuf_sz > 0) snprintf(errbuf, errbuf_sz, "cannot open %s", path);
|
|
return -1;
|
|
}
|
|
|
|
hawk_poparser_init(&parser);
|
|
|
|
for (;;)
|
|
{
|
|
const char* s;
|
|
char* frag;
|
|
int x;
|
|
|
|
x = read_line(pocat->gem, fp, &line);
|
|
if (x == 0) break;
|
|
if (x <= -1)
|
|
{
|
|
if (errbuf && errbuf_sz > 0) snprintf(errbuf, errbuf_sz, "%s: out of memory", path);
|
|
status = -1;
|
|
goto done;
|
|
}
|
|
|
|
line_no++;
|
|
trim_eol(line);
|
|
s = skip_ws(line);
|
|
|
|
if (is_blank_line(s))
|
|
{
|
|
if (hawk_poparser_finalize_entry(pocat, &parser) <= -1)
|
|
{
|
|
if (errbuf && errbuf_sz > 0) snprintf(errbuf, errbuf_sz, "%s: out of memory", path);
|
|
status = -1;
|
|
goto done;
|
|
}
|
|
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
|
|
if (hawk_comp_bcstr_limited(s, "#~", 2, 0) == 0)
|
|
{
|
|
parser.cur.obsolete = 1;
|
|
s += 2;
|
|
s = skip_ws(s);
|
|
if (*s == '\0')
|
|
{
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
}
|
|
else if (*s == '#')
|
|
{
|
|
if (hawk_comp_bcstr_limited(s, "#,", 2, 0) == 0 && hawk_find_bchars_in_bcstr(s + 2, "fuzzy", 5, 0)) parser.cur.fuzzy = 1;
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
|
|
if (starts_with_kw(s, "msgctx"))
|
|
{
|
|
x = parse_quoted(pocat->gem, s + 7, &frag);
|
|
if (x == 0) goto syntax_error;
|
|
if (x <= -1) goto nomem_error;
|
|
if (parser.cur.msgctx) hawk_gem_freemem(pocat->gem, parser.cur.msgctx);
|
|
parser.cur.msgctx = frag;
|
|
parser.active_field = HAWK_POFLD_MSGCTXT;
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
|
|
if (starts_with_kw(s, "msgid_plural"))
|
|
{
|
|
x = parse_quoted(pocat->gem, s + 12, &frag);
|
|
if (x == 0) goto syntax_error;
|
|
if (x <= -1) goto nomem_error;
|
|
if (parser.cur.msgid_plural) hawk_gem_freemem(pocat->gem, parser.cur.msgid_plural);
|
|
parser.cur.msgid_plural = frag;
|
|
parser.active_field = HAWK_POFLD_MSGID_PLURAL;
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
|
|
if (starts_with_kw(s, "msgid"))
|
|
{
|
|
if (parser.cur.msgid && (parser.cur.msgstr_count > 0 || parser.cur.msgctx || parser.cur.msgid_plural))
|
|
{
|
|
if (hawk_poparser_finalize_entry(pocat, &parser) <= -1) goto nomem_error;
|
|
}
|
|
|
|
x = parse_quoted(pocat->gem, s + 5, &frag);
|
|
if (x == 0) goto syntax_error;
|
|
if (x <= -1) goto nomem_error;
|
|
if (parser.cur.msgid) hawk_gem_freemem(pocat->gem, parser.cur.msgid);
|
|
parser.cur.msgid = frag;
|
|
parser.active_field = HAWK_POFLD_MSGID;
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
|
|
if (starts_with_kw(s, "msgstr"))
|
|
{
|
|
int index = 0;
|
|
const char* after = HAWK_NULL;
|
|
|
|
if (hawk_poparser_parse_msgstr_index_line(s, &index, &after)) x = parse_quoted(pocat->gem, after, &frag);
|
|
else x = parse_quoted(pocat->gem, s + 6, &frag);
|
|
|
|
if (x == 0) goto syntax_error;
|
|
if (x <= -1) goto nomem_error;
|
|
|
|
if (hawk_poent_ensure_msgstr(&parser.cur, pocat->gem, index) <= -1)
|
|
{
|
|
hawk_gem_freemem(pocat->gem, frag);
|
|
goto nomem_error;
|
|
}
|
|
|
|
if (parser.cur.msgstr[index]) hawk_gem_freemem(pocat->gem, parser.cur.msgstr[index]);
|
|
parser.cur.msgstr[index] = frag;
|
|
parser.active_field = HAWK_POFLD_MSGSTR;
|
|
parser.active_msgstr_index = index;
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
|
|
if (*s == '"')
|
|
{
|
|
x = parse_quoted(pocat->gem, s, &frag);
|
|
if (x == 0) goto syntax_error;
|
|
if (x <= -1) goto nomem_error;
|
|
|
|
if (hawk_poparser_append_fragment(&parser, pocat->gem, frag) <= -1)
|
|
{
|
|
hawk_gem_freemem(pocat->gem, frag);
|
|
goto nomem_error;
|
|
}
|
|
|
|
hawk_gem_freemem(pocat->gem, frag);
|
|
hawk_gem_freemem(pocat->gem, line);
|
|
line = HAWK_NULL;
|
|
continue;
|
|
}
|
|
|
|
syntax_error:
|
|
if (errbuf && errbuf_sz > 0) snprintf(errbuf, errbuf_sz, "%s:%lu: syntax error", path, line_no);
|
|
status = -1;
|
|
goto done;
|
|
|
|
nomem_error:
|
|
if (errbuf && errbuf_sz > 0) snprintf(errbuf, errbuf_sz, "%s: out of memory", path);
|
|
status = -1;
|
|
goto done;
|
|
}
|
|
|
|
if (hawk_poparser_finalize_entry(pocat, &parser) <= -1)
|
|
{
|
|
if (errbuf && errbuf_sz > 0) snprintf(errbuf, errbuf_sz, "%s: out of memory", path);
|
|
status = -1;
|
|
goto done;
|
|
}
|
|
|
|
status = 0;
|
|
|
|
done:
|
|
if (line) hawk_gem_freemem(pocat->gem, line);
|
|
hawk_poparser_reset_current(&parser, pocat->gem);
|
|
fclose(fp);
|
|
return status;
|
|
}
|
|
|
|
static int hawk_poent_matches (const hawk_poent_t* poent, const char* msgctx, const char* msgid)
|
|
{
|
|
if (!poent->msgid || !msgid) return 0;
|
|
if (poent->obsolete) return 0;
|
|
if (hawk_comp_bcstr(poent->msgid, msgid, 0) != 0) return 0;
|
|
|
|
if (msgctx == HAWK_NULL && poent->msgctx == HAWK_NULL) return 1;
|
|
if (msgctx != HAWK_NULL && poent->msgctx != HAWK_NULL && hawk_comp_bcstr(poent->msgctx, msgctx, 0) == 0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static const hawk_poent_t* find (const hawk_pocat_t* pocat, const char* msgctx, const char* msgid)
|
|
{
|
|
hawk_oow_t i;
|
|
|
|
for (i = 0; i < pocat->count; i++)
|
|
{
|
|
if (hawk_poent_matches(&pocat->entries[i], msgctx, msgid)) return &pocat->entries[i];
|
|
}
|
|
|
|
return HAWK_NULL;
|
|
}
|
|
|
|
const char* hawk_pocat_get (const hawk_pocat_t* pocat, const char* msgctx, const char* msgid)
|
|
{
|
|
const hawk_poent_t* poent;
|
|
|
|
if (!pocat || !msgid) return HAWK_NULL;
|
|
|
|
poent = find(pocat, msgctx, msgid);
|
|
if (!poent) return HAWK_NULL;
|
|
if (poent->fuzzy) return HAWK_NULL;
|
|
if (poent->msgstr_count < 1 || !poent->msgstr || !poent->msgstr[0] || !poent->msgstr[0][0]) return HAWK_NULL;
|
|
|
|
return poent->msgstr[0];
|
|
}
|
|
|
|
const char* hawk_pocat_gettext (const hawk_pocat_t* pocat, const char* msgctx, const char* msgid)
|
|
{
|
|
const char* text;
|
|
|
|
text = hawk_pocat_get(pocat, msgctx, msgid);
|
|
return text? text: msgid;
|
|
}
|
|
|
|
const char* hawk_pocat_nget (const hawk_pocat_t* pocat, const char* msgctx, const char* msgid, const char* msgid_plural, unsigned long n)
|
|
{
|
|
const hawk_poent_t* poent;
|
|
int index;
|
|
|
|
if (!pocat || !msgid || !msgid_plural) return (n == 1)? msgid: msgid_plural;
|
|
|
|
poent = find(pocat, msgctx, msgid);
|
|
if (!poent || poent->fuzzy) return (n == 1)? msgid: msgid_plural;
|
|
|
|
index = eval_plural_index(pocat, n);
|
|
if (index >= 0 && index < poent->msgstr_count && poent->msgstr[index] && poent->msgstr[index][0]) return poent->msgstr[index];
|
|
|
|
return (n == 1)? msgid: msgid_plural;
|
|
}
|