; config.asm ; Reading a configuration file. ; ; One setting to a line: a key, a space, and the rest of the line is the value. A semicolon ; starts a comment and a blank line is nothing. That format was not designed so much as ; noticed - textSplit already cuts the first word off a line and leaves the rest, and ; textSame already compares two strings and insists they end together, so reading a setting ; is those two routines and a loop. It is also the shape the shell reads, which means a ; configuration line is a command line the machine reads instead of a person typing one. ; ; ---- Configuration is advice ---- ; ; A file that is not there, a key that is not in it, and a value that makes no sense are ; all the same answer: use the default. Nothing here reports a failure for any of them, ; because a program that cannot run without its configuration has turned its configuration ; into a single point of failure - and for the thing that starts the machine, that would ; mean a mistyped file is a machine that does not start. ; ; ---- But quiet is not the same as silent ---- ; ; A setting that was meant and did not take effect should say so, or the only symptom is ; that the machine did not do what somebody asked it to. So there are TWO routines rather ; than one: cfgGet reads, and never says anything; cfgCheck reads the whole file once and ; reports what it did not understand. ; ; They are separate because they know different things. A malformed line is malformed to ; anybody, so cfgGet's own scan could spot one - but reading three settings would then ; report the same bad line three times. And an unknown KEY is not something this can judge ; at all: only the caller knows which keys mean anything to it, which is why cfgCheck is ; given a table of them. ; ; Written by Anachronaut #Program ; ---- Reading the file in ---- ; ; DP0 names a path, DP1 names a buffer, and A is how many blocks the buffer holds. ; ; Q is zero if there is something to read, INCLUDING WHEN THERE IS NO FILE. A missing ; configuration file is a file with no settings in it, which is a perfectly ordinary thing ; for a disk to have, and the caller wanting the defaults gets them either way. cfgLoad: SETD.2 CfgRoom STA.2 SETD.2 CfgBufferAt STD.1.2 ; Nothing loaded until something is. RSTA SETD.2 CfgLength STA.2 INCD.2 STA.2 CALL sbfsFind BNQ cfgNoFile ; A file too big for the buffer is read as far as it fits rather than refused: the ; settings at the top still work, and the ones past the end are missing keys, which is ; a case every caller already handles. SETD.2 SbfsFileBlocks INCD.2 LDA.2 SETD.2 CfgRoom LDB.2 CCF SUB BNC cfgTooBig SETD.2 CfgBufferAt LDD.1.2 CALL sbfsRead BNQ cfgNoFile ; How many bytes of it are real. A file is whole blocks and then a tail, which is the ; block count in the high byte and the tail in the low one. SETD.2 SbfsFileBlocks INCD.2 LDA.2 SETD.2 CfgLength STA.2 SETD.2 SbfsFileTail LDA.2 SETD.2 CfgLength INCD.2 STA.2 cfgReady: RSTA RSTB CCF ADD RET cfgTooBig: ; As much of it as there is room for, which is every whole block of the buffer. SETD.2 CfgRoom LDA.2 SETD.2 SbfsFileBlocks INCD.2 STA.2 RSTA SETD.2 SbfsFileTail STA.2 SETD.2 CfgBufferAt LDD.1.2 CALL sbfsRead BNQ cfgNoFile SETD.2 CfgRoom LDA.2 SETD.2 CfgLength STA.2 RSTA INCD.2 STA.2 BRI cfgReady cfgNoFile: ; No file, or a disk that would not give it up. Neither is a failure: it is a file with ; nothing in it, and every key will be missing, which is what a default is for. RSTA SETD.2 CfgLength STA.2 INCD.2 STA.2 BRI cfgReady ; ---- Walking it a line at a time ---- ; ; cfgRewind puts the walk back at the top. cfgLine copies the next line into CfgLine and ; leaves Q zero if there was one. ; ; A line longer than the buffer holds is copied as far as it goes and the rest of it is ; skipped, with CfgLong set to say so. Ignoring it is what the format asks for; saying that ; it was ignored is what cfgCheck is for. cfgRewind: SETD.0 CfgBufferAt SETD.1 CfgWalk CALL sbfsCopyWord SETD.0 CfgLength SETD.1 CfgLeft CALL sbfsCopyWord RET cfgLine: RSTA SETD.0 CfgLong STA.0 SETD.0 CfgFill STA.0 SETD.0 CfgLeft LDA.0 INCD.0 LDB.0 OR BRQ cfgLineNone SETD.2 CfgWalk LDD.0.2 SETD.1 CfgLine cfgLineLoop: SETD.2 CfgLeft LDA.2 INCD.2 LDB.2 OR BRQ cfgLineEnd LDA.0 INIB 0x0A CCF SUB BRQ cfgLineBreak ; Room for it, or the line is one of the long ones and only its beginning is kept. SETD.2 CfgFill LDB.2 INIA 0d128 CCF SUB BRQ cfgLineOverflow LDA.0 STA.1 INCD.1 LDA.2 INCA STA.2 cfgLineStep: INCD.0 RCAL cfgSpent BRI cfgLineLoop cfgLineOverflow: INIA 0x01 SETD.2 CfgLong STA.2 BRI cfgLineStep cfgLineBreak: INCD.0 RCAL cfgSpent cfgLineEnd: RSTA STA.1 ; The zero that makes what was copied a string. SETD.2 CfgWalk STD.0.2 RSTA RSTB CCF ADD RET cfgLineNone: RSTA INIB 0d1 CCF ADD RET ; One byte of the file accounted for. cfgSpent: SETD.2 CfgLeft INCD.2 LDA.2 BNA cfgSpentLow SETD.2 CfgLeft LDA.2 DECA STA.2 INCD.2 INIA 0xFF STA.2 RRET cfgSpentLow: DECA STA.2 RRET ; ---- What a line turns out to be ---- ; ; Q is zero if there is a setting on it, and then CfgLine names the key and CfgValue names ; the value. Q is not zero for a blank line or a comment, which are not settings and are ; not mistakes either. cfgParse: RSTA SETD.0 CfgNoValue STA.0 ; A LINE THAT WAS CUT SHORT IS NOT A SETTING. Its key may look perfectly good and its ; value is whatever fitted, so treating it as one would hand back an answer that is wrong ; rather than missing - and a missing setting gets the default, which is the safe thing. ; Reported by cfgCheck, ignored by everything. SETD.0 CfgLong LDA.0 BNA cfgParseNothing SETD.0 CfgLine LDA.0 BRA cfgParseNothing INIB 0x3B ; A semicolon starts a comment. CCF SUB BRQ cfgParseNothing CALL textSplit SETD.0 TextRest SETD.1 CfgValue CALL sbfsCopyWord ; A KEY WITH NOTHING AFTER IT IS NOT A SETTING, and finding that out here rather than in ; each caller is what makes first-match-wins safe. A file with ; ; system ; system /System/Boot/cosmos.bin ; ; in it would otherwise match the first line, hand back an empty value, and the machine ; would try to start a file with no name while a perfectly good setting sat underneath. ; An unusable value is an absent one, which is what C3 says and where it earns its keep. SETD.2 CfgValue LDD.0.2 LDA.0 BRA cfgParseNoValue RSTA RSTB CCF ADD RET cfgParseNoValue: ; Told apart from a blank line, because one of them is a mistake and the other is not. INIA 0x01 SETD.0 CfgNoValue STA.0 cfgParseNothing: RSTA INIB 0d1 CCF ADD RET ; ---- Asking for one setting ---- ; ; DP0 names the key. Q is zero if the file had it, and CfgValue then names the value. ; Says nothing about anything, ever. cfgGet: SETD.1 CfgWanted STD.0.1 CALL cfgRewind cfgGetLoop: CALL cfgLine BNQ cfgGetMissing CALL cfgParse BNQ cfgGetLoop SETD.0 CfgLine SETD.2 CfgWanted LDD.1.2 CALL textSame BNQ cfgGetLoop RSTA RSTB CCF ADD RET cfgGetMissing: RSTA INIB 0d1 CCF ADD RET ; ---- Saying what was not understood ---- ; ; DP0 names a table of the keys the caller knows: strings one after another, ended by an ; empty one. Every line is looked at once, and anything that will not take effect is ; reported with enough of itself to be found and fixed. ; ; Nothing here changes what any setting does. The defaults have already been used by the ; time this runs, or will be; this exists so that a setting somebody meant, which did not ; happen, does not do so in silence. cfgCheck: SETD.1 CfgKnown STD.0.1 CALL cfgRewind cfgCheckLoop: CALL cfgLine BNQ cfgCheckDone SETD.0 CfgLong LDA.0 BRA cfgCheckParse SETD.0 CfgLongText RCAL cfgSay SETD.0 CfgLine RCAL cfgSay RCAL cfgNewLine BRI cfgCheckLoop ; Said once. What its key looks like is not worth a second remark. cfgCheckParse: CALL cfgParse BRQ cfgCheckKnown ; Not a setting. A blank line or a comment is not a mistake and gets no remark; a key ; somebody started and did not finish is, and gets one. SETD.0 CfgNoValue LDA.0 BRA cfgCheckLoop SETD.0 CfgEmptyText RCAL cfgSay SETD.0 CfgLine RCAL cfgSay RCAL cfgNewLine BRI cfgCheckLoop cfgCheckKnown: SETD.2 CfgKnown LDD.1.2 cfgCheckNext: LDA.1 BRA cfgCheckUnknown ; The empty name that ends the table. SETD.0 CfgLine CALL textSame BRQ cfgCheckLoop ; A key this caller knows, so there is nothing to say. ; Past this name and on to the next one. cfgCheckSkip: LDA.1 BRA cfgCheckSkipped INCD.1 BRI cfgCheckSkip cfgCheckSkipped: INCD.1 BRI cfgCheckNext cfgCheckUnknown: SETD.0 CfgUnknownText RCAL cfgSay SETD.0 CfgLine RCAL cfgSay RCAL cfgNewLine BRI cfgCheckLoop cfgCheckDone: RET ; ---- Saying things ---- ; ; Its own rather than the console's, because the first thing to read a configuration file ; is the boot loader and everything it uses has to fit in a boot slot. cfgSay: LDA.0 BRA cfgSaid OUTA 0x00 INCD.0 BRI cfgSay cfgSaid: RRET cfgNewLine: INIA 0x0A OUTA 0x00 RRET #Data CfgLongText: "a line too long to read: " CfgEmptyText: "a setting with no value: " CfgUnknownText: "a setting nothing asked for: " CfgBufferAt: 0x00 0x00 CfgRoom: 0x00 CfgLength: 0x00 0x00 CfgWalk: 0x00 0x00 CfgLeft: 0x00 0x00 CfgFill: 0x00 CfgLong: 0x00 CfgNoValue: 0x00 CfgWanted: 0x00 0x00 CfgKnown: 0x00 0x00 CfgValue: 0x00 0x00 ; One line, and the number is the one the format says: a key of twenty two and a path of ; the length a path is allowed to be leaves room to spare in a hundred and twenty eight. CfgLine: #Reserve 0d129