久しぶりに、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"モジュールの変更をしました。
最近のコメント