vc3/asm/main.c
2025-04-04 00:26:55 +02:00

453 lines
13 KiB
C

#include "assemble.h"
#include "build_header.h"
#include "eval.h"
#include "parse.h"
#include "report.h"
#include "resolve.h"
#include "str.h"
#include <assert.h>
#include <errno.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
const char* input_file;
const char* output_file;
} Args;
static inline Args parse_args(int argc, char** argv);
static inline char* read_text_file(const char* filename);
static inline int include_file(IdentResolver* resolver,
OperandEvaluator* evaluator,
const char* origin,
const char* filename);
static inline void report_redefinition(
const IdentResol* existing, PStmt* stmt, Reporter* rep);
static inline int define_labels(
IdentResolver* resolver, PLabel* label, uint16_t asm_ip, Reporter* rep);
static inline void use_labels(IdentResolver* resolver, PLabel* label);
int main(int argc, char** argv)
{
int res = 0;
Args args = parse_args(argc, argv);
char* input_text = read_text_file(args.input_file);
bool errors_occured = false;
Parser parser;
parser_construct(&parser, args.input_file, input_text);
Reporter rep = {
.filename = parser.lexer.filename,
.text = parser.lexer.text,
.text_len = parser.lexer.text_len,
};
IdentResolver resolver;
ident_resolver_construct(&resolver);
OperandEvaluator evaluator;
operand_evaluator_construct(&evaluator, &resolver, &rep);
HeaderBuilder* header_builder = header_builder_new();
size_t lines_capacity = 1024;
PLine** lines = malloc(sizeof(PLine*) * lines_capacity);
size_t lines_size = 0;
size_t extern_ids = 0;
while (!parser_done(&parser)) {
PStmt* stmt = parser_next_stmt(&parser);
if (!stmt) {
continue;
}
switch (stmt->ty) {
case PStmtTy_Line:
if (lines_size + 1 > lines_capacity) {
lines_capacity *= 2;
lines = realloc(lines, sizeof(PLine*) * lines_capacity);
}
lines[lines_size++] = stmt->line;
// NOTE: Shallow free.
free(stmt);
break;
case PStmtTy_Global:
header_builder_add_global_sym(
header_builder, asm_strdup(stmt->ident), stmt->loc, 0);
pstmt_free(stmt);
break;
case PStmtTy_Extern: {
const IdentResol* existing
= ident_resolver_resolve(&resolver, stmt->ident);
if (existing != NULL) {
report_redefinition(existing, stmt, &rep);
pstmt_free(stmt);
continue;
}
size_t id = extern_ids++;
ident_resolver_define_extern(
&resolver, asm_strdup(stmt->ident), stmt->loc, id);
header_builder_add_extern_sym(
header_builder, id, asm_strdup(stmt->ident));
pstmt_free(stmt);
break;
}
case PStmtTy_Const: {
const IdentResol* existing
= ident_resolver_resolve(&resolver, stmt->ident);
if (existing != NULL) {
report_redefinition(existing, stmt, &rep);
pstmt_free(stmt);
continue;
}
EvaledOperand evaled
= eval_operand_to_imm(&evaluator, stmt->value);
if (evaled.ty == EoTy_Err) {
pstmt_free(stmt);
errors_occured = true;
continue;
}
ident_resolver_define_const(&resolver,
asm_strdup(stmt->ident),
stmt->loc,
evaled.imm,
asm_strdup(args.input_file));
pstmt_free(stmt);
break;
}
case PStmtTy_Include: {
int include_res = include_file(
&resolver, &evaluator, args.input_file, stmt->str);
pstmt_free(stmt);
if (include_res != 0) {
errors_occured = true;
}
break;
}
}
}
errors_occured |= parser_error_occured(&parser);
size_t chunk_capacity = 64;
uint16_t* chunk = malloc(sizeof(uint16_t) * chunk_capacity);
uint16_t ip = 0;
for (size_t i = 0; i < lines_size; ++i) {
operand_evaluator_reset_externals(&evaluator);
int res = define_labels(&resolver, lines[i]->labels, ip, &rep);
if (res != 0)
errors_occured = true;
uint16_t size = pline_assemble(&evaluator, chunk, lines[i], &rep);
if (size == 0)
errors_occured = true;
for (size_t i = 0; i < evaluator.externals_size; ++i) {
const SurrogateExternal* ex = &evaluator.externals[i];
bool found = false;
for (uint16_t chunk_i = 1; chunk_i < size; ++chunk_i) {
if (chunk[chunk_i] == ex->surrogate) {
found = true;
header_builder_add_extern_ref(
header_builder, ex->extern_id, ip + chunk_i);
break;
}
}
assert(found);
}
ip += size;
}
uint16_t program_size = ip;
res = header_builder_resolve_global_syms(header_builder, &resolver, &rep);
if (res != 0) {
errors_occured = true;
}
if (errors_occured) {
fprintf(stderr, "nothing written. stopping...\n");
res = -1;
goto leave_free_chunk;
}
header_builder_print(header_builder);
FILE* output_fp = fopen(args.output_file, "wb");
if (!output_fp) {
REPORTF_ERROR("could not open output file '%s': %s",
args.output_file,
strerror(errno));
res = -1;
goto leave_free_chunk;
}
size_t total_words_written;
res = header_builder_write(
header_builder, output_fp, &total_words_written, args.output_file);
if (res != 0) {
errors_occured = true;
fclose(output_fp);
res = -1;
goto leave_free_chunk;
}
evaluator.second_pass = true;
for (size_t i = 0; i < lines_size; ++i) {
use_labels(&resolver, lines[i]->labels);
size_t size = pline_assemble(&evaluator, chunk, lines[i], &rep);
if (size == 0) {
errors_occured = true;
}
if (errors_occured)
break;
size_t amount_written
= fwrite(chunk, sizeof(uint16_t), size, output_fp);
total_words_written += amount_written;
if (amount_written != size) {
REPORTF_ERROR("could not write to output file '%s': %s",
args.output_file,
strerror(errno));
errors_occured = true;
}
}
fclose(output_fp);
if (errors_occured) {
fprintf(
stderr, "%ld bytes written. stopping...\n", total_words_written);
res = -1;
goto leave_free_chunk;
}
res = 0;
leave_free_chunk:
free(chunk);
// leave_free_lines:
for (size_t i = 0; i < lines_size; ++i)
pline_free(lines[i]);
header_builder_free(header_builder);
free(lines);
free(input_text);
operand_evaluator_destroy(&evaluator);
ident_resolver_destroy(&resolver);
return res;
}
static inline Args parse_args(int argc, char** argv)
{
const char* input_file = NULL;
const char* output_file = NULL;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-o") == 0) {
i += 1;
if (i >= argc) {
REPORTF_ERROR("%s", "no filename given to -o");
exit(1);
}
output_file = argv[i];
} else {
if (input_file != NULL) {
REPORTF_ERROR("%s", "multiple input files specified");
exit(1);
}
input_file = argv[i];
}
}
if (input_file == NULL) {
REPORTF_ERROR("%s", "no input file");
exit(1);
}
if (output_file == NULL) {
output_file = "out.o";
}
return (Args) {
input_file,
output_file,
};
}
static inline int include_file(IdentResolver* resolver,
OperandEvaluator* evaluator,
const char* origin_filename,
const char* include_filename)
{
char* origin_dir_buf = asm_strdup(origin_filename);
const char* origin_dir = dirname(origin_dir_buf);
char* filepath = calloc(
strlen(origin_dir) + strlen(include_filename) + 2, sizeof(char));
strcat(filepath, origin_dir);
free(origin_dir_buf);
strcat(filepath, "/");
strcat(filepath, include_filename);
char* text = read_text_file(filepath);
if (!text)
return -1;
Parser parser;
parser_construct(&parser, filepath, text);
Reporter rep = {
.filename = parser.lexer.filename,
.text = parser.lexer.text,
.text_len = parser.lexer.text_len,
};
bool errors_occured = false;
while (!parser_done(&parser)) {
PStmt* stmt = parser_next_stmt(&parser);
if (!stmt) {
continue;
}
switch (stmt->ty) {
case PStmtTy_Const: {
const IdentResol* existing
= ident_resolver_resolve(resolver, stmt->ident);
if (existing != NULL) {
if (existing->ty == IdentResolTy_Const
&& strcmp(existing->src_filename, filepath) == 0) {
// Same file included multiple times.
pstmt_free(stmt);
continue;
}
report_redefinition(existing, stmt, &rep);
errors_occured = true;
pstmt_free(stmt);
continue;
}
EvaledOperand evaled
= eval_operand_to_imm(evaluator, stmt->value);
if (evaled.ty == EoTy_Err) {
pstmt_free(stmt);
errors_occured = true;
continue;
}
ident_resolver_define_const(resolver,
asm_strdup(stmt->ident),
stmt->loc,
evaled.imm,
asm_strdup(filepath));
break;
}
case PStmtTy_Include: {
int include_res
= include_file(resolver, evaluator, filepath, stmt->str);
pstmt_free(stmt);
if (include_res != 0) {
errors_occured = true;
}
break;
}
default:
break;
}
pstmt_free(stmt);
}
errors_occured |= parser_error_occured(&parser);
free(filepath);
free(text);
return errors_occured;
}
static inline void report_redefinition(
const IdentResol* existing, PStmt* stmt, Reporter* rep)
{
REPORTF_ERROR("redefinition of identifier '%s'", stmt->ident);
reporter_print_loc(rep, stmt->loc);
const char* filename = rep->filename;
if (existing->ty == IdentResolTy_Const) {
filename = existing->src_filename;
}
REPORTF_INFO("previous definition of '%s' here", existing->ident);
reporter_print_loc(rep, existing->loc);
rep->filename = filename;
}
static inline char* read_text_file(const char* filename)
{
FILE* fp = fopen(filename, "r");
if (!fp) {
REPORTF_ERROR("could not open file '%s' for reading: %s",
filename,
strerror(errno));
return NULL;
}
fseek(fp, 0L, SEEK_END);
size_t file_size = (size_t)ftell(fp);
rewind(fp);
char* text = calloc(file_size + 1, sizeof(char));
size_t bytes_read = fread(text, sizeof(char), file_size, fp);
fclose(fp);
if (bytes_read != file_size) {
REPORTF_ERROR(
"could not read input file '%s': %s", filename, strerror(errno));
return NULL;
}
return text;
}
static inline int define_labels(
IdentResolver* resolver, PLabel* label, uint16_t asm_ip, Reporter* rep)
{
if (label == NULL)
return 0;
int res = define_labels(resolver, label->next, asm_ip, rep);
const IdentResol* existing = ident_resolver_resolve(resolver, label->ident);
if (existing != NULL) {
REPORTF_ERROR("redefinition of identifier '%s'", label->ident);
reporter_print_loc(rep, label->loc);
const char* filename = rep->filename;
if (existing->ty == IdentResolTy_Const) {
rep->filename = existing->src_filename;
}
REPORTF_INFO("original definition of '%s'", existing->ident);
reporter_print_loc(rep, existing->loc);
rep->filename = filename;
return 1;
}
if (label->sub_label) {
ident_resolver_define_sublabel(
resolver, asm_strdup(label->ident), label->loc, asm_ip);
} else {
ident_resolver_define_label(
resolver, asm_strdup(label->ident), label->loc, asm_ip);
}
return res;
}
static inline void use_labels(IdentResolver* resolver, PLabel* label)
{
if (label == NULL)
return;
use_labels(resolver, label->next);
const IdentResol* existing = ident_resolver_resolve(resolver, label->ident);
if (existing && existing->ty == IdentResolTy_Label) {
resolver->current_parent = existing;
}
}