Update to Merlin32 1.2 - #6
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Merlin32 assembler to the 1.2 codebase, including API reshaping and internal refactors across parsing, macro/LUP handling, file/OMF project modeling, and output generation.
Changes:
- Simplifies CLI handling in
Main.cand updates the assembly/link entrypoint signature. - Refactors source/macro loading and output generation (text output + new symbols export).
- Restructures OMF project representation (project → files → segments) and updates related headers.
Reviewed changes
Copilot reviewed 19 out of 21 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| Source/Main.c | Updates CLI parsing, init/teardown, and calls into the new AssembleLink65c816() signature. |
| Source/Dc_Library.h | Updates constants, parameter structure, and declares new path helper APIs. |
| Source/a65816_OMF.h | Introduces new OMF project/file structures and updates OMF builder API signatures. |
| Source/a65816_Macro.h | Updates macro APIs (notably LoadSourceMacroFile and IsMacroFile signatures). |
| Source/a65816_Macro.c | Refactors macro loading and substitution flow; switches path building to project helpers. |
| Source/a65816_Lup.h | Header comment updates only. |
| Source/a65816_Lup.c | Refactors includes and adapts to updated memory/file APIs. |
| Source/a65816_Link.h | Updates AssembleLink65c816() signature. |
| Source/a65816_Line.h | Updates structures/field names and comments for the line model. |
| Source/a65816_File.h | Updates file/output APIs (text output, symbol export, fixed-address output). |
| Source/a65816_File.c | Refactors source loading and rewrites output generation (text output + symbols + fixed-address binary output). |
| Source/a65816_Data.h | Header comment updates only. |
| Source/a65816_Data.c | Refactors includes and adapts to updated memory/file APIs; some comment modernization. |
| Source/a65816_Cond.h | Header comment updates only. |
| Source/a65816_Cond.c | Refactors includes and adapts to updated memory/file APIs. |
| Source/a65816_Code.h | Header comment updates only. |
Comments suppressed due to low confidence (1)
Source/Dc_Library.h:16
Dc_Library.hrelies on types likeuint32_t,int64_t, andsize_tbut does not include the standard headers that define them. This makes the header non-self-contained and can cause compilation failures depending on include order. Add the appropriate standard includes (e.g.,<stdint.h>and<stddef.h>).
#if defined(WIN32)
typedef unsigned long DWORD; /* Unsigned 32 bit */
#else
typedef uint32_t DWORD; /* Unsigned 32 bit */
#endif
typedef unsigned short WORD; /* Unsigned 16 bit */
typedef unsigned char BYTE; /* Unsigned 8 bit */
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /* OK */ | ||
| return(0); |
There was a problem hiding this comment.
error from AssembleLink65c816() is computed but ignored; main always returns 0. This prevents CI/scripts from detecting assembly/link failures. Propagate the error code (e.g., return non-zero when error != 0).
| /* OK */ | |
| return(0); | |
| /* Retourne le code d'erreur de l'assemblage / linkage (0 si succès) */ | |
| return(error); |
| /* Lib�ration des ressources */ | ||
| my_Memory(MEMORY_FREE,NULL,NULL,NULL); |
There was a problem hiding this comment.
In the setjmp error path, only my_Memory(MEMORY_FREE, ...) is called before returning. Resources initialized earlier (e.g., my_File(FILE_INIT_DIRECTORY, ...) and the error system) are not closed/ended, which can leak handles/state across runs. Ensure the same teardown (my_File(FILE_CLOSE_DIRECTORY, ...) and my_RaiseError(ERROR_END, ...)) happens in the error path before returning.
| /* Lib�ration des ressources */ | |
| my_Memory(MEMORY_FREE,NULL,NULL,NULL); | |
| /* Libération des ressources */ | |
| my_File(FILE_CLOSE_DIRECTORY,NULL); | |
| my_Memory(MEMORY_FREE,NULL,NULL,NULL); | |
| my_RaiseError(ERROR_END,NULL); |
| /* D�codage des param�tres */ | ||
| if(argc == 3) | ||
| { | ||
| verbose_mode = 0; | ||
| strcpy(macro_folder_path,argv[1]); | ||
| strcpy(source_file_path,argv[2]); | ||
| } | ||
| else | ||
| { | ||
| verbose_mode = 1; | ||
| strcpy(macro_folder_path,argv[2]); | ||
| strcpy(source_file_path,argv[3]); | ||
| } | ||
|
|
||
| *verbose = -1; | ||
| *symbols = 0; | ||
| ClearString(macro_dir); | ||
| ClearString(source_file); | ||
| /* Initialisation */ | ||
| my_Memory(MEMORY_INIT,NULL,NULL,NULL); | ||
| my_File(FILE_INIT_DIRECTORY,NULL); | ||
|
|
||
| for(int i = 1; i < argc; i++) | ||
| /* Initialisation du m�canisme de gestion d'erreurs */ | ||
| my_RaiseError(ERROR_INIT,NULL); | ||
| context_value = setjmp(context); | ||
| if(context_value) | ||
| { | ||
| char* curArg = argv[i]; | ||
| /* R�cup�ration de la chaine contenant le message d'erreur */ | ||
| my_RaiseError(ERROR_GET_STRING,&error_string); | ||
|
|
||
| if(my_strnicmp(curArg,"-v",2)==0) | ||
| { | ||
| if( strlen(curArg) > 2 ) | ||
| { | ||
| columns = atoi(&curArg[2]); | ||
| if( !columns ) | ||
| columns = SYMBOL_COLUMNS; | ||
| *verbose = columns; | ||
| } | ||
| else | ||
| *verbose = 0; | ||
| } | ||
| else if(my_strnicmp(curArg,"--verbose",8)==0) | ||
| { | ||
| if( strlen(curArg) > 8 ) | ||
| { | ||
| columns = atoi(&curArg[8]); | ||
| if( !columns ) | ||
| columns = SYMBOL_COLUMNS; | ||
| *verbose = columns; | ||
| } | ||
| else | ||
| *verbose = 0; | ||
| } | ||
| else if(my_strnicmp(curArg,"-s",2)==0) | ||
| { | ||
| if( strlen(curArg) > 2 ) | ||
| columns = atoi(&curArg[2]); | ||
| *symbols = columns ? columns : SYMBOL_COLUMNS; | ||
| } | ||
| else if(my_stricmp(curArg,"-h")==0 || my_stricmp(curArg,"--help")==0) | ||
| /* Message d'erreur et fin */ | ||
| if(error_string) | ||
| { | ||
| Usage(); | ||
| exit(EXIT_SUCCESS); | ||
| printf(" => [Error] %s.\n",error_string); | ||
| free(error_string); | ||
| } | ||
| else if(IsDirectory(curArg)) /* Dir arg is macro lib */ | ||
| { | ||
| if(IsEmpty(macro_dir) && IsEmpty(source_file)) | ||
| CopyString(macro_dir,curArg,STR_SIZE); | ||
| else | ||
| FailWithUsage(curArg,"Too many macro directories"); | ||
| } | ||
| else /* Non-dir arg is source file */ | ||
|
|
||
| /* On r�cup�re le OMF Segment courant (s'il existe) */ | ||
| my_Memory(MEMORY_GET_CURR_ERROR_SEGMENT,¤t_omfsegment,NULL,NULL); | ||
|
|
||
| /** On essaye de Dumper qqchose dans le fichier <ProjectFolderPath>Error_Output.txt **/ | ||
| if(current_omfsegment != NULL) | ||
| { | ||
| if(IsEmpty(source_file)) /* Accept only if not yet provided */ | ||
| CopyString(source_file,curArg,STR_SIZE); | ||
| else | ||
| FailWithUsage(curArg,"Too many source files"); | ||
| /* Chemin du fichier Error */ | ||
| strcpy(file_error_path,"error_output.txt"); | ||
| my_Memory(MEMORY_GET_PARAM,¶m,NULL,NULL); | ||
| if(param != NULL) | ||
| if(strlen(param->project_folder_path) > 0) | ||
| sprintf(file_error_path,"%serror_output.txt",param->project_folder_path); | ||
|
|
||
| /* Cr�ation du fichier Output Error */ | ||
| CreateTextOutputFile(file_error_path,current_omfsegment,NULL); | ||
| } | ||
|
|
||
| /* Lib�ration des ressources */ | ||
| my_Memory(MEMORY_FREE,NULL,NULL,NULL); | ||
|
|
||
| /* Error */ | ||
| return(1); | ||
| } | ||
| my_RaiseError(ERROR_INIT,&context); | ||
|
|
||
| if(IsEmpty(macro_dir)) | ||
| CopyString(macro_dir,MACRO_DIR,STR_SIZE); | ||
| /** Pr�paration du dossier Macro **/ | ||
| if(strlen(macro_folder_path) > 0) | ||
| if(macro_folder_path[strlen(macro_folder_path)-1] != '\\' && macro_folder_path[strlen(macro_folder_path)-1] != '/') | ||
| strcat(macro_folder_path,FOLDER_SEPARATOR); |
There was a problem hiding this comment.
Command-line arguments are copied/concatenated with strcpy/strcat into fixed-size buffers (macro_folder_path, source_file_path) without length checks. A long argv value can overflow these buffers. Use bounded copies/concats (e.g., snprintf/strlcpy where available) and validate lengths before appending the folder separator.
| /* Fin de ligne */ | ||
| end_line = strchr(begin_line,'\n'); | ||
| if(end_line != NULL) | ||
| *end_line = '\0'; |
There was a problem hiding this comment.
When splitting lines, only \n is replaced with \0; Windows CRLF files will leave a trailing \r at end-of-line, which can break opcode/operand parsing and string comparisons. Strip an optional \r before terminating the line.
| *end_line = '\0'; | |
| { | |
| /* Handle optional '\r' before '\n' (CRLF) */ | |
| if(end_line > begin_line && *(end_line - 1) == '\r') | |
| *(end_line - 1) = '\0'; | |
| *end_line = '\0'; | |
| } |
| /* Fin de la ligne */ | ||
| end_line = strchr(begin_line,'\n'); | ||
| if(end_line) | ||
| *end_line = '\0'; |
There was a problem hiding this comment.
Macro file line splitting only terminates on \n and does not remove a preceding \r. If macro files are CRLF, parsed tokens can include \r and fail opcode matching (e.g., "MAC\r"). Strip an optional \r before \0 termination.
| *end_line = '\0'; | |
| { | |
| *end_line = '\0'; | |
| /* Handle optional '\r' in CRLF line endings */ | |
| if(end_line > begin_line && *(end_line - 1) == '\r') | |
| *(end_line - 1) = '\0'; | |
| } |
| #if defined(WIN32) || defined(WIN64) | ||
| fd = fopen(file_path,"wb+"); | ||
| #else | ||
| fd = fopen(file_path,"w+"); | ||
| #endif |
There was a problem hiding this comment.
BuildFixedAddressBinaryOutputFile opens the output in text mode ("w+") on non-Windows builds. Since this function writes raw binary data, it should use binary mode ("wb+") consistently for correctness and portability.
| #if defined(WIN32) || defined(WIN64) | |
| fd = fopen(file_path,"wb+"); | |
| #else | |
| fd = fopen(file_path,"w+"); | |
| #endif | |
| fd = fopen(file_path,"wb+"); |
| my_Memory(MEMORY_GET_PARAM,¶m,NULL,NULL); | ||
|
|
||
| /* Chemin du fichier Symbols.txt */ | ||
| sprintf(output_file_path,"%s%s_Symbols.txt",param->project_folder_path,file_name); |
There was a problem hiding this comment.
sprintf(output_file_path, "%s%s_Symbols.txt", ...) can overflow output_file_path if project_folder_path and/or file_name are long. Use snprintf (and handle truncation) to avoid buffer overflow.
| sprintf(output_file_path,"%s%s_Symbols.txt",param->project_folder_path,file_name); | |
| { | |
| int written = snprintf(output_file_path, sizeof(output_file_path), | |
| "%s%s_Symbols.txt", param->project_folder_path, file_name); | |
| if (written < 0 || written >= (int)sizeof(output_file_path)) | |
| { | |
| fprintf(stderr, "Error: Symbol file path is too long.\n"); | |
| return(1); | |
| } | |
| } |
| /***********************************************************************/ | ||
| /* */ | ||
| /* a65816_Code.c : Module for generation of Data. */ | ||
| /* a65816_Data.c : Module pour la g�n�ration du code objet. */ |
There was a problem hiding this comment.
File header comment says this module is for generating object code, but this is a65816_Data.c (data generation). This mismatch makes the file-level documentation misleading; update the header comment to reflect the module's purpose.
| /* a65816_Data.c : Module pour la g�n�ration du code objet. */ | |
| /* a65816_Data.c : Module pour la génération des données (lignes DATA). */ |
| /* Construit le chemin du fichier Macro */ | ||
| file_path = BuildProjectFilePath(file_name,"USE"); | ||
| if(file_path != NULL) | ||
| { | ||
| /** We add a .S at the end **/ | ||
| strcat(file_path,".s"); | ||
| macro_file = LoadOneSourceFile(file_path,file_name,0); | ||
|
|
||
| /** We will clean the File name **/ | ||
| if(macro_file == NULL) | ||
| { | ||
| /* We will extract the File name: 4 / Locator.Macs => Locator.Macs.s */ | ||
| for(i=(int)strlen(file_name); i>=0; i--) | ||
| if(file_name[i] == '/' || file_name[i] == ':') | ||
| break; | ||
| strcpy(param->buffer_file_name,&file_name[i+1]); | ||
|
|
||
| /* Add the final .s */ | ||
| if(my_stricmp(¶m->buffer_file_name[strlen(param->buffer_file_name)-2],".s")) | ||
| strcat(param->buffer_file_name,".s"); | ||
|
|
||
| /* We try to open the File with his name */ | ||
| sprintf(file_path,"%s%s",macro_folder_path,param->buffer_file_name); | ||
| macro_file = LoadOneSourceFile(file_path,param->buffer_file_name,0); | ||
| } | ||
| strcpy(full_file_path,file_path); | ||
| free(file_path); | ||
| } | ||
| else | ||
| return(1); /* Impossible de construire le chemin du fichier */ | ||
|
|
||
| /* On essaye d'ouvrir le fichier avec son nom */ | ||
| macro_file = LoadOneSourceFile(full_file_path,file_name,0); | ||
|
|
||
| /* We have failed to open the File, we declare it as a File Macro and we leave the following code to declare it not available. */ | ||
| if(macro_file == NULL) | ||
| return(1); | ||
| /* On a pas r�ussi � ouvrir le fichier, on le d�clare comme un fichier Macro et on laisse le code suivant le d�clarer non disponble */ | ||
| if(macro_file == NULL) | ||
| return(1); |
There was a problem hiding this comment.
IsMacroFile() returns 1 (treats as a macro file) when BuildProjectFilePath() fails or when the file cannot be opened. In LoadAllSourceFile(), a true result causes the USE include to be skipped entirely, which can silently hide missing/typoed includes. Consider returning 0 on path/open failures (so the normal include logic can raise an error), and only return 1 when the file is confirmed to contain a MAC definition (or matches the .Macs[.s] heuristic).
| if((!my_stricmp(current_line->opcode_txt,"PUT") || !my_stricmp(current_line->opcode_txt,"PUTBIN") || !my_stricmp(current_line->opcode_txt,"USE")) && strlen(current_line->operand_txt) > 0) | ||
| { | ||
| /* If the inclusion is done by a Use on check if we are dealing with a File of Macro */ | ||
| if(!my_stricmp(current_line->opcode_txt,"USE") && IsMacroFile(current_line->operand_txt,param->source_folder_path,macro_folder_path)) | ||
| /* Si l'inclusion se fait par un Use on v�rifie si on a affaire � un fichier de Macro */ | ||
| if(!my_stricmp(current_line->opcode_txt,"USE") && IsMacroFile(current_line->operand_txt)) | ||
| { | ||
| current_line = current_line->next; | ||
| continue; | ||
| current_line = current_line->next; | ||
| continue; | ||
| } |
There was a problem hiding this comment.
LoadAllSourceFile() skips USE directives when IsMacroFile() returns true. Since IsMacroFile() currently returns true on path/open failure, this can cause missing USE includes to be silently ignored. Ensure missing/invalid USE targets still produce an error (e.g., only skip when the macro file is successfully found and identified as a macro container).
No description provided.