Sample Site

VBA レキシカルアナライザ

昔作った VBE(Visual Basic Editor:VBAを記述する実行環境)アドイン、VBAtoHTML というプログラムが著しく貧弱であるので、その心臓部であるレキシカルアナライザ(字句解析器)から作り直すことにしました。レキシカルアナライザがどういうものなのかは コチラ をご覧ください。切り出すトークンの属性は18種と、わりと細かく分類します。相変わらずVB系の文字列リテラルは、中にあるダブルクォーテーションが分かり難くて、悶々と考えさせられます。

今回は使っていない RegExp ですが、それなりに理由があります。このソースで使うように、「ある1文字がリストの中にあるかどうか」を確認するには、 Like の方が断然速かったりします。リストの選択肢が少なければ、Dictionary よりも速いんです。Like 侮りがたし。ネイティブコンパイルした後はどうなのかわかりませんが、VBA で使うなら Like 一択です。あ、そういえば、条件コンパイルの項目忘れてた! 使うことないし、また今度ということで。

このソースを元に上記 VBAtoHTML を作り直したところ、下記600行(空行含む)のソースを0.05秒 (47msec/Multimedia Timer)で HTML に変換しました。以前と比べ残念ながら、2倍程度の時間がかかっていますが、分類にちょっと凝ってみたり、メンテナンスが幾分容易になったので良しとします。ちなみに18種に分けましたが、色分けは似たものがあるので13種にしました。GoToの行先と着地が色違いになると何のための色分けかわからなくなるので。

以下のソースコードについて、シンタックスハイライト/プロシージャ領域分割/リンク生成/埋め込みは、新生 VBAtoHTML が 上記 VBE(VBIDE) から情報を取得して自動生成したものです。

vbLexer.bas 参照設定:Microsoft Forms2.0 / scripting runtime

1: '====================================vbaLexer=================================== 2: '|USAGE |:VB/VBA/VBS用 レキシカルアナライザ 3: '| OF |:frm/bas/cls/vbs 等のファイルを字句解析し、結果をクリップボード出力 4: '| THIS |: 5: '|MODULE|:参照設定 > Forms 2.0 / scripting runtime 6: '|Number|Knd|Procedure Name |:-----Summary Description----- 7: '|No.1 |Sub|main_lex |:メイン関数 (vblex() テスト用) 8: '|No.2 |Fnc|vbLex |:VB/VBA/VBS用レキシカルアナライザ本体 9: '|No.3 |Sub|LexDicsInit |:連想配列辞書 各オブジェクトを初期化 10: '|No.4 |Sub|LexDicsFree |:オブジェクト変数への割り当てを解除 11: '|No.5 |Sub|DicTokeRemove |:解析結果である トークンと属性を初期化 12: '|No.6 |Fnc|Tab2Space |:水平タブを半角スペースに変換 Sift_JIS 13: '|No.7 |Fnc|FileReadLine |:ファイルを1行ずつ読んでコレクションで返す 14: '|No.8 |Sub|SendClip |:テキストデータをクリップボードに渡す 15: Option Explicit 16: Public gDicToke As Dictionary '外部では READ_ONLY (cls_vblex) 17: Public gDicAttr As Dictionary '外部では READ_ONLY (cls_vblex) 18: Private mDicIdtT As Dictionary 19: Private mDicIdtF As Dictionary 20: Private mDicLabl As Dictionary 21: Private mDicNumT As Dictionary 22: Private mDicNumF As Dictionary 23: Private mDicOpeT As Dictionary 24: Private mDicOpeF As Dictionary 25: Private mDicCtrs As Dictionary 26: Private mDicLgOp As Dictionary 27: Private mDicKeyw As Dictionary 28: Private mDicBltf As Dictionary 29: Private mDicBltv As Dictionary

30: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 31: '|概 要|:メイン関数 (vblex() テスト用) 32: '|依 存|: 33: '|解 説|:本来VBIDEを用いてVBEから直読(VBE Addin VBAtoHTML) 34: '| |:辞書大小文字区別 cPath に対象ファイルの絶対パス クリップボード出力 35: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:2019/05/26 15:22:38 36: Sub main_lex() 37: Const cPath = "D:\VBA_FILES\test00.bas" 38: Dim line As Integer 39: Dim i As Integer 40: Dim ct As Integer 41: Dim buf As String 42: Dim satt As String 43: Dim colfread As Collection 44: Dim var As Variant 45: 46: Call LexDicsInit '各種辞書を初期化 47: 48: Set colfread = FileReadLine(cPath) 'ファイルを一行ずつ取り込み 49: line = 0 50: 51: For Each var In colfread '1行ずつ取り出してレキシカルアナライズ 52: line = line + 1 53: ct = vbLex(Tab2Space(var)) 'vbTab/space変換後に解析(直読時不要) 54: buf = buf & vbCrLf & line & "行目" & vbCrLf 55: '結果を buf に書き込む 56: For i = 1 To ct 57: buf = buf & gDicAttr.Item(i) & " :" & gDicToke.Item(i) & vbCrLf 58: Next i 59: Next 60: 61: Call SendClip(buf) 'クリップボードに送る 62: Call LexDicsFree '辞書を開放する 63: 64: End Sub

65: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 66: '|概 要|:VB/VBA/VBS用レキシカルアナライザ本体 67: '|戻り値|:型 Integer :解析トークンの個数 68: '|パラメータ| 仮引数名 |I/O| 型 | 説明 69: '| No.1 | str |I | String |:ByValで受ける 70: '|依 存|: 71: '|解 説|:strを解析し、連想配列の値に gDicAttr 属性 gDicToke トークン を格納 72: '| |:2つのkeyは同期 日本語変数不可 Shift_JIS 73: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:2019/05/26 15:22:51 74: Public Function vbLex(ByVal str As String) As Integer 75: ' 76: '@補足:以下の属性にトークンを分類する 77: 'ctrst:制御文キーワード numer:数値 opera:演算子 78: 'keywd:一般キーワード liter:文字列リテラル lgope:論理演算子 79: 'bltfn:組み込み関数 comme:コメント signm:単項演算子- 80: 'bltvc:組み込み定数 label:ラベル space:半角スペース 81: 'ident:一般識別子 golbl:Goto側ラベル panct:句読記号 82: 'membr:クラスメンバ等 prmop:メソッドparam(:=) nline:行番号(コロン有無) 83: ' 84: Dim i As Integer, ib As Integer, ct As Integer 85: Dim spct As Integer, ctmp As Integer, strlen As Integer 86: Dim ch As String, tch As String 87: Dim ret As String, att As String 88: 89: On Error GoTo PROC_ERR 90: 91: ct = 0 92: spct = 0 93: i = 1 94: 'トークン連想配列初期化 95: Call DicTokeRemove 96: strlen = Len(str) 97: 98: While i <= strlen 99: ch = Mid$(str, i, 1) 100: ib = i 101: ret = "" 102: att = "" 103: '空白 (1つずつだと圧倒的に手数が増えるのでまとめる) 104: '※ テキストファイルを読む場合 Tab は lex 以前に要スペース変換) 105: If ch = " " Then 106: att = "space" 107: tch = Mid$(str, i + 1, 1) '次の文字を調べる 108: If tch = ch Then 109: For i = i + 2 To strlen 110: tch = Mid$(str, i, 1) 111: If tch = ch Then 112: spct = spct + 1 'カウントのみ 113: Else 114: Exit For 115: End If 116: Next i 'カウンタ i は連続スペースの次の文字 117: ret = Space(spct + 2) '最初の2個 118: spct = 0 '初期化 119: Else 120: i = i + 1 'カウンタを1つ進める 121: ret = ch 122: End If 123: '文字列 124: ElseIf mDicIdtT.Exists(ch) = True Then 'Like の方が若干速い 125: For i = i + 1 To strlen 126: If mDicIdtF.Exists(Mid$(str, i, 1)) = False Then 127: Exit For 128: End If 129: Next i 130: '文字列取り出し 131: ret = Mid$(str, ib, i - ib) 132: '直前文字を取得 133: If ib = 1 Then 134: tch = "" 135: Else 136: tch = Mid$(str, ib - 1, 1) 137: End If 138: 'クラス/構造体 メンバ名 139: If tch = "." Then 140: att = "membr" 141: '下記辞書は大文字小文字が区別される 142: 'ステートメントコントローラ(If For Do 等 GoTo 系除く) 143: ElseIf mDicCtrs.Exists(ret) = True Then 144: att = "ctrst" 145: '一般キーワード 146: ElseIf mDicKeyw.Exists(ret) = True Then 147: att = "keywd" 148: '同名関数 String 149: If ret = "String" Then 150: If Mid$(str, i, 1) = "(" Then 151: att = "bltfn" 152: End If 153: End If 154: '組み込み関数 155: ElseIf mDicBltf.Exists(ret) = True Then 156: att = "bltfn" 157: '論理演算子 158: ElseIf mDicLgOp.Exists(ret) = True Then 159: att = "lgope" 160: '組み込み定数/変数 161: ElseIf mDicBltv.Exists(ret) = True Then 162: att = "bltvc" 163: 'GoTo/GoSub/Resume と行先ラベル 1行記述前提 改行→失敗 164: ElseIf mDicLabl.Exists(ret) = True Then 165: ct = ct + 1 166: gDicToke.Add ct, ret 167: gDicAttr.Add ct, "ctrst" 168: ct = ct + 1 '以下強制 169: gDicToke.Add ct, " " 170: gDicAttr.Add ct, "space" 171: ib = i + 1 172: For i = i + 1 To strlen 173: tch = Mid$(str, i, 1) 174: If tch = " " Then 175: Exit For 176: End If 177: Next i 178: ret = Mid$(str, ib, i - ib) 179: att = "golbl" 180: ct = ct + 1 181: gDicToke.Add ct, ret 182: gDicAttr.Add ct, att 183: GoTo WEND_POS 184: '剰余演算子 185: ElseIf ret = "Mod" Then 186: att = "opera" 187: 'それ以外の識別子 188: Else 189: att = "ident" 190: End If 191: '数値 &H &O含む 192: ElseIf mDicNumT.Exists(ch) = True Then 193: '文字結合 & を除外 194: If ch = "&" Then 195: If Mid$(str, i + 1, 1) = " " Then 196: i = i + 1 '1つしか進めない 197: ct = ct + 1 198: gDicToke.Add ct, ch 199: gDicAttr.Add ct, "opera" 200: GoTo WEND_POS 201: End If 202: End If 203: For i = i + 1 To strlen 204: If mDicNumF.Exists(Mid$(str, i, 1)) = False Then 205: Exit For 206: End If 207: Next i 208: ret = Mid$(str, ib, i - ib) 209: att = "numer" 210: '行番号 211: If ct = 0 Then 212: att = "nline" 213: End If 214: '文字列リテラル 215: ElseIf ch = """" Then 216: ctmp = 0 'ダブルクォート カウンタ 217: ' ch" の次から 偶数の塊は無視 奇数の塊の最後尾または単独 218: For i = i + 1 To strlen 219: If Mid$(str, i, 1) = """" Then 220: ctmp = ctmp + 1 221: Else 222: If ctmp Mod 2 = 1 Then 223: Exit For 224: Else 225: ctmp = 0 226: End If 227: End If 228: Next i 229: '最後尾の " が見つからない→最後尾まで取得 230: ret = Mid$(str, ib, i - ib) 231: att = "liter" 232: 'コメント 233: ElseIf ch = "'" Then 234: ret = Mid$(str, i) 235: att = "comme" 236: i = strlen + 1 'ループ強制終了 237: '演算子 ※( Mod は文字列で) 238: ElseIf mDicOpeT.Exists(ch) = True Then 239: ret = ch 240: att = "opera" 241: '「<= >= <>」があるため次の文字を検査 242: tch = Mid$(str, i + 1, 1) 243: If mDicOpeF.Exists(tch) = False Then 244: i = i + 1 245: '単項演算子マイナスチェック 246: If ch = "-" And tch <> " " Then 247: att = "signm" 248: End If 249: Else 250: i = i + 2 'カウンタを2文字分進める 251: ret = ch & tch 252: End If 253: '句読記号 254: Else 255: i = i + 1 'カウンタを1つ進める 256: ret = ch 257: att = "panct" 258: 'コロンの処理(1行まとめ書きは panct) 259: If ret = ":" Then 260: ' := メソッドパラメータ代入 261: tch = Mid$(str, i, 1) 262: If tch = "=" Then 263: i = i + 1 264: ret = ":=" 265: att = "prmop" 266: 'ラベル 267: ElseIf ct = 1 Then 268: '1つ前に辞書登録した内容を書き換える 269: ret = gDicToke.Item(ct) 270: att = gDicAttr.Item(ct) 271: If att <> "nline" Then 272: att = "label" 273: End If 274: gDicToke(ct) = ret & ch 275: gDicAttr(ct) = att 276: GoTo WEND_POS 277: End If 278: End If 279: End If 280: ct = ct + 1 281: gDicToke.Add ct, ret 282: gDicAttr.Add ct, att 283: 284: WEND_POS: 285: Wend 286: vbLex = ct 287: 288: PROC_EXIT: 289: Exit Function 290: 291: PROC_ERR: 292: MsgBox "Error_Number: " & Err.Number & ". : " & Err.Description, , _ 293: "vbaLexer" & "." & "vbLex" 294: Resume PROC_EXIT 295: 296: End Function

297: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 298: '|概 要|:連想配列辞書 各オブジェクトを初期化 299: '|依 存|: 300: '|解 説|:VBE Addin ではフォームロード時に実行、アンロードまで辞書を保持する 301: '| |:事前バインディングにて 302: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:2019/05/26 15:23:04 303: Public Sub LexDicsInit() 304: Dim sidtT As String, aidtT() As String 305: Dim sidtf As String, aidtf() As String 306: Dim snumT As String, anumT() As String 307: Dim snumF As String, anumF() As String 308: Dim sopeT As String, aopeT() As String 309: Dim sopeF As String, aopeF() As String 310: Dim sctrs As String, actrs() As String 311: Dim slgop As String, algop() As String 312: Dim skeyw As String, akeyw() As String 313: Dim sbltf As String, abltf() As String 314: Dim sbltv As String, abltv() As String 315: Dim ct As Integer 316: 317: On Error GoTo PROC_ERR 318: 319: '解析結果入力用連想配列 320: Set gDicToke = New Scripting.Dictionary 321: Set gDicAttr = New Scripting.Dictionary 322: 323: 'トークン文字列参照辞書(RegExpをつかわない) 324: Set mDicLabl = New Scripting.Dictionary 325: Set mDicIdtT = New Scripting.Dictionary 326: Set mDicIdtF = New Scripting.Dictionary 327: Set mDicNumT = New Scripting.Dictionary 328: Set mDicNumF = New Scripting.Dictionary 329: Set mDicOpeT = New Scripting.Dictionary 330: Set mDicOpeF = New Scripting.Dictionary 331: Set mDicCtrs = New Scripting.Dictionary 332: Set mDicLgOp = New Scripting.Dictionary 333: Set mDicKeyw = New Scripting.Dictionary 334: Set mDicBltf = New Scripting.Dictionary 335: Set mDicBltv = New Scripting.Dictionary 336: 337: sidtT = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z," & _ 338: "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z" 339: 340: sidtf = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z," & _ 341: "A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,_,$," & _ 342: "0,1,2,3,4,5,6,7,8,9" 343: 344: snumT = "0,1,2,3,4,5,6,7,8,9,&" 345: 346: snumF = "0,1,2,3,4,5,6,7,8,9,.,a,b,c,d,e,f,h,o,A,B,C,D,E,F,H,O" 347: 348: sopeT = "=,<,>,+,*,.,-,^,\" 'Mod IFblock & Numerblock 349: 350: sopeF = "=,<,>" 351: 352: sctrs = "Case,Do,Each,Else,ElseIf,End,Exit,For,Function," & _ 353: "If,In,Loop,Next,Property,Select,Step,Sub,Then,To,Until,Wend," & _ 354: "While,With" 'Goto/GoSub/Resume Dictionary 355: 356: slgop = "And,Or,Not,Eqv,Imp,Xor" 357: 358: skeyw = "Access,AddressOf,Alias,Any,Append,As,Assert,Base,Begin," & _ 359: "Binary,Boolean,ByRef,Byte,ByVal,Call,Close,Compare,Const,Currency," & _ 360: "Date,Debug,Declare,Dim,Double,Enum,Erase,Error,Event,Explicit," & _ 361: "Friend,Get,Global,Implements,Integer,Is,Let,Lib,Like,Line,Lock," & _ 362: "Long,LSet,Me,New,Nothing,Null,Object,On,Open,Option,Optional," & _ 363: "Output,ParamArray,Preserve,Print,Private,PSet,Public,Put,RaiseEvent," & _ 364: "Random,Read,ReDim,RSet,Scale,Set,Shared,Single,Static,String,Type," & _ 365: "TypeOf,Unlock,Variant,WithEvents,Write" 366: 367: sbltf = "Abs,Array,Asc,Ascb,Ascw,Atn,CBool,CByte,CCur,CDate,CDbl,CDec," & _ 368: "CInt,CLng,CSng,CStr,CVerr,CVar,CallByName,Choose,Chr,Chr$,Chrb,ChrB$," & _ 369: "Chrw,Command,Command$,Cos,CreateObject,CurDir,CurDir$,DDB,Date,Date$," & _ 370: "DateAdd,DateDiff,DatePart,DateSerial,DateValue,Day,Dir,Dir$,DoEvents,EOF," & _ 371: "Environ,Error,Error$,Exp,FV,FileAttr,FileDateTime,FileLen,Filter,Fix," & _ 372: "Format,Format$,FormatCurrency,FormatNumber,FormatPercent," & _ 373: "FreeFile,GetAllSettings,GetAttr,GetObject,GetSetting,Hex,Hex$,Hour,IIf," & _ 374: "IMESstatus,IPmt,IRR,InStr,InStrRev,Input,Input$,InputB,InputB$,InputBox," & _ 375: "Int,IsArray,IsDate,IsEmpty,IsError,IsMissing,IsNull,IsNumeric,IsObject," & _ 376: "Join,LBound,LCase,LCase$,LCaseB,LCaseB$,Lof,LTrim,LTrim$,Left,Left$,LeftB," & _ 377: "LeftB$,Len,LenB,Loc,Log,MIRR,MacId,MacScript,Mid,Mid$,MidB,MidB$,Minute," & _ 378: "Month,MonthName,MsgBox,NPV,NPer,Now,Oct,Oct$,PPmt,PV,Partition,Pmt," & _ 379: "QBColor,RGB,RTrim,RTrim$,Rate,Replace,Right,Right$,RightB,RightB$,Rnd," & _ 380: "Round,SLN,SYD,Second,Seek,Sgn,Shell,Sin,Space,Space$,Spc,Split,Sqr,Str," & _ 381: "Str$,StrComp,StrConv,StrReverse,String,String$,Switch,Tab,Tan,Time,Time$," & _ 382: "TimeSerial,TimeValue,Timer,Trim,Trim$,TypeName,UBound,UCase,Val,VarType," & _ 383: "WeekDay,WeekDayName,Year" 384: 385: sbltv = "False,True,vbDataObject,vbMenuText,vbButtonText,vbYellow," & _ 386: "vbHighLight,vbOKCancel,vbInteger,vbCyan,vbString,vbAlias,vbEmpty,vbYesNo," & _ 387: "vbCR,vbMsgBoxRtlReading,vbHidden,vb3DLight,vbByte,vbOK,vbMsgBoxHelpButton," & _ 388: "vbScrollBars,vbReadOnly,vbInfoText,vbYes,vbObject,vbInfoBackground," & _ 389: "vbError,vbGrayText,vbInactiveBorder,vbDecimal,vbInactiveCaptionText," & _ 390: "vbKatakana,vbQuestion,vbYesNoCancel,vbArchive,vbInactiveTitleBar," & _ 391: "vbButtonShadow,vbFormFeed,vbSystem,vbTitleBarText,vbHighLightText," & _ 392: "vbArray,vbAbortRetryIgnore,vbCrLf,vbNullChar,vbMagenta,vbDefaultButton1," & _ 393: "vbDefaultButton2,vbDefaultButton3,vbDefaultButton4,vbExclamation,vbBlack," & _ 394: "vbOKOnly,vbLong,vbActiveTitleBar,vbNarrow,vbWide,vbDirectory,vbGreen," & _ 395: "vbMsgBoxSetForeground,vb3DDKShadow,vbRed,vbUpperCase,vbIgnore,vbRetry," & _ 396: "vb3DHighLight,vbApplicationModal,vbVariant,vbFromUnicode,vbBoolean," & _ 397: "vbRetryCancel,vbUnicode,vbNull,vbApplicationWorkspace,vbCritical,vbDate," & _ 398: "vbNormal,vbNullString,vbButtonFace,vbAbort,vbUserDefinedType,vbCancel," & _ 399: "vbInformation,vbWindowText,vbSingle,vbSystemModal,vbLf,vbLongLong," & _ 400: "vbWindowBackground,vbVolume,vbVerticalTab,vbBlue,vbProperCase,vbDouble," & _ 401: "vbTab,vbHiragana,vbMenuBar,vbNewLine,vbActiveBorder,vbNo,vbCurrency," & _ 402: "vbBack,vbMsgBoxRight,vbLowerCase,vbDesktop,vbWhite,vbWindowFrame" 403: 404: aidtT = Split(sidtT, ",") 405: aidtf = Split(sidtf, ",") 406: anumT = Split(snumT, ",") 407: anumF = Split(snumF, ",") 408: aopeT = Split(sopeT, ",") 409: aopeF = Split(sopeF, ",") 410: actrs = Split(sctrs, ",") 411: algop = Split(slgop, ",") 412: akeyw = Split(skeyw, ",") 413: abltf = Split(sbltf, ",") 414: abltv = Split(sbltv, ",") 415: 416: mDicLabl.Add "GoTo", 1 417: mDicLabl.Add "GoSub", 1 418: mDicLabl.Add "Resume", 1 419: For ct = 0 To UBound(aidtT) 420: mDicIdtT.Add aidtT(ct), 1 421: Next ct 422: For ct = 0 To UBound(aidtf) 423: mDicIdtF.Add aidtf(ct), 1 424: Next ct 425: For ct = 0 To UBound(anumT) 426: mDicNumT.Add anumT(ct), 1 427: Next ct 428: For ct = 0 To UBound(anumF) 429: mDicNumF.Add anumF(ct), 1 430: Next ct 431: For ct = 0 To UBound(aopeT) 432: mDicOpeT.Add aopeT(ct), 1 433: Next ct 434: For ct = 0 To UBound(aopeF) 435: mDicOpeF.Add aopeF(ct), 1 436: Next ct 437: For ct = 0 To UBound(actrs) 438: mDicCtrs.Add actrs(ct), 1 439: Next ct 440: For ct = 0 To UBound(algop) 441: mDicLgOp.Add algop(ct), 1 442: Next ct 443: For ct = 0 To UBound(akeyw) 444: mDicKeyw.Add akeyw(ct), 1 445: Next ct 446: For ct = 0 To UBound(abltf) 447: mDicBltf.Add abltf(ct), 1 448: Next ct 449: For ct = 0 To UBound(abltv) 450: mDicBltv.Add abltv(ct), 1 451: Next ct 452: 453: PROC_EXIT: 454: Exit Sub 455: 456: PROC_ERR: 457: MsgBox "Error_Number: " & Err.Number & ". : " & Err.Description, , _ 458: "vbaLexer" & "." & "LexDicsInit" 459: Resume PROC_EXIT 460: 461: End Sub

462: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 463: '|概 要|:オブジェクト変数への割り当てを解除 464: '|依 存|: 465: '|解 説|:フォーム アンロード時に実行 466: '| |: 467: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:2019/05/23 16:31:18 468: Public Sub LexDicsFree() 469: 470: On Error GoTo PROC_ERR 471: 472: Set gDicToke = Nothing 473: Set gDicAttr = Nothing 474: Set mDicLabl = Nothing 475: Set mDicIdtT = Nothing 476: Set mDicIdtF = Nothing 477: Set mDicNumT = Nothing 478: Set mDicNumF = Nothing 479: Set mDicOpeT = Nothing 480: Set mDicOpeF = Nothing 481: Set mDicCtrs = Nothing 482: Set mDicLgOp = Nothing 483: Set mDicKeyw = Nothing 484: Set mDicBltf = Nothing 485: Set mDicBltv = Nothing 486: 487: PROC_EXIT: 488: Exit Sub 489: 490: PROC_ERR: 491: MsgBox "Error_Number: " & Err.Number & ". : " & Err.Description, , _ 492: "vbaLexer" & "." & "LexDicsFree" 493: Resume PROC_EXIT 494: 495: End Sub

496: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 497: '|概 要|:解析結果である トークンと属性を初期化 498: '|依 存|: 499: '|解 説|:全削除 500: '| |: 501: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:未更新 502: Private Sub DicTokeRemove() 503: 504: On Error GoTo PROC_ERR 505: 506: gDicToke.RemoveAll 507: gDicAttr.RemoveAll 508: 509: PROC_EXIT: 510: Exit Sub 511: 512: PROC_ERR: 513: MsgBox "Error_Number: " & Err.Number & ". : " & Err.Description, , _ 514: "vbaLexer" & "." & "DicTokeRemove" 515: Resume PROC_EXIT 516: 517: End Sub

518: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 519: '|概 要|:水平タブを半角スペースに変換 Sift_JIS 520: '|戻り値|:型 String : 521: '|パラメータ| 仮引数名 |I/O| 型 | 説明 522: '| No.1 | str |I | String |:Byval必須,Shift_JIS 523: '| No.2 | [tablen] |I | [Integer] |[Def=4]: 524: '|依 存|: 525: '|解 説|:引数が Variant データ(実態 String)でも読めるようにByValで受ける 526: '| |:ByRefでVariantは読めない LenB()を使用するのでShift_JIS専用 527: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:2019/05/23 16:30:08 528: Private Function Tab2Space(ByVal str As String, _ 529: Optional tablen As Integer = 4) As String 530: Dim tstr As String 531: Dim pos As Integer 532: 533: On Error GoTo PROC_ERR 534: 535: Do 536: pos = InStr(str, vbTab) 537: If pos <> 0 Then 538: tstr = tstr & Mid$(str, 1, pos - 1) 539: tstr = tstr & Space(tablen - (LenB(tstr) Mod tablen)) 540: str = Mid$(str, pos + 1) '既読部を削除 541: Else 542: tstr = tstr & str 543: End If 544: Loop Until pos = 0 545: 546: Tab2Space = tstr 547: 548: PROC_EXIT: 549: Exit Function 550: 551: PROC_ERR: 552: MsgBox "Error_Number: " & Err.Number & ". : " & Err.Description, , _ 553: "vbaLexer" & "." & "Tab2Space" 554: Resume PROC_EXIT 555: 556: End Function

557: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 558: '|概 要|:ファイルを1行ずつ読んでコレクションで返す 559: '|戻り値|:型 Collection : 560: '|パラメータ| 仮引数名 |I/O| 型 | 説明 561: '| No.1 | fpath |I | String |:ファイルパス 562: '|依 存|: 563: '|解 説|: 564: '| |: 565: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:未更新 566: Private Function FileReadLine(fpath As String) As Collection 567: Dim colret As New Collection 568: Dim fso As Object 569: Dim fileo As Object 570: 571: On Error GoTo PROC_ERR 572: 573: Set fso = CreateObject("Scripting.FileSystemObject") 574: Set fileo = fso.OpenTextFile(fpath, 1, False, 0) 575: 576: Do Until fileo.AtEndOfStream 577: colret.Add fileo.ReadLine 578: Loop 579: 580: fileo.Close 581: Set fileo = Nothing 582: Set fso = Nothing 583: 584: Set FileReadLine = colret 585: 586: PROC_EXIT: 587: Exit Function 588: 589: PROC_ERR: 590: MsgBox "Error_Number: " & Err.Number & ". : " & Err.Description, , _ 591: "vbaLexer" & "." & "FileReadLine" 592: Resume PROC_EXIT 593: 594: End Function

595: '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 596: '|概 要|:テキストデータをクリップボードに渡す 597: '|パラメータ| 仮引数名 |I/O| 型 | 説明 598: '| No.1 | buf |I | String |: 599: '|依 存|:参照設定 > Microsoft Forms 2.0 Object Library 600: '|解 説|:もしくはフォームモジュールを1つ作る 601: '| |: 602: '|作 者|:mfi.sub.jp: 作成日:2019/05/23: 更新:未更新 603: Private Sub SendClip(buf As String) 604: Dim sc As Object 605: 606: On Error GoTo PROC_ERR 607: 608: Set sc = New DataObject 609: With sc 610: .SetText buf 611: .PutInClipboard 612: End With 613: 614: PROC_EXIT: 615: Exit Sub 616: 617: PROC_ERR: 618: MsgBox "Error_Number: " & Err.Number & ". : " & Err.Description, , _ 619: "vbaLexer" & "." & "SendClip" 620: Resume PROC_EXIT 621: 622: End Sub

GAWK のすすめ

VB系のソースを字句解析するスクリプトを GAWK で書けば、登録するキーワードによりますが、およそ 300行 + α くらいで済みます。VBIDEみたいに自己のソース情報を管理するためには、プラス100行あれば、モジュールレベルのソース解析もできます。さらにプラス30行で HTML 化できるでしょう。

今回の VBA による字句解析で、ファイルを開く/読む/連想配列(ハッシュ)をたくさん使うための準備、オブジェクトの解放などその関連作業で、軽く100行くらいは使っています。対してGAWK の配列は元々連想配列しかありませんから、Scripting.Dictionary が標準の配列になっているようなものです。しかも、配列の宣言なんてできませんから、いきなり使い始めることになります。ファイルもコマンドラインにファイル名を引数として与えるだけで、これもいきなり読めてしまいます。特にコマンドは要りません。データドリブンという概念だそうで、イベントドリブンにどっぷり浸かった VBA/VB に慣れた人からすると、ナニコレってなります。筆者も十数年前そうでした。つまり、すぐに肝要な部分の製作に取り掛かれるということです。ちなみに、表計算としては MicrosoftExcel のよきパートナーとなり得るほど柔軟に csv ファイルを扱えたりもします。

GAWK は CUI ですし、C や他のスクリプト言語に比べると、いろいろな観点から「おもちゃ」呼ばわりされることもあります。そもそもこのご時世に キャラクタベースの言語で何作るの? という感じかもしれません。プログラミング言語として GAWK / AWK 言語を採用し、真面目に取り組む人は稀有な存在なのでしょう。上の例で行くと、クリップボードにデータを送ることも単独ではできないくらいですから。つつましい限りです。しかし、今回のようにテキスト処理を中心にプログラミングを行う場合、初めに GAWK でプロトタイプを作り、その骨子に納得がいったら、C や C++ 及び VBA/VB に移植し、ディテールを固めるという工程でいくと問題が起こりにくくなります。(あくまでも筆者の場合)

CUI だからコマンドプロンプトやパワーシェルとにらめっこなんだろ、と思われたそこのあなた。マクロとアウトプットを備えたエディタがあれば、それはもう VBA の使い勝手と同じなんです。実行ボタンを押したら、書いたコードが実行されて、結果を表示する。だからなにって感じですが、使ってみればわかります。GAWK 楽で楽しいですよ。

GAWK サクラエディタで快適に使う