453 lines
13 KiB
C
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;
|
|
}
|
|
}
|