mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-01 03:18:09 +03:00
Fixes #2. The problem here was that init_member_functions assumed ASCII or SJIS encoding of function/struct names in the ain file. Now it will convert to UTF-8 when analyzing member functions. This commit also removes the --ain-encoding option to ainedit. --output-encoding should always match the encoding of the input ain file. --transcode can be used to change the encoding before running running further ainedit commands.
947 lines
29 KiB
C
947 lines
29 KiB
C
/* 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 <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include "ainedit.h"
|
|
#include "system4.h"
|
|
#include "system4/ain.h"
|
|
#include "system4/hashtable.h"
|
|
#include "system4/instructions.h"
|
|
#include "system4/string.h"
|
|
#include "asm_parser.tab.h"
|
|
|
|
extern FILE *asm_in;
|
|
|
|
KHASH_MAP_INIT_STR(string_ht, size_t);
|
|
|
|
// TODO: better error messages
|
|
#define ASM_ERROR(state, ...) ERROR(__VA_ARGS__)
|
|
|
|
#define MACRO(code, _name, nargs, _ip_inc, ...) \
|
|
[code - PSEUDO_OP_OFFSET] = { \
|
|
.opcode = code, \
|
|
.name = _name, \
|
|
.nr_args = nargs, \
|
|
.ip_inc = _ip_inc, \
|
|
.implemented = false, \
|
|
.args = { __VA_ARGS__ } \
|
|
}
|
|
|
|
#define PSEUDO_OP(code, _name, nargs, ...) \
|
|
[code - PSEUDO_OP_OFFSET] = { \
|
|
.opcode = code, \
|
|
.name = _name, \
|
|
.nr_args = nargs, \
|
|
.ip_inc = 0, \
|
|
.implemented = false, \
|
|
.args = { __VA_ARGS__ } \
|
|
}
|
|
|
|
const struct instruction asm_pseudo_ops[NR_PSEUDO_OPS - PSEUDO_OP_OFFSET] = {
|
|
PSEUDO_OP(PO_CASE, ".CASE", 2),
|
|
PSEUDO_OP(PO_STRCASE, ".STRCASE", 2),
|
|
PSEUDO_OP(PO_DEFAULT, ".DEFAULT", 1),
|
|
PSEUDO_OP(PO_SETSTR, ".SETSTR", 2),
|
|
PSEUDO_OP(PO_SETMSG, ".SETMSG", 2),
|
|
MACRO(PO_MSG, ".MSG", 1, 6),
|
|
MACRO(PO_LOCALREF, ".LOCALREF", 1, 10),
|
|
MACRO(PO_LOCALREFREF, ".LOCALREFREF", 1, 10),
|
|
MACRO(PO_LOCALINC, ".LOCALINC", 1, 10),
|
|
MACRO(PO_LOCALINC2, ".LOCALINC2", 1, 20),
|
|
MACRO(PO_LOCALDEC, ".LOCALDEC", 1, 10),
|
|
MACRO(PO_LOCALDEC2, ".LOCALDEC2", 1, 20),
|
|
MACRO(PO_LOCALPLUSA, ".LOCALPLUSA", 2, 18),
|
|
MACRO(PO_LOCALMINUSA, ".LOCALMINUSA", 2, 18),
|
|
MACRO(PO_LOCALASSIGN, ".LOCALASSIGN", 2, 18),
|
|
MACRO(PO_LOCALASSIGN2, ".LOCALASSIGN2", 1, 14),
|
|
MACRO(PO_F_LOCALASSIGN, ".F_LOCALASSIGN", 2, 18),
|
|
MACRO(PO_STACK_LOCALASSIGN, ".STACK_LOCALASSIGN", 1, 26),
|
|
MACRO(PO_S_LOCALASSIGN, ".S_LOCALASSIGN", 2, 20),
|
|
MACRO(PO_LOCALDELETE, ".LOCALDELETE", 1, 24),
|
|
MACRO(PO_LOCALCREATE, ".LOCALCREATE", 2, 34),
|
|
MACRO(PO_GLOBALREF, ".GLOBALREF", 1, 10),
|
|
MACRO(PO_GLOBALREFREF, ".GLOBALREFREF", 1, 10),
|
|
MACRO(PO_GLOBALINC, ".GLOBALINC", 1, 10),
|
|
MACRO(PO_GLOBALDEC, ".GLOBALDEC", 1, 10),
|
|
MACRO(PO_GLOBALASSIGN, ".GLOBALASSIGN", 2, 18),
|
|
MACRO(PO_F_GLOBALASSIGN, ".F_GLOBALASSIGN", 2, 18),
|
|
MACRO(PO_STRUCTREF, ".STRUCTREF", 2, 10),
|
|
MACRO(PO_STRUCTREFREF, ".STRUCTREFREF", 2, 10),
|
|
MACRO(PO_STRUCTINC, ".STRUCTINC", 2, 10),
|
|
MACRO(PO_STRUCTDEC, ".STRUCTDEC", 2, 10),
|
|
MACRO(PO_STRUCTASSIGN, ".STRUCTASSIGN", 3, 18),
|
|
MACRO(PO_F_STRUCTASSIGN, ".F_STRUCTASSIGN", 3, 18),
|
|
MACRO(PO_PUSHVMETHOD, ".PUSHVMETHOD", 2, 30),
|
|
};
|
|
|
|
struct string_table {
|
|
struct string **strings;
|
|
size_t size;
|
|
size_t allocated;
|
|
};
|
|
|
|
#define ASM_FUNC_STACK_SIZE 16
|
|
|
|
struct asm_state {
|
|
struct ain *ain;
|
|
uint32_t flags;
|
|
uint8_t *buf;
|
|
size_t buf_ptr;
|
|
size_t buf_len;
|
|
int32_t func;
|
|
int32_t func_stack[ASM_FUNC_STACK_SIZE];
|
|
int32_t lib;
|
|
struct hash_table *str_ht;
|
|
};
|
|
|
|
const_pure int32_t asm_instruction_width(int opcode)
|
|
{
|
|
if (opcode >= PSEUDO_OP_OFFSET)
|
|
return asm_pseudo_ops[opcode - PSEUDO_OP_OFFSET].ip_inc;
|
|
return instruction_width(opcode);
|
|
}
|
|
|
|
static void init_asm_state(struct asm_state *state, struct ain *ain, uint32_t flags)
|
|
{
|
|
memset(state, 0, sizeof(*state));
|
|
state->ain = ain;
|
|
state->flags = flags;
|
|
state->func = -1;
|
|
state->lib = -1;
|
|
state->str_ht = ht_create(4096);
|
|
|
|
for (intptr_t i = 0; i < ain->nr_strings; i++) {
|
|
struct ht_slot *p = ht_put(state->str_ht, ain->strings[i]->text);
|
|
if (p->value) {
|
|
char *u = encode_text_for_print(ain->strings[i]->text);
|
|
WARNING("Duplicate string in ain file: \"%s\"", u);
|
|
free(u);
|
|
}
|
|
p->value = (void*)(i+1);
|
|
}
|
|
}
|
|
|
|
static void fini_asm_state(struct asm_state *state)
|
|
{
|
|
ht_free(state->str_ht);
|
|
}
|
|
|
|
static void asm_write_opcode(struct asm_state *state, uint16_t opcode)
|
|
{
|
|
// if (state->buf_len - state->buf_ptr <= (size_t)instruction_width(opcode)) {
|
|
if (state->buf_len - state->buf_ptr <= 18) {
|
|
if (!state->buf) {
|
|
state->buf_len = 4096;
|
|
state->buf = xmalloc(state->buf_len);
|
|
} else {
|
|
state->buf_len = state->buf_len * 2;
|
|
state->buf = xrealloc(state->buf, state->buf_len);
|
|
}
|
|
}
|
|
|
|
state->buf[state->buf_ptr++] = opcode & 0xFF;
|
|
state->buf[state->buf_ptr++] = (opcode & 0xFF00) >> 8;
|
|
}
|
|
|
|
static void asm_write_argument(struct asm_state *state, uint32_t arg)
|
|
{
|
|
state->buf[state->buf_ptr++] = (arg & 0x000000FF);
|
|
state->buf[state->buf_ptr++] = (arg & 0x0000FF00) >> 8;
|
|
state->buf[state->buf_ptr++] = (arg & 0x00FF0000) >> 16;
|
|
state->buf[state->buf_ptr++] = (arg & 0xFF000000) >> 24;
|
|
}
|
|
|
|
const struct instruction *asm_get_instruction(const char *name)
|
|
{
|
|
if (name[0] == '.') {
|
|
for (int i = 0; i < NR_PSEUDO_OPS - PSEUDO_OP_OFFSET; i++) {
|
|
if (!strcmp(name, asm_pseudo_ops[i].name))
|
|
return &asm_pseudo_ops[i];
|
|
}
|
|
}
|
|
for (int i = 0; i < NR_OPCODES; i++) {
|
|
if (!strcmp(name, instructions[i].name))
|
|
return &instructions[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static char *parse_identifier(possibly_unused struct asm_state *state, char *s, int *n)
|
|
{
|
|
char *delim = strchr(s, '#');
|
|
if (!delim) {
|
|
*n = 0;
|
|
return s;
|
|
}
|
|
|
|
*delim = '\0';
|
|
delim++;
|
|
|
|
char *endptr;
|
|
long nn = strtol(delim, &endptr, 10);
|
|
if (*delim && !*endptr) {
|
|
*n = nn;
|
|
return s;
|
|
}
|
|
|
|
*delim = '#';
|
|
ASM_ERROR(state, "Invalid identifier: '%s' (bad suffix)", s);
|
|
}
|
|
|
|
static int32_t parse_integer_constant(possibly_unused struct asm_state *state, const char *arg)
|
|
{
|
|
char *endptr;
|
|
errno = 0;
|
|
long i = strtol(arg, &endptr, 0);
|
|
if (errno || *endptr != '\0')
|
|
ASM_ERROR(state, "Invalid integer constant: '%s'", arg);
|
|
//if (i > INT32_MAX || i < INT32_MIN)
|
|
// ASM_ERROR(state, "Integer would be truncated: %ld -> %d", i, (int32_t)i);
|
|
return i;
|
|
}
|
|
|
|
static void realloc_switch_table(struct ain *ain, int i)
|
|
{
|
|
if (i < ain->nr_switches)
|
|
return;
|
|
ain->switches = xrealloc_array(ain->switches, ain->nr_switches, i+1, sizeof(struct ain_switch));
|
|
ain->nr_switches = i+1;
|
|
}
|
|
|
|
static void realloc_switch_cases(struct ain_switch *swi, int i)
|
|
{
|
|
if (i < swi->nr_cases)
|
|
return;
|
|
swi->cases = xrealloc_array(swi->cases, swi->nr_cases, i+1, sizeof(struct ain_switch_case));
|
|
swi->nr_cases = i+1;
|
|
}
|
|
|
|
static void realloc_string_table(struct ain *ain, int i)
|
|
{
|
|
if (i < ain->nr_strings)
|
|
return;
|
|
ain->strings = xrealloc_array(ain->strings, ain->nr_strings, i+1, sizeof(struct string*));
|
|
ain->nr_strings = i+1;
|
|
}
|
|
|
|
static void realloc_message_table(struct ain *ain, int i)
|
|
{
|
|
if (i < ain->nr_messages)
|
|
return;
|
|
ain->messages = xrealloc_array(ain->messages, ain->nr_messages, i+1, sizeof(struct string*));
|
|
ain->nr_messages = i+1;
|
|
}
|
|
|
|
static int asm_add_string(struct asm_state *state, const char *str)
|
|
{
|
|
char *sjis = encode_text(str);
|
|
struct ht_slot *p = ht_put(state->str_ht, sjis);
|
|
if (p->value) {
|
|
free(sjis);
|
|
return (intptr_t)p->value - 1;
|
|
}
|
|
|
|
int i = state->ain->nr_strings;
|
|
realloc_string_table(state->ain, i);
|
|
state->ain->strings[i] = make_string(sjis, strlen(sjis));
|
|
p->key = state->ain->strings[i]->text;
|
|
free(sjis);
|
|
return i;
|
|
}
|
|
|
|
static uint32_t asm_resolve_arg(struct asm_state *state, enum opcode opcode, enum instruction_argtype type, const char *arg)
|
|
{
|
|
if (state->flags & ASM_RAW)
|
|
type = T_INT;
|
|
|
|
struct ain *ain = state->ain;
|
|
switch (type) {
|
|
case T_INT:
|
|
return parse_integer_constant(state, arg);
|
|
case T_FLOAT: {
|
|
char *endptr;
|
|
union { int32_t i; float f; } v;
|
|
errno = 0;
|
|
v.f = strtof(arg, &endptr);
|
|
if (errno || *endptr != '\0')
|
|
ASM_ERROR(state, "Invalid float: %s", arg);
|
|
return v.i;
|
|
}
|
|
case T_SWITCH: {
|
|
int i = parse_integer_constant(state, arg);
|
|
if (i < 0)
|
|
ASM_ERROR(state, "Invalid switch number: %d", i);
|
|
realloc_switch_table(ain, i);
|
|
if (opcode == SWITCH)
|
|
ain->switches[i].case_type = AIN_SWITCH_INT;
|
|
else if (opcode == STRSWITCH)
|
|
ain->switches[i].case_type = AIN_SWITCH_STRING;
|
|
return i;
|
|
}
|
|
case T_ADDR: {
|
|
khiter_t k;
|
|
k = kh_get(label_table, label_table, arg);
|
|
if (k == kh_end(label_table))
|
|
ASM_ERROR(state, "Unable to resolve label: '%s'", arg);
|
|
return kh_value(label_table, k);
|
|
}
|
|
case T_FUNC: {
|
|
char *u = encode_text(arg);
|
|
struct ain_function *f = ain_get_function(ain, u);
|
|
free(u);
|
|
if (!f)
|
|
ASM_ERROR(state, "Unable to resolve function: '%s'", arg);
|
|
return f - ain->functions;
|
|
}
|
|
case T_STRING: {
|
|
return asm_add_string(state, arg);
|
|
}
|
|
case T_MSG: {
|
|
int32_t i = parse_integer_constant(state, arg);
|
|
if (i < 0)
|
|
ASM_ERROR(state, "Message index out of bounds: '%s'", arg);
|
|
realloc_message_table(ain, i);
|
|
return i;
|
|
}
|
|
case T_LOCAL: {
|
|
int n, count = 0;
|
|
char *u = encode_text(arg);
|
|
u = parse_identifier(state, u, &n);
|
|
struct ain_function *f = &state->ain->functions[state->func];
|
|
for (int i = 0; i < f->nr_vars; i++) {
|
|
if (!strcmp(u, f->vars[i].name)) {
|
|
if (count < n) {
|
|
count++;
|
|
continue;
|
|
}
|
|
free(u);
|
|
return i;
|
|
}
|
|
}
|
|
ASM_ERROR(state, "Unable to resolve local variable: '%s'", arg);
|
|
}
|
|
case T_GLOBAL: {
|
|
char *u = encode_text(arg);
|
|
for (int i = 0; i < state->ain->nr_globals; i++) {
|
|
if (!strcmp(u, state->ain->globals[i].name)) {
|
|
free(u);
|
|
return i;
|
|
}
|
|
}
|
|
ASM_ERROR(state, "Unable to resolve global variable: '%s'", arg);
|
|
}
|
|
case T_STRUCT: {
|
|
char *u = encode_text(arg);
|
|
struct ain_struct *s = ain_get_struct(state->ain, u);
|
|
free(u);
|
|
if (!s)
|
|
ASM_ERROR(state, "Unable to resolve struct: '%s'", arg);
|
|
return s - state->ain->structures;
|
|
}
|
|
case T_SYSCALL: {
|
|
for (int i = 0; i < NR_SYSCALLS; i++) {
|
|
if (!strcmp(arg, syscalls[i].name))
|
|
return i;
|
|
}
|
|
ASM_ERROR(state, "Unable to resolve system call: '%s'", arg);
|
|
}
|
|
case T_HLL: {
|
|
for (int i = 0; i < state->ain->nr_libraries; i++) {
|
|
if (!strcmp(arg, state->ain->libraries[i].name)) {
|
|
state->lib = i;
|
|
return i;
|
|
}
|
|
}
|
|
ASM_ERROR(state, "Unable to resolve library: '%s'", arg);
|
|
}
|
|
case T_HLLFUNC: {
|
|
if (state->lib < 0)
|
|
ERROR("Tried to resolve library function without active library?");
|
|
int n, count = 0;
|
|
char *u = encode_text(arg);
|
|
u = parse_identifier(state, u, &n);
|
|
for (int i = 0; i < state->ain->libraries[state->lib].nr_functions; i++) {
|
|
if (strcmp(u, state->ain->libraries[state->lib].functions[i].name))
|
|
continue;
|
|
if (count < n) {
|
|
count++;
|
|
continue;
|
|
}
|
|
state->lib = -1;
|
|
free(u);
|
|
return i;
|
|
}
|
|
ASM_ERROR(state, "Unable to resolve library function: '%s.%s'",
|
|
state->ain->libraries[state->lib].name, arg);
|
|
}
|
|
case T_FILE: {
|
|
if (!state->ain->nr_filenames)
|
|
return atoi(arg);
|
|
char *u = encode_text(arg);
|
|
for (int i = 0; i < state->ain->nr_filenames; i++) {
|
|
if (!strcmp(u, state->ain->filenames[i])) {
|
|
free(u);
|
|
return i;
|
|
}
|
|
}
|
|
ASM_ERROR(state, "Unable to resolve filename: '%s'", arg);
|
|
}
|
|
case T_DLG: {
|
|
char *u = encode_text(arg);
|
|
for (int i = 0; i < state->ain->nr_delegates; i++) {
|
|
if (!strcmp(u, state->ain->delegates[i].name)) {
|
|
free(u);
|
|
return i;
|
|
}
|
|
}
|
|
ASM_ERROR(state, "Unable to resolve delegate: '%s'", arg);
|
|
}
|
|
default:
|
|
ASM_ERROR(state, "Unhandled argument type: %d", type);
|
|
}
|
|
}
|
|
|
|
static void decompose_switch_index(struct asm_state *state, char *in, int *switch_out, int *case_out)
|
|
{
|
|
char *tmp = strchr(in, ':');
|
|
if (!tmp)
|
|
ASM_ERROR(state, "Invalid switch:case index: '%s'", in);
|
|
|
|
*tmp++ = '\0';
|
|
|
|
int n_switch = parse_integer_constant(state, in);
|
|
int n_case = parse_integer_constant(state, tmp);
|
|
if (n_switch < 0 || n_switch >= state->ain->nr_switches)
|
|
ASM_ERROR(state, "Invalid switch index: %d", n_switch);
|
|
if (n_case < 0 || n_case >= state->ain->switches[n_switch].nr_cases)
|
|
ASM_ERROR(state, "Invalid case index: %d", n_case);
|
|
|
|
*switch_out = n_switch;
|
|
*case_out = n_case;
|
|
}
|
|
|
|
static int find_member(struct ain_struct *s, char *_member_name)
|
|
{
|
|
int member_no = -1;
|
|
char *member_name = encode_text(_member_name);
|
|
|
|
for (int i = 0; i < s->nr_members; i++) {
|
|
if (!strcmp(member_name, s->members[i].name)) {
|
|
member_no = i;
|
|
break;
|
|
}
|
|
}
|
|
free(member_name);
|
|
return member_no;
|
|
}
|
|
|
|
static int get_member_no(struct asm_state *state, char *struct_name, char *_member_name)
|
|
{
|
|
int struct_no = asm_resolve_arg(state, PUSH, T_STRUCT, struct_name);
|
|
int member_no = find_member(&state->ain->structures[struct_no], _member_name);
|
|
|
|
if (member_no < 0) {
|
|
char *sname = encode_text_for_print(struct_name);
|
|
char *mname = encode_text_for_print(_member_name);
|
|
ASM_ERROR(state, "Unable to resolve struct member: %s.%s", sname, mname);
|
|
}
|
|
|
|
return member_no;
|
|
}
|
|
|
|
static int get_vtable_no(struct asm_state *state)
|
|
{
|
|
int struct_type = state->ain->functions[state->func].struct_type;
|
|
if (struct_type < 0)
|
|
ASM_ERROR(state, ".PUSHVMETHOD macro in non-member function");
|
|
|
|
int member_no = find_member(&state->ain->structures[struct_type], "<vtable>");
|
|
|
|
if (member_no < 0) {
|
|
ASM_ERROR(state, "Unable to resolve vtable");
|
|
}
|
|
|
|
return member_no;
|
|
}
|
|
|
|
void handle_pseudo_op(struct asm_state *state, struct parse_instruction *instr)
|
|
{
|
|
switch (instr->opcode) {
|
|
case PO_CASE: {
|
|
int n_switch, n_case, c;
|
|
decompose_switch_index(state, kv_A(*instr->args, 0)->text, &n_switch, &n_case);
|
|
|
|
struct ain_switch *swi = &state->ain->switches[n_switch];
|
|
c = parse_integer_constant(state, kv_A(*instr->args, 1)->text);
|
|
realloc_switch_cases(swi, n_case);
|
|
swi->cases[n_case].address = state->buf_ptr;
|
|
swi->cases[n_case].value = c;
|
|
break;
|
|
}
|
|
case PO_STRCASE: {
|
|
int n_switch, n_case, c;
|
|
decompose_switch_index(state, kv_A(*instr->args, 0)->text, &n_switch, &n_case);
|
|
|
|
struct ain_switch *swi = &state->ain->switches[n_switch];
|
|
c = asm_add_string(state, kv_A(*instr->args, 1)->text);
|
|
realloc_switch_cases(swi, n_case);
|
|
swi->cases[n_case].address = state->buf_ptr;
|
|
swi->cases[n_case].value = c;
|
|
break;
|
|
}
|
|
case PO_DEFAULT: {
|
|
int n_switch = parse_integer_constant(state, kv_A(*instr->args, 0)->text);
|
|
if (n_switch < 0 || n_switch >= state->ain->nr_switches)
|
|
ASM_ERROR(state, "Invalid switch index: %d", n_switch);
|
|
state->ain->switches[n_switch].default_address = state->buf_ptr;
|
|
break;
|
|
}
|
|
case PO_SETSTR: {
|
|
int n_str = parse_integer_constant(state, kv_A(*instr->args, 0)->text);
|
|
if (n_str < 0)
|
|
ASM_ERROR(state, "Invalid string index: %d", n_str);
|
|
realloc_string_table(state->ain, n_str);
|
|
if (state->ain->strings[n_str])
|
|
free_string(state->ain->strings[n_str]);
|
|
char *sjis = encode_text(kv_A(*instr->args, 1)->text);
|
|
state->ain->strings[n_str] = make_string(sjis, strlen(sjis));
|
|
free(sjis);
|
|
break;
|
|
}
|
|
case PO_SETMSG: {
|
|
int n_msg = parse_integer_constant(state, kv_A(*instr->args, 0)->text);
|
|
if (n_msg < 0)
|
|
ASM_ERROR(state, "Invalid message index: %d", n_msg);
|
|
realloc_message_table(state->ain, n_msg);
|
|
if (state->ain->messages[n_msg])
|
|
free_string(state->ain->messages[n_msg]);
|
|
char *sjis = encode_text(kv_A(*instr->args, 1)->text);
|
|
state->ain->messages[n_msg] = make_string(sjis, strlen(sjis));
|
|
free(sjis);
|
|
break;
|
|
}
|
|
case PO_MSG: {
|
|
int n_msg = state->ain->nr_messages;
|
|
realloc_message_table(state->ain, n_msg);
|
|
char *sjis = encode_text(kv_A(*instr->args, 0)->text);
|
|
state->ain->messages[n_msg] = make_string(sjis, strlen(sjis));
|
|
free(sjis);
|
|
|
|
asm_write_opcode(state, MSG);
|
|
asm_write_argument(state, n_msg);
|
|
break;
|
|
}
|
|
case PO_LOCALREF: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, REF);
|
|
break;
|
|
}
|
|
case PO_GLOBALREF: {
|
|
asm_write_opcode(state, PUSHGLOBALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_GLOBAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, REF);
|
|
break;
|
|
}
|
|
case PO_LOCALREFREF: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, REFREF);
|
|
break;
|
|
}
|
|
case PO_GLOBALREFREF: {
|
|
asm_write_opcode(state, PUSHGLOBALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_GLOBAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, REFREF);
|
|
break;
|
|
}
|
|
case PO_LOCALINC: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, INC);
|
|
break;
|
|
}
|
|
case PO_LOCALINC2: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, DUP2);
|
|
asm_write_opcode(state, REF);
|
|
asm_write_opcode(state, DUP_X2);
|
|
asm_write_opcode(state, POP);
|
|
asm_write_opcode(state, INC);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_LOCALDEC: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, DEC);
|
|
break;
|
|
}
|
|
case PO_LOCALDEC2: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, DUP2);
|
|
asm_write_opcode(state, REF);
|
|
asm_write_opcode(state, DUP_X2);
|
|
asm_write_opcode(state, POP);
|
|
asm_write_opcode(state, DEC);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_LOCALPLUSA: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_INT, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, PLUSA);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_LOCALMINUSA: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_INT, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, MINUSA);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_LOCALASSIGN: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_INT, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_LOCALASSIGN2: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, SWAP);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, SWAP);
|
|
asm_write_opcode(state, ASSIGN);
|
|
break;
|
|
}
|
|
case PO_F_LOCALASSIGN: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, F_PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, F_PUSH, T_FLOAT, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, F_ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_STACK_LOCALASSIGN: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, REF);
|
|
asm_write_opcode(state, DELETE);
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, SWAP);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, SWAP);
|
|
asm_write_opcode(state, ASSIGN);
|
|
break;
|
|
}
|
|
case PO_S_LOCALASSIGN: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, REF);
|
|
asm_write_opcode(state, S_PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, S_PUSH, T_STRING, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, S_ASSIGN);
|
|
asm_write_opcode(state, DELETE);
|
|
break;
|
|
}
|
|
case PO_LOCALDELETE: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, DUP2);
|
|
asm_write_opcode(state, REF);
|
|
asm_write_opcode(state, DELETE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, -1);
|
|
asm_write_opcode(state, ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_LOCALCREATE: {
|
|
asm_write_opcode(state, PUSHLOCALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_LOCAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, DUP2);
|
|
asm_write_opcode(state, REF);
|
|
asm_write_opcode(state, DELETE);
|
|
asm_write_opcode(state, DUP2);
|
|
asm_write_opcode(state, NEW);
|
|
asm_write_argument(state, asm_resolve_arg(state, NEW, T_STRUCT, kv_A(*instr->args, 1)->text));
|
|
asm_write_argument(state, -1);
|
|
asm_write_opcode(state, ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
asm_write_opcode(state, POP);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_GLOBALINC: {
|
|
asm_write_opcode(state, PUSHGLOBALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_GLOBAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, INC);
|
|
break;
|
|
}
|
|
case PO_GLOBALDEC: {
|
|
asm_write_opcode(state, PUSHGLOBALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_GLOBAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, DEC);
|
|
break;
|
|
}
|
|
case PO_GLOBALASSIGN: {
|
|
asm_write_opcode(state, PUSHGLOBALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_GLOBAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_INT, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_F_GLOBALASSIGN: {
|
|
asm_write_opcode(state, PUSHGLOBALPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_GLOBAL, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, F_PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, F_PUSH, T_FLOAT, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, F_ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_STRUCTREF: {
|
|
asm_write_opcode(state, PUSHSTRUCTPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, get_member_no(state, kv_A(*instr->args, 0)->text, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, REF);
|
|
break;
|
|
}
|
|
case PO_STRUCTREFREF: {
|
|
asm_write_opcode(state, PUSHSTRUCTPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, get_member_no(state, kv_A(*instr->args, 0)->text, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, REFREF);
|
|
break;
|
|
}
|
|
case PO_STRUCTINC: {
|
|
asm_write_opcode(state, PUSHSTRUCTPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, get_member_no(state, kv_A(*instr->args, 0)->text, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, INC);
|
|
break;
|
|
}
|
|
case PO_STRUCTDEC: {
|
|
asm_write_opcode(state, PUSHSTRUCTPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, get_member_no(state, kv_A(*instr->args, 0)->text, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, DEC);
|
|
break;
|
|
}
|
|
case PO_STRUCTASSIGN: {
|
|
asm_write_opcode(state, PUSHSTRUCTPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, get_member_no(state, kv_A(*instr->args, 0)->text, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_INT, kv_A(*instr->args, 2)->text));
|
|
asm_write_opcode(state, ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_F_STRUCTASSIGN: {
|
|
asm_write_opcode(state, PUSHSTRUCTPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, get_member_no(state, kv_A(*instr->args, 0)->text, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, F_PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, F_PUSH, T_FLOAT, kv_A(*instr->args, 2)->text));
|
|
asm_write_opcode(state, F_ASSIGN);
|
|
asm_write_opcode(state, POP);
|
|
break;
|
|
}
|
|
case PO_PUSHVMETHOD: {
|
|
asm_write_opcode(state, PUSHSTRUCTPAGE);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_INT, kv_A(*instr->args, 0)->text));
|
|
asm_write_opcode(state, DUP_U2);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, get_vtable_no(state));
|
|
asm_write_opcode(state, REF);
|
|
asm_write_opcode(state, SWAP);
|
|
asm_write_opcode(state, PUSH);
|
|
asm_write_argument(state, asm_resolve_arg(state, PUSH, T_INT, kv_A(*instr->args, 1)->text));
|
|
asm_write_opcode(state, ADD);
|
|
asm_write_opcode(state, REF);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void validate_ain(struct ain *ain)
|
|
{
|
|
for (int i = 0; i < ain->nr_strings; i++) {
|
|
if (ain->strings[i])
|
|
continue;
|
|
WARNING("String 0x%x unallocated", i);
|
|
ain->strings[i] = make_string("", 0);
|
|
}
|
|
|
|
for (int i = 0; i < ain->nr_messages; i++) {
|
|
if (ain->messages[i])
|
|
continue;
|
|
WARNING("Message 0x%x unallocated", i);
|
|
ain->messages[i] = make_string("", 0);
|
|
}
|
|
|
|
for (int i = 0; i < ain->nr_switches; i++) {
|
|
if (!ain->switches[i].nr_cases)
|
|
WARNING("Switch 0x%x unallocated", i);
|
|
}
|
|
|
|
if (ain->main < 0 || ain->main >= ain->nr_functions)
|
|
ERROR("Invalid main function: %d", ain->main);
|
|
if (strcmp(ain->functions[ain->main].name, "main"))
|
|
WARNING("Main function is not named 'main': '%s'", ain->functions[ain->main].name);
|
|
|
|
if (ain->MSGF.present) {
|
|
if (ain->msgf < 0 || ain->msgf >= ain->nr_functions)
|
|
ERROR("Invalid message function: %d", ain->msgf);
|
|
if (strcmp(ain->functions[ain->msgf].name, "message"))
|
|
WARNING("Message function is not named 'message': '%s' (%d)", ain->functions[ain->msgf].name, ain->msgf);
|
|
}
|
|
}
|
|
|
|
static void asm_enter_function(struct asm_state *state, int32_t fno)
|
|
{
|
|
if (fno < 0 || fno >= state->ain->nr_functions)
|
|
ASM_ERROR(state, "Invalid function number: %d", fno);
|
|
|
|
for (int i = 1; i < ASM_FUNC_STACK_SIZE; i++) {
|
|
state->func_stack[i] = state->func_stack[i-1];
|
|
}
|
|
state->func_stack[0] = state->func;
|
|
state->func = fno;
|
|
|
|
state->ain->functions[fno].address = state->buf_ptr + 6;
|
|
asm_write_opcode(state, FUNC);
|
|
asm_write_argument(state, fno);
|
|
}
|
|
|
|
static void asm_leave_function(struct asm_state *state)
|
|
{
|
|
state->func = state->func_stack[0];
|
|
for (int i = 1; i < ASM_FUNC_STACK_SIZE; i++) {
|
|
state->func_stack[i-1] = state->func_stack[i];
|
|
}
|
|
}
|
|
|
|
void asm_assemble_jam(const char *filename, struct ain *ain, uint32_t flags)
|
|
{
|
|
struct asm_state state;
|
|
init_asm_state(&state, ain, flags);
|
|
|
|
if (filename) {
|
|
if (!strcmp(filename, "-"))
|
|
asm_in = stdin;
|
|
else
|
|
asm_in = fopen(filename, "r");
|
|
if (!asm_in)
|
|
ERROR("Opening input file '%s': %s", filename, strerror(errno));
|
|
}
|
|
label_table = kh_init(label_table);
|
|
NOTICE("Parsing...");
|
|
asm_parse();
|
|
|
|
NOTICE("Encoding...");
|
|
for (size_t i = 0; i < kv_size(*parsed_code); i++) {
|
|
struct parse_instruction *instr = kv_A(*parsed_code, i);
|
|
if (instr->opcode >= PSEUDO_OP_OFFSET) {
|
|
handle_pseudo_op(&state, instr);
|
|
continue;
|
|
}
|
|
|
|
struct instruction *idef = &instructions[instr->opcode];
|
|
|
|
// NOTE: special case: we need to record the new function address in the ain structure
|
|
if (idef->opcode == FUNC) {
|
|
asm_enter_function(&state, asm_resolve_arg(&state, FUNC, T_INT, kv_A(*instr->args, 0)->text));
|
|
continue;
|
|
} else if (idef->opcode == ENDFUNC) {
|
|
asm_leave_function(&state);
|
|
}
|
|
|
|
asm_write_opcode(&state, instr->opcode);
|
|
for (int a = 0; a < idef->nr_args; a++) {
|
|
asm_write_argument(&state, asm_resolve_arg(&state, idef->opcode, idef->args[a], kv_A(*instr->args, a)->text));
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < kv_size(*parsed_code); i++) {
|
|
struct parse_instruction *instr = kv_A(*parsed_code, i);
|
|
if (!instr->args)
|
|
continue;
|
|
for (size_t a = 0; a < kv_size(*instr->args); a++) {
|
|
free_string(kv_A(*instr->args, a));
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (state.buf_ptr != ain->code_size)
|
|
WARNING("CODE SIZE CHANGED");
|
|
if (state.strings.size != (size_t)ain->nr_strings)
|
|
WARNING("NR STRINGS CHANGED (%d -> %lu)", ain->nr_strings, state.strings.size);
|
|
if (state.messages.size != (size_t)ain->nr_messages)
|
|
WARNING("NR MESSAGES CHANGED (%d -> %lu)", ain->nr_messages, state.messages.size);
|
|
*/
|
|
|
|
free(ain->code);
|
|
ain->code = state.buf;
|
|
ain->code_size = state.buf_ptr;
|
|
|
|
validate_ain(ain);
|
|
|
|
fini_asm_state(&state);
|
|
}
|