ainedit: initial .pje support

Support building from project files used by the official SDK.
This commit is contained in:
Nunuhara Cabbage
2020-07-01 09:41:17 -07:00
parent 1e87c85588
commit 5bc3a05167
13 changed files with 452 additions and 21 deletions
+8
View File
@@ -28,6 +28,7 @@ enum ini_value_type {
INI_STRING,
INI_LIST,
_INI_LIST_ENTRY,
INI_FORMATION,
INI_NR_TYPES
};
@@ -37,14 +38,21 @@ struct ini_value {
union {
int i;
struct string *s;
// list
struct {
size_t list_size;
struct ini_value *list;
};
// list item
struct {
size_t _list_pos;
struct ini_value *_list_value;
};
// formation
struct {
size_t nr_entries;
struct ini_entry *entries;
};
};
};
+1
View File
@@ -62,5 +62,6 @@ void string_set_char(struct string **s, int i, unsigned int c);
// C strings
int int_to_cstr(char *buf, size_t size, int v, int figures, bool zero_pad, bool zenkaku);
int float_to_cstr(char *buf, size_t size, float v, int figures, bool zero_pad, int precision, bool zenkaku);
struct string *cstr_to_string(const char *str);
#endif
+6
View File
@@ -121,6 +121,12 @@ static void ini_free_value(struct ini_value *value)
ini_free_value(value->_list_value);
free(value->_list_value);
break;
case INI_FORMATION:
for (size_t i = 0; i < value->nr_entries; i++) {
ini_free_entry(&value->entries[i]);
}
free(value->entries);
break;
default: break;
}
}
+3 -9
View File
@@ -38,6 +38,7 @@ unsigned long yini_line = 1;
%%
\/\/[^\n]*\n yini_line++;
[ \t\r] ;
\n yini_line++;
;[^\n]*\n yini_line++;
@@ -49,6 +50,7 @@ unsigned long yini_line = 1;
, return ',';
true return TRUE;
false return FALSE;
Formation return FORMATION;
[0-9]+ yini_lval.i = atoi(yini_text); return INTEGER;
[a-zA-Z]+[a-zA-Z0-9]* yini_lval.s = make_string(yini_text, yini_leng); return IDENTIFIER;
@@ -64,15 +66,7 @@ false return FALSE;
\n ERROR("Unterminated string literal at line %lu", yini_line);
\\n *string_buf_ptr++ = '\n';
\\t *string_buf_ptr++ = '\t';
\\r *string_buf_ptr++ = '\r';
\\b *string_buf_ptr++ = '\b';
\\f *string_buf_ptr++ = '\f';
\\(.|\n) *string_buf_ptr++ = yini_text[1];
[^\\\n\"]+ {
[^\n\"]+ {
char *yptr = yini_text;
while (*yptr)
*string_buf_ptr++ = *yptr++;
+21 -1
View File
@@ -43,6 +43,8 @@ static void push_entry(entry_list *ini, struct ini_entry *entry)
static struct ini_value make_list(value_list *values)
{
if (!values)
return ini_make_list(NULL, 0);
struct ini_value *vals = kv_data(*values);
size_t nr_vals = kv_size(*values);
free(values);
@@ -74,9 +76,25 @@ static struct ini_entry *make_list_assign(struct string *name, int i, struct ini
return ini_make_entry(name, wrapper);
}
static struct ini_entry *make_formation(struct string *name, entry_list *entries)
{
struct ini_value wrapper = {
.type = INI_FORMATION,
.nr_entries = kv_size(*entries),
.entries = xcalloc(kv_size(*entries), sizeof(struct ini_entry)),
};
for (size_t i = 0; i < wrapper.nr_entries; i++) {
wrapper.entries[i] = *kv_A(*entries, i);
free(kv_A(*entries, i));
}
kv_destroy(*entries);
free(entries);
return ini_make_entry(name, wrapper);
}
%}
%token <token> TRUE FALSE
%token <token> TRUE FALSE FORMATION
%token <i> INTEGER
%token <s> STRING IDENTIFIER
@@ -99,12 +117,14 @@ entries : entry { $$ = make_ini(); push_entry($$, $1); }
entry : IDENTIFIER '=' value { $$ = ini_make_entry($1, $3); }
| IDENTIFIER '[' INTEGER ']' '=' value { $$ = make_list_assign($1, $3, $6); }
| FORMATION STRING '{' entries '}' { $$ = make_formation($2, $4); }
;
value : INTEGER { $$ = ini_make_integer($1); }
| TRUE { $$ = ini_make_boolean(true); }
| FALSE { $$ = ini_make_boolean(false); }
| STRING { $$ = ini_make_string($1); }
| '{' '}' { $$ = make_list(NULL); }
| '{' list '}' { $$ = make_list($2); }
| '{' list ',' '}' { $$ = make_list($2); }
;
+3 -3
View File
@@ -12,6 +12,7 @@ system4 = ['afa.c',
'flat.c',
'fnl.c',
'hashtable.c',
'ini.c',
'instructions.c',
'pms.c',
'qnt.c',
@@ -30,7 +31,6 @@ xsystem4 = ['audio.c',
'fnl_render.c',
'format.c',
'heap.c',
'ini.c',
'input.c',
'page.c',
'resume.c',
@@ -75,8 +75,8 @@ bisongen = generator(bison,
output: ['@BASENAME@.tab.c', '@BASENAME@.tab.h'],
arguments : ['@INPUT@', '--defines=@OUTPUT1@', '--output=@OUTPUT0@'])
xsystem4 += flexgen.process('ini_lexer.l')
xsystem4 += bisongen.process('ini_parser.y')
system4 += flexgen.process('ini_lexer.l')
system4 += bisongen.process('ini_parser.y')
libsys4 = static_library('sys4', system4,
dependencies : [libm, zlib, tj, webp, png],
+5
View File
@@ -54,6 +54,11 @@ struct string *make_string(const char *str, unsigned int len)
return s;
}
struct string *cstr_to_string(const char *str)
{
return make_string(str, strlen(str));
}
struct string *string_ref(struct string *s)
{
s->cow = 1;
+18 -7
View File
@@ -32,6 +32,7 @@ static void usage(void)
puts(" Edit AIN files.");
puts("");
puts(" -h, --help Display this message and exit");
puts(" -p, --project <pje-file> Build AIN from project file");
puts(" -c, --code <jam-file> Update the CODE section (assemble .jam file)");
puts(" --jaf <jaf-file> Update AIN file from JAF source code");
puts(" -j, --json <json-file> Update AIN file from JSON data");
@@ -44,13 +45,13 @@ static void usage(void)
puts(" --ain-encoding <enc> Specify the text encoding of the input AIN file");
puts(" --ain-version <version> Specify the AIN version (when creating a new AIN file)");
puts(" --silent Don't write messages to stdout");
//puts(" -p,--project <pje-file> Build AIN from project file");
}
extern int text_parse(void);
enum {
LOPT_HELP = 256,
LOPT_PROJECT,
LOPT_CODE,
LOPT_JAF,
LOPT_JSON,
@@ -92,6 +93,7 @@ int main(int argc, char *argv[])
initialize_instructions();
struct ain *ain;
int err = AIN_SUCCESS;
const char *project_file = NULL;
const char *code_file = NULL;
unsigned nr_jaf_files = 0;
const char **jaf_files = NULL;
@@ -107,6 +109,7 @@ int main(int argc, char *argv[])
while (1) {
static struct option long_options[] = {
{ "help", no_argument, 0, LOPT_HELP },
{ "project", required_argument, 0, LOPT_PROJECT },
{ "code", required_argument, 0, LOPT_CODE },
{ "jaf", required_argument, 0, LOPT_JAF },
{ "json", required_argument, 0, LOPT_JSON },
@@ -123,7 +126,7 @@ int main(int argc, char *argv[])
int option_index = 0;
int c;
c = getopt_long(argc, argv, "hc:j:t:o:", long_options, &option_index);
c = getopt_long(argc, argv, "hp:c:j:t:o:", long_options, &option_index);
if (c == -1)
break;
@@ -132,6 +135,10 @@ int main(int argc, char *argv[])
case LOPT_HELP:
usage();
return 0;
case 'p':
case LOPT_PROJECT:
project_file = optarg;
break;
case 'c':
case LOPT_CODE:
code_file = optarg;
@@ -190,11 +197,6 @@ int main(int argc, char *argv[])
ERROR("Too many arguments.");
}
if (!output_file) {
usage();
ERROR("No output file given");
}
if ((ain_conv = iconv_open(output_encoding, input_encoding)) == (iconv_t)-1) {
ERROR("iconv_open: %s", strerror(errno));
}
@@ -205,6 +207,15 @@ int main(int argc, char *argv[])
ERROR("iconv_open: %s", strerror(errno));
}
if (project_file) {
pje_build(project_file, ain_version);
return 0;
}
if (!output_file) {
output_file = "out.ain";
}
if (!argc) {
ain = ain_new(ain_version);
} else {
+3
View File
@@ -66,6 +66,9 @@ enum asm_pseudo_opcode {
const struct instruction asm_pseudo_ops[NR_PSEUDO_OPS - PSEUDO_OP_OFFSET];
// pje.c
void pje_build(const char *path, int ain_version);
// asm.c
void asm_assemble_jam(const char *filename, struct ain *ain, uint32_t flags);
const struct instruction *asm_get_instruction(const char *name);
+1
View File
@@ -530,6 +530,7 @@ function_definition
function_declarator
: IDENTIFIER '(' parameter_list ')' { $$ = jaf_function_declarator($1, $3); }
| IDENTIFIER '(' ')' { $$ = jaf_function_declarator($1, NULL); }
| IDENTIFIER '(' VOID ')' { $$ = jaf_function_declarator($1, NULL); }
;
parameter_list
+1
View File
@@ -9,6 +9,7 @@ ainedit = ['../../cJSON.c',
'jaf_static_analysis.c',
'jaf_types.c',
'json.c',
'pje.c',
'repack.c',
'text.c',
'transcode.c',
+381
View File
@@ -0,0 +1,381 @@
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include "file.h"
#include "system4.h"
#include "system4/ain.h"
#include "system4/ini.h"
#include "system4/string.h"
#include "ainedit.h"
#include "jaf.h"
struct pje_formation {
struct string *name;
unsigned nr_defines;
struct string **defines;
};
struct string_list {
unsigned n;
struct string **items;
};
struct pje_config {
struct string *project_name;
struct string *code_name;
int game_version;
struct string *source_dir;
struct string *hll_dir;
struct string *obj_dir;
struct string *output_dir;
unsigned nr_formations;
struct pje_formation *formations;
struct string_list system_source;
struct string_list source;
struct string *mod_ain;
struct string *mod_text;
};
struct inc_config {
struct string_list system_source;
struct string_list source;
struct string_list copy_to_run;
struct string_list copy_to_dll;
struct string_list copy_to_dp;
struct string_list load_dll;
};
struct build_job {
struct string_list system_source;
struct string_list source;
struct string_list headers;
};
static void string_list_append(struct string_list *list, struct string *str)
{
list->items = xrealloc_array(list->items, list->n, list->n+1, sizeof(struct string*));
list->items[list->n++] = str;
}
static void free_string_list(struct string_list *list)
{
for (unsigned i = 0; i < list->n; i++) {
free_string(list->items[i]);
}
free(list->items);
}
static struct string *directory_name(const char *path)
{
char *tmp = strdup(path);
char *dir = dirname(tmp);
struct string *r = make_string(dir, strlen(dir));
free(tmp);
return r;
}
static struct string *directory_file(struct string *dir, struct string *file)
{
char tmp[1024];
if (dir->size + file->size + 2 >= 1024)
ERROR("path too long");
memcpy(tmp, dir->text, dir->size);
tmp[dir->size] = '/';
memcpy(tmp+dir->size+1, file->text, file->size);
tmp[dir->size+file->size+1] = '\0';
for (int i = dir->size+1; i < dir->size + file->size + 1; i++) {
if (tmp[i] == '\\')
tmp[i] = '/';
}
return make_string(tmp, dir->size+file->size+1);
}
static struct string *pje_string(struct ini_entry *entry)
{
if (entry->value.type != INI_STRING)
ERROR("Invalid value for '%s': not a string", entry->name->text);
return string_dup(entry->value.s);
}
static int pje_integer(struct ini_entry *entry)
{
if (entry->value.type != INI_INTEGER)
ERROR("Invalid value for '%s': not an integer", entry->name->text);
return entry->value.i;
}
static void pje_string_list(struct ini_entry *entry, struct string_list *out)
{
if (entry->value.type != INI_LIST)
ERROR("Invalid value for '%s': not a list", entry->name->text);
out->n = entry->value.list_size;
out->items = xcalloc(entry->value.list_size, sizeof(struct string*));
for (unsigned i = 0; i < out->n; i++) {
if (entry->value.list[i].type != INI_STRING)
ERROR("Invalid value in '%s': not a string", entry->name->text);
out->items[i] = string_dup(entry->value.list[i].s);
}
}
static void pje_parse(const char *path, struct pje_config *config)
{
size_t ini_size;
struct ini_entry *ini = ini_parse(path, &ini_size);
for (size_t i = 0; i < ini_size; i++) {
if (!strcmp(ini[i].name->text, "ProjectName")) {
config->project_name = pje_string(&ini[i]);
} else if (!strcmp(ini[i].name->text, "CodeName")) {
config->code_name = pje_string(&ini[i]);
} else if (!strcmp(ini[i].name->text, "GameVersion")) {
config->game_version = pje_integer(&ini[i]);
} else if (!strcmp(ini[i].name->text, "SourceDir")) {
config->source_dir = pje_string(&ini[i]);
} else if (!strcmp(ini[i].name->text, "HLLDir")) {
config->hll_dir = pje_string(&ini[i]);
} else if (!strcmp(ini[i].name->text, "ObjDir")) {
config->obj_dir = pje_string(&ini[i]);
} else if (!strcmp(ini[i].name->text, "OutputDir")) {
config->output_dir = pje_string(&ini[i]);
} else if (!strcmp(ini[i].name->text, "SystemSource")) {
pje_string_list(&ini[i], &config->system_source);
} else if (!strcmp(ini[i].name->text, "Source")) {
pje_string_list(&ini[i], &config->source);
} else if (!strcmp(ini[i].name->text, "HLL")) {
WARNING("HLL renaming not supported");
} else if (!strcmp(ini[i].name->text, "SyncFolder")) {
WARNING("SyncFolder not supported");
} else if (!strcmp(ini[i].name->text, "SyncLock")) {
WARNING("SyncLock not supported");
} else if (!strcmp(ini[i].name->text, "ModAin")) {
config->mod_ain = pje_string(&ini[i]);
} else if (!strcmp(ini[i].name->text, "ModText")) {
config->mod_text = pje_string(&ini[i]);
} else if (ini[i].value.type == INI_FORMATION) {
WARNING("Formations not supported");
} else {
ERROR("Unhandled pje variable: %s", ini[i].name->text);
}
ini_free_entry(&ini[i]);
}
free(ini);
// defaults
if (!config->project_name)
config->project_name = cstr_to_string("UntitledProject");
if (!config->code_name)
config->code_name = cstr_to_string("out.ain");
if (!config->source_dir)
config->source_dir = cstr_to_string("source");
if (!config->hll_dir)
config->hll_dir = cstr_to_string("hll");
if (!config->obj_dir)
config->obj_dir = cstr_to_string("obj");
if (!config->output_dir)
config->output_dir = cstr_to_string("run");
}
static void pje_parse_inc(const char *path, struct inc_config *config)
{
size_t ini_size;
struct ini_entry *ini = ini_parse(path, &ini_size);
for (size_t i = 0; i < ini_size; i++) {
if (!strcmp(ini[i].name->text, "Source")) {
pje_string_list(&ini[i], &config->source);
} else if (!strcmp(ini[i].name->text, "SystemSource")) {
pje_string_list(&ini[i], &config->system_source);
} else if (!strcmp(ini[i].name->text, "CopyToRun")) {
pje_string_list(&ini[i], &config->copy_to_run);
} else if (!strcmp(ini[i].name->text, "CopyToDLL")) {
pje_string_list(&ini[i], &config->copy_to_dll);
} else if (!strcmp(ini[i].name->text, "CopyToDP")) {
pje_string_list(&ini[i], &config->copy_to_dp);
} else if (!strcmp(ini[i].name->text, "LoadDLL")) {
pje_string_list(&ini[i], &config->load_dll);
} else {
ERROR("Unhandled variable in file '%s': %s", path, ini[i].name->text);
}
ini_free_entry(&ini[i]);
}
free(ini);
}
static void pje_free_inc(struct inc_config *config)
{
free_string_list(&config->system_source);
free_string_list(&config->source);
free_string_list(&config->copy_to_run);
free_string_list(&config->copy_to_dll);
free_string_list(&config->copy_to_dp);
free_string_list(&config->load_dll);
}
static const char *extname(const char *s)
{
const char *e = strrchr(s, '.');
return e ? e+1 : NULL;
}
static void pje_read_source(struct build_job *job, struct string *dir, struct string_list *source, bool system);
static void pje_read_inc(struct build_job *job, struct string *dir, struct string *inc)
{
struct inc_config config = {0};
struct string *file = directory_file(dir, inc);
struct string *file_dir = directory_name(file->text);
pje_parse_inc(file->text, &config);
pje_read_source(job, file_dir, &config.system_source, true);
pje_read_source(job, file_dir, &config.source, false);
pje_free_inc(&config);
free_string(file);
free_string(file_dir);
}
static void pje_read_source(struct build_job *job, struct string *dir, struct string_list *source, bool system)
{
for (unsigned i = 0; i < source->n; i++) {
const char *ext = extname(source->items[i]->text);
if (!strcmp(ext, "inc")) {
pje_read_inc(job, dir, source->items[i]);
} else if (!strcmp(ext, "jaf")) {
string_list_append(system ? &job->system_source : &job->source, directory_file(dir, source->items[i]));
} else if (!strcmp(ext, "hll")) {
if (i+1 >= source->n)
ERROR("Missing HLL name in source list: %s", source->items[i]->text);
if (strchr(source->items[i+1]->text, '.'))
ERROR("HLL name contains '.': %s", source->items[i+1]->text);
string_list_append(&job->headers, directory_file(dir, source->items[i]));
string_list_append(&job->headers, directory_file(dir, source->items[i+1]));
i++;
} else {
ERROR("Unhandled file extension in source list: '%s'", ext);
}
}
}
static void pje_free(struct pje_config *config)
{
if (config->project_name)
free_string(config->project_name);
if (config->code_name)
free_string(config->code_name);
if (config->source_dir)
free_string(config->source_dir);
if (config->hll_dir)
free_string(config->hll_dir);
if (config->obj_dir)
free_string(config->obj_dir);
if (config->output_dir)
free_string(config->output_dir);
free_string_list(&config->system_source);
free_string_list(&config->source);
}
static void build_job_free(struct build_job *job)
{
free_string_list(&job->system_source);
free_string_list(&job->source);
free_string_list(&job->headers);
}
void pje_build(const char *path, int ain_version)
{
struct build_job job = {0};
struct pje_config config = {0};
pje_parse(path, &config);
struct string *pje_dir = directory_name(path);
struct string *src_dir = directory_file(pje_dir, config.source_dir);
struct string *out_dir = directory_file(pje_dir, config.output_dir);
if (mkdir_p(out_dir->text)) {
ERROR("Creating output directory '%s': %s", out_dir->text, strerror(errno));
}
// collect source file names
pje_read_source(&job, src_dir, &config.system_source, true);
pje_read_source(&job, src_dir, &config.source, false);
// TODO: parse hll files
for (unsigned i = 0; i < job.headers.n; i++) {
WARNING("Ignoring header: %s", job.headers.items[i]->text);
}
// consolidate files into list
// TODO: It's unclear from the docs if ALL SystemSource files should be compiled
// before ALL Source files. This becomes an issue if .inc files are used
// which contain both SystemSource and Source lists. E.g., should
// SystemSource files included from the top-level Source list be compiled
// before Source files included from the top-level SystemSource list?
// This needs to be confirmed before implementing global constructors.
unsigned nr_source_files = job.system_source.n + job.source.n;
const char **source_files = xcalloc(nr_source_files, sizeof(const char*));
for (unsigned i = 0; i < job.system_source.n; i++) {
source_files[i] = job.system_source.items[i]->text;
}
for (unsigned i = 0; i < job.source.n; i++) {
source_files[job.system_source.n + i] = job.source.items[i]->text;
}
// open/create ain object
struct ain *ain;
if (config.mod_ain) {
int err;
struct string *mod_ain = directory_file(pje_dir, config.mod_ain);
if (!(ain = ain_open(mod_ain->text, &err))) {
ERROR("Failed to open ain file: %s", ain_strerror);
}
free_string(mod_ain);
} else {
ain = ain_new(ain_version);
}
// apply text substitution, if ModText given
if (config.mod_text) {
struct string *mod_text = directory_file(pje_dir, config.mod_text);
read_text(mod_text->text, ain);
free_string(mod_text);
}
// build
jaf_build(ain, source_files, nr_source_files);
// write to disk
NOTICE("Writing AIN file...");
struct string *output_file = directory_file(out_dir, config.code_name);
ain_write(output_file->text, ain);
free_string(pje_dir);
free_string(src_dir);
free_string(out_dir);
free_string(output_file);
free(source_files);
build_job_free(&job);
pje_free(&config);
ain_free(ain);
}
+1 -1
View File
@@ -3,6 +3,6 @@ int main()
int a;
ref int ra = a;
ref int rb = ra;
ra = 1;
rb = 1;
return !(a == 1);
}