久しぶりに、Modula-2でプログラミング ― 2023年01月18日 06:46
CP/Mは、よくできたDOSですが、いくつか不満があります。そのひとつに、コマンド行パラメーターが
すべて大文字になってしまうのです。これでは、findやsedのような文字列を検索したり、置換したりする
アプリケーションで小文字を入力できないので、困ってしまいます。CP/Mが盛んに使われた時代を考えると
仕方のないことですが、何とかしたいものです。
入力されたコマンド行パラメーター入力をすべて小文字にするのは乱暴すぎます。折衷案として
パラメータの入力を促し読みとる仕組みを作ることにしました。ついでに、少し賢い引数分解の仕組みを
組み込みました。
作成した仕組み"CmdArgs"は以下のルールで、引数を分解します。
- 引数は、区切り文字(コンマ、スペース、タブ)で区切られる。
- 最初のコンマの前が、スペース、タブである場合は、第一番目の引数は、ヌル文字列とみなす。
- 最後の文字がコンマの場合は、最後に、ヌル文字列があるものとみなす。
- 引数は、クオート文字('、または、")でくくることができる。 コンマ、スペース、タブを含むことができる。
- クオート文字で始まる引数は、行末までにクオート文字が出現しない場合、行末末までのすべての文字を 含む。
プログラムは、"MODULA-2 Compiler for Z80-CP/M Version 2.01"で作成しました。
まずは、テストプログラム"TstArgs.mod"で、実際に引き数の入力や分解をみてください。
C0#tstargs arguments>one , two three "ONE TWO ", ' THREE FOUR' four Argc:= 6 0[one] 1[two] 2[three] 3[ONE TWO ] 4[ THREE FOUR] 5[four]
C0#tstargs arguments> , one two three , <---カンマ直後に改行 Argc:= 5 0[] 1[one] 2[two] 3[three] 4[]
C0#tstargs arguments> one two 'Three,Four Five <--- Fiveの後ろにはスペースがある Argc:= 3 0[one] 1[two] 2[Three,Four Five ]
テストプログラム"TstArgs.mod"は、このようになります。
MODULE TstArgs;
FROM InOut IMPORT Write,WriteInt,WriteLn,WriteString;
FROM CmdArgs IMPORT Argc,Argv; <--- コマンド行の引数を処理するモジュールを取り込む
CONST
MAXSTRINGLENGTH = 80;
VAR
Arg: ARRAY [0..MAXSTRINGLENGTH] OF CHAR;
nArgs: CARDINAL;
BEGIN
WriteString('Argc:=');WriteInt(Argc,3);WriteLn;
nArgs := 0;
WHILE nArgs < Argc DO
WriteInt(nArgs,3);
Write('[');
Argv(nArgs, Arg);
WriteString(Arg);
Write(']');WriteLn;
INC(nArgs);
END;
END TstArgs.
コマンド行を処理するモジュール"CmdArgs.mod"については、分量があるので、次回に公開します。
久しぶりに、Modula-2でプログラミング--その2 CmdLineモジュールの紹介-- ― 2023年01月23日 20:58
コマンドライン・パラメーターを読みとり、分解する仕組みを実現したモジュールを公開します。 Modula-2で作成しました。Modula-2では、コンパイル単位を「モジュール」と呼びます。モジュールは3種類あり、 「定義モジュール(DEFINITION MODULE)」、「実現モジュール(IMPLEMENTATION MODULE)」、 「モジュール(MODULE") です。
「定義モジュール」は、モジュールのインターフェイスを記述したもので、他のモジュールに公開する定数や手続きを記述します。
「実現モジュール」は、名前の通り、モジュールの機能を書き下したプログラムです。
「モジュール」は、いわゆるメインプログラムです。
まずは、定義モジュール"CmdArgs.def"です。
DEFINITION MODULE CmdArgs;
EXPORT QUALIFIED Argv,Argc;
VAR
Argc: CARDINAL;
PROCEDURE Argv(n: CARDINAL;VAR ArgString: ARRAY OF CHAR);
END CmdArgs.
モジュール"CmdArgs"が提供(EXPORT)する手続き"Argv"と変数"Argc"を定義しています。
これを事前コンパイルしておき、このモジュールを利用するモジュールをコンパイルする際に読み込まれ、
妥当性のチェックに使われます。
おつぎは、実現モジュール"CmdArgs.mod"です。
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;
(* Argc: CARDINAL; defined in CmdArgs.Def *)
Msg: ARRAY [0..80] OF CHAR;
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 Argv(n: CARDINAL;VAR ArgString: ARRAY OF CHAR);
VAR
s,d: CARDINAL;
BEGIN
IF Argc < 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 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 Argc := 0 TO MAXARGS-1 DO
ArgList[Argc] := EMPTY;
END;
Argc := 0;
c := 0;
CommaFlag := FALSE;
skipBlanks();
WHILE (CommandLine.text[c] # EOS) AND (Argc < MAXARGS) DO
ArgList[Argc] := getOneArg();
skipBlanks();
INC(Argc);
END;
IF (CommandLine.text[c] = EOS) AND CommaFlag THEN
ArgList[Argc] := c;
INC(Argc);
END;
END getCmdLine;
BEGIN
getCmdLine();
END CmdArgs.
手続き"getCmdLine()"は、このモジュールが初期化されるときに動きます。"getCmdLine()"は、プロンプト "arguments>"を表示し、引数を読みとります。その後、前回示したルールに従って引数を分解します。
変数"Argc"は、分解後の引数の数です。
手続き"Argv(n,Param)"は、"n"番目の引数(nilで終わる文字列)を "Param"にコピーして返します。 存在しない引数の時は、"Param"は、長さ0の文字列になります。第一引数は引数は、"n=0"です。
何の変哲のない下請け手続きで書き下しましたので、ロジックを簡単に追えると思います。
次回は、"CmdArgs"モジュールを使用した例を紹介します。
久しぶりに、Modula-2でプログラミング -- その3 -- "CmdLine.mod"モジュールの応用"Find.mod"の紹介 ― 2023年01月26日 11:18
先に公開しました"CmdLine.mod"モジュールを使った応用を紹介します。
応用モジュール"Find.mod"は、指定された文字列をファイル中から探し表示します。このような感じです。
文字列"WriteLn"をファイル"find.mod"、"cmdargs.def"、"tstargs.mod"から探して表示します。
C0#find
arguments>WriteLn find.mod cmdargs.def tstargs.mod
find.mod[3]FROM InOut IMPORT Write,WriteCard,WriteString,WriteLn;
find.mod[35] WriteLn;
find.mod[87] WriteString('find');WriteLn;
find.mod[88] WriteString(' TargetString File1 File2 FIle3,,,,');WriteLn;
tstargs.mod[2]FROM InOut IMPORT Write,WriteInt,WriteLn,WriteString;
tstargs.mod[13] WriteString('Argc:=');WriteInt(Argc,3);WriteLn;
tstargs.mod[20] Write(']');WriteLn;
このように、ファイル名、出現した行番号、出現した行を表示します。"CmdArgs.def"には、該当する文字列がありませんでした。
では、"Find.mod"モジュールの内容です。文字列の検索は"FindTarget()"手続きでします。
ストレートに書き下しましたので、ロジックを追えることと思います。
Find.mod
MODULE Find;
FROM CmdArgs IMPORT Argc,Argv;
FROM InOut IMPORT Write,WriteCard,WriteString,WriteLn;
FROM ASCII IMPORT nul,lf;
FROM Files IMPORT FILE,FileState,Open,Close,Read;
FROM OpSys IMPORT Bdos,BdosFunctions;
FROM SYSTEM IMPORT WORD;
CONST
EOS = nul;
MAXLINE = 128;
FILENAMESIZE = 15;
EOFMARK = 32C;
VAR
FileName: ARRAY [0..FILENAMESIZE] OF CHAR;
Target: ARRAY [0..MAXLINE] OF CHAR;
LineBuffer: ARRAY [0..MAXLINE] OF CHAR;
FileNumber,LineNumber: CARDINAL;
inFile: FILE;
EndOfFile: BOOLEAN;
fs: FileState;
BdosCmd: RECORD
CASE BOOLEAN OF
TRUE: Func: BdosFunctions;
| FALSE: Cmd: WORD;
END;
END;
PROCEDURE putLine(Name:ARRAY OF CHAR;Line:CARDINAL;Buffer:ARRAY OF CHAR);
BEGIN
WriteString(Name);
Write('[');WriteCard(Line,1);Write(']');
WriteString(Buffer);
WriteLn;
END putLine;
PROCEDURE ReBoot();
VAR
junk: WORD;
BEGIN
BdosCmd.Func := boot;
Bdos(BdosCmd.Cmd,junk,junk);
END ReBoot;
PROCEDURE findTarget(Target,Line:ARRAY OF CHAR): BOOLEAN;
VAR
t,l: CARDINAL;
found: BOOLEAN;
BEGIN
found := FALSE;
t := 0; l := 0;
WHILE (Line[l] # EOS) AND (NOT found) DO
IF Line[l] = Target[0] THEN
t := 1;
WHILE Line[l+t] = Target[t] DO
INC(t);
END;
IF Target[t] = EOS THEN
found := TRUE;
END;
END;
INC(l);
END;
RETURN found;
END findTarget;
PROCEDURE ReadLine(f:FILE;VAR Line:ARRAY OF CHAR);
VAR
c: CARDINAL;
BEGIN
c := 0;
Read(f,Line[c]);
WHILE (Line[c] # lf) AND (NOT EndOfFile) DO
IF Line[c] = EOFMARK THEN
EndOfFile := TRUE;
ELSE
INC(c);
Read(f,Line[c]);
END;
END;
Line[c] := EOS;
END ReadLine;
PROCEDURE Usage();
BEGIN
WriteString('find');WriteLn;
WriteString(' TargetString File1 File2 FIle3,,,,');WriteLn;
END Usage;
BEGIN
IF Argc < 1 THEN
Usage();
ReBoot();
END;
Argv(0,Target);
FileNumber := 1;
WHILE FileNumber < Argc DO
Argv(FileNumber,FileName);
fs := Open(inFile,FileName);
IF fs = FileOK THEN
EndOfFile := FALSE;
LineNumber := 1;
WHILE NOT EndOfFile DO
ReadLine(inFile,LineBuffer);
IF findTarget(Target,LineBuffer) THEN
putLine(FileName,LineNumber,
LineBuffer);
END;
INC(LineNumber);
END;
fs := Close(inFile);
END;
INC(FileNumber);
END;
END Find.
久しぶりに、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"モジュールの変更をしました。
最近のコメント