久しぶりに、Modula-2でプログラミング --その4-- CmdArgsの修正2023年01月30日 15:50

先に公開した"CmdArgs"モジュールに危ないところがありました。エクスポートしている変数"Argc"は、 インポートしたモジュールで変更できます。変えられてしまうと、"CmdArgs"モジュールが誤作動します。これを さけるために、引数の数を手続き"Argc()"で返すようにしました。 これで、不用意に"CmdArgs"モジュールにダメージを 与えることがなくなりました。

修正版の定義モジュール"CmdArgs.def"は、以下の通り。

DEFINITION MODULE CmdArgs;

EXPORT QUALIFIED Argv,Argc;

PROCEDURE Argc(): CARDINAL;
PROCEDURE Argv(n: CARDINAL;VAR ArgString: ARRAY OF CHAR);

END CmdArgs.

修正版の実装モジュール"CmdArgs.mod"は、以下の通り。

IMPLEMENTATION MODULE CmdArgs;
FROM ASCII IMPORT nul,ht,lf,cr;
FROM SYSTEM IMPORT ADR,WORD;
FROM OpSys IMPORT BdosFunctions,Bdos,CPMStringBuffer;

CONST
        SPACE = ' ';
        TAB = ht;
        COMMA = ',';
        SQUORT = "'";
        DQUORT = '"';
        EOS = nul;
        MAXARGS = 20;
        EMPTY = -1;

VAR
        ArgList: ARRAY [0..MAXARGS-1] OF INTEGER;
        CommandLine: CPMStringBuffer;
        QChar: CHAR;
        c,s: CARDINAL;
        CommaFlag: BOOLEAN;
        nArgc: CARDINAL;
        Msg: ARRAY [0..80] OF CHAR;

PROCEDURE Argc(): CARDINAL;
BEGIN
        RETURN nArgc;
END Argc;

PROCEDURE Argv(n: CARDINAL;VAR ArgString: ARRAY OF CHAR);
VAR
        s,d: CARDINAL;
BEGIN
        IF nArgc < n THEN
                ArgString[0] := EOS;
        ELSIF ArgList[n] = EMPTY THEN
                ArgString[0] := EOS;
        ELSE
                s := ArgList[n];
                d := 0;
                WHILE CommandLine.text[s] # EOS DO
                        ArgString[d] := CommandLine.text[s];
                        INC(s);INC(d);
                END;
                ArgString[d] := EOS;
        END;
END Argv;

PROCEDURE skipBlanks();
BEGIN
        WHILE isBlankChar(CommandLine.text[c]) DO
                INC(c);
        END;
END skipBlanks;

PROCEDURE skipSeparator();
BEGIN
        WHILE isSeparator(CommandLine.text[c]) DO
                INC(c);
        END;
END skipSeparator;

PROCEDURE isBlankChar(ch: CHAR): BOOLEAN;
BEGIN
        IF (ch = SPACE) OR (ch = TAB) THEN
                RETURN TRUE;
        END;
        RETURN FALSE;
END isBlankChar;

PROCEDURE nextQuort();
BEGIN
        WHILE (CommandLine.text[c] # EOS)
                AND (CommandLine.text[c] # QChar) DO
                INC(c);
        END;
END nextQuort;

PROCEDURE nextSeparator();
BEGIN
        WHILE (CommandLine.text[c] # EOS)
                AND (NOT isSeparator(CommandLine.text[c])) DO
                INC(c);
        END;
END nextSeparator;

PROCEDURE getOneArg(): CARDINAL;
VAR
        s: CARDINAL;
BEGIN
        IF CommandLine.text[c] # EOS THEN
                IF isQuort(CommandLine.text[c]) THEN
                        QChar := CommandLine.text[c];
                        INC(c);
                        s := c;
                        nextQuort();
                ELSE
                        s := c;
                        nextSeparator();
                END;
                CommaFlag := FALSE;
                IF CommandLine.text[c] # EOS THEN
                        IF CommandLine.text[c] # COMMA THEN
                                CommandLine.text[c] := EOS;
                                INC(c);
                                skipBlanks();
                        END;
                        IF CommandLine.text[c] = COMMA THEN
                                CommaFlag := TRUE;
                                CommandLine.text[c] := EOS;
                                INC(c);
                        END;
                END;
        ELSE
                s := 0;
        END;
        RETURN s;
END getOneArg;

PROCEDURE isQuort(ch: CHAR): BOOLEAN;
BEGIN
        IF (ch = SQUORT) OR (ch = DQUORT) THEN
                RETURN TRUE;
        END;
        RETURN FALSE;
END isQuort;

PROCEDURE isSeparator(ch: CHAR): BOOLEAN;
BEGIN
        CASE ch OF
        SPACE: RETURN TRUE;
        | TAB: RETURN TRUE;
        | COMMA: RETURN TRUE;
        ELSE
                RETURN FALSE;
        END;
END isSeparator;

PROCEDURE getCmdLine();
VAR
        BDOSCmd: RECORD
                CASE BOOLEAN OF
                TRUE: Func: BdosFunctions;
                | FALSE: Cmd: WORD;
                END;
        END;
        junk: WORD;
BEGIN
        BDOSCmd.Func := prtStr;
        Msg := 'arguments>$';
        Bdos(BDOSCmd.Cmd,ADR(Msg),junk);

        CommandLine.maxLen := CHR(255);
        CommandLine.curLen := CHR(0);
        BDOSCmd.Func := rdCBuf;
        Bdos(BDOSCmd.Cmd,ADR(CommandLine),junk);
        CommandLine.text[ORD(CommandLine.curLen)] := EOS;

        BDOSCmd.Func := prtStr;
        Msg[0] := cr; Msg[1] := lf; Msg[2] := '$';
        Bdos(BDOSCmd.Cmd,ADR(Msg),junk);

        FOR nArgc := 0 TO MAXARGS-1 DO
                ArgList[nArgc] := EMPTY;
        END;

        nArgc := 0;
        c := 0;
        CommaFlag := FALSE;
        skipBlanks();
        WHILE (CommandLine.text[c] # EOS) AND (nArgc < MAXARGS) DO
                ArgList[nArgc] := getOneArg();
                skipBlanks();
        INC(nArgc);
        END;
        IF (CommandLine.text[c] = EOS) AND CommaFlag THEN
                ArgList[nArgc] := c;
                INC(nArgc);
        END;
END getCmdLine;

BEGIN
        getCmdLine();
END CmdArgs.

この修正にあわせて、"Find"モジュールの変更をしました。