diff --git a/Source/Assembler/Assembler.c b/Source/Assembler/Assembler.c index a1d446d..6f3ffb2 100644 --- a/Source/Assembler/Assembler.c +++ b/Source/Assembler/Assembler.c @@ -95,15 +95,28 @@ void assemblerCleanup(intermediateElement *intermediateArray, int arraySize, cha free(outputFileName); } +// EVERY OPTION HERE NAMES A FILE THE ASSEMBLER WRITES, and the source is the bare argument +// at the end. A list of bare flags does not say that: "-S " reads just as naturally as +// "dump the symbols of ", and asking for it that way hands the source to -S, leaves +// nothing positional behind it, and gets answered with "No source file specified" - which +// describes the symptom and hides the cause. So the long names are printed, every line says +// where the file is written, and an example shows a whole command rather than leaving one to +// be assembled out of the parts. void printUsage(const char *programName) { printf("Usage: %s [OPTIONS] \n", programName); printf("\n"); + printf("Assembles one source file. The source is the last argument, on its own; every\n"); + printf(" below is a path the assembler writes, and every one it searches.\n"); + printf("\n"); printf("Options:\n"); - printf(" -o Write the output to this path instead of alongside the source.\n"); - printf(" -I Look in this directory for included files. May be given more than once.\n"); - printf(" -M Write the source files this output depends on, as a make rule.\n"); - printf(" -S Write every label and the address it was given, in address order.\n"); - printf(" -h, --help Display this help message.\n"); + printf(" -o, --output Write the assembled output here, instead of alongside the source.\n"); + printf(" -I, --include Search this directory for included files. May be given more than once.\n"); + printf(" -M, --depend Write a make rule here, naming every source that went into the output.\n"); + printf(" -S, --symbols Write every label here with the address it was given, in address order.\n"); + printf(" -h, --help Display this help message.\n"); + printf("\n"); + printf("Example:\n"); + printf(" %s -I Libraries -S game.sym -o game.sbx game.asm\n", programName); } // Writes a make rule naming every source file that went into the output, so that a @@ -207,6 +220,14 @@ int main(int argc, char *argv[]) { if (optind >= argc) { fprintf(stderr, RED "Error: No source file specified.\n" RESET); + // The commonest way to get here is giving the source to an option that wanted a + // path to write, which consumes it and leaves nothing positional. Saying so is the + // difference between an error that names the mistake and one that names the hole + // the mistake left behind. + if (outputFileName || dependencyFileName || symbolFileName) { + fprintf(stderr, " -o, -M and -S each name a file to WRITE, not one to read.\n" + " The source file is the last argument, on its own.\n"); + } printUsage(argv[0]); return 1; } diff --git a/SplitBit Assembler Manual.md b/SplitBit Assembler Manual.md index be2da3b..e87691f 100644 --- a/SplitBit Assembler Manual.md +++ b/SplitBit Assembler Manual.md @@ -420,8 +420,13 @@ Assembler [options] | -o, --output \ | Write the output to this path. Without it, the output is named after the source file, in the directory the assembler was run from, taking .bin if it is a boot image and .sbx if it is a loadable program. | | -I, --include \ | Look in this directory for included files. May be given more than once, and the directories are searched in the order given. | | -M, --depend \ | Write out which source files went into the output, as a make rule. | +| -S, --symbols \ | Write every label and the address it was given, in address order, to this path. | | -h, --help | Print the options and stop. | +Every option above that takes a \ names a file the assembler WRITES, and the source is the bare argument at the end. So `Assembler -S program.asm` does not dump the symbols of program.asm. It asks for the symbol file to be called program.asm, and then finds it has no source left to assemble. + +-S is for looking at what the assembler decided. Every label in the program, in address order, with the address it ended up at: which segment a name landed in, how far apart two routines really are, and whether the label you are looking for was assembled at all. + The assembler stops at the first error, says which file and line it was in, and exits without writing anything. ## Building With Make: diff --git a/Tests/docs.sh b/Tests/docs.sh index 364aea6..f7d41e6 100755 --- a/Tests/docs.sh +++ b/Tests/docs.sh @@ -172,6 +172,40 @@ else: problems.append("%s (0x%02X) is a device class and has no row in the Devices" " table" % (name, value)) +# ---- Every option the assembler takes is written down in both places ---- +# +# An option added to getopt is an option nobody knows about until it is said twice: in the +# manual's table, and in the usage the assembler prints when it is asked for help or refuses +# a command line. -S arrived with neither, and the damage was worse than an undocumented +# switch. The usage listed a bare "-S " with no long name and no statement that the +# file is one it WRITES, so it read as "dump the symbols of " - which hands the source +# to -S, leaves nothing positional behind it, and gets answered with "No source file +# specified" on a command line that plainly names one. +# +# The option table is the source of truth because it is what getopt_long is actually given. +assembler = read("Source/Assembler/Assembler.c") +options = re.findall(r'^\s*\{"([a-z]+)",\s*\w+,\s*0,\s*\'(\w)\'\s*\},', assembler, re.M) +if not options: + problems.append("could not find the assembler's option table") +elif "void printUsage" not in assembler: + problems.append("the assembler has lost its printUsage") +elif "## Running the Assembler:" not in am: + problems.append("the Assembler Manual has lost its options table") +else: + usage = assembler.split("void printUsage")[1].split("\n}")[0] + section = am.split("## Running the Assembler:")[1].split("\n## ")[0] + # Only the table rows count, so a switch merely mentioned in the prose below it does not + # pass for a documented one. + rows = re.findall(r'^\| (-\w, --[a-z]+)', section, re.M) + for longName, shortName in options: + spelled = "-%s, --%s" % (shortName, longName) + if spelled not in rows: + problems.append("the assembler takes %s and the Assembler Manual's option table" + " has no row for it" % spelled) + if spelled not in usage: + problems.append("the assembler takes %s and its own usage message does not name" + " it" % spelled) + # ---- The vector ranges the manuals quote are the ones the assembler uses ---- # # Both manuals print the boundary between numbers a program may pin and numbers the