2007年12月15日土曜日

HDLのデータベースを利用したスクリプトの(X)HTML変換

SQLeleとHDLのデータベースを利用した、HSPスクリプトの(X)HTMLコンバータ……のモジュール。サンプルスクリプト付き。
ブログにアップするような量じゃない(約1000行!)のですが、折角できたので公開。そのうち正式にHPに置くと思います。ヘルプも作ってないので使いにくいと思いますが、使い道がありましたらどうぞご自由にお使いください。

このモジュールを利用したコンバータはHSPにHDLとSQLeleが標準でつくようになったら配布を開始したいと思います。

このスクリプト自身の変換で約3秒かかります。不必要な外部ファイルは解析しない処理を組み込んだこともあり、2秒近い高速化が行えているようです。
// HSPスクリプトを(X)HTMLへ変換するモジュール(HTXmodule)
// 【動作条件】
//   ・HDL用データベース(hdlbase.xdb)が作成されていること
//   ・sqlele.hspがcommonフォルダにあること
// HDL用データベース内のCacheテーブルを使用します。
#ifndef HTX_MSG
#include "sqlele.hsp"
#module HSPtoXHTML


// 変換設定
#const global HTX_HTML          0x00000000  // HTML形式で変換
#const global HTX_XHTML         0x00000001  // XHTML形式で変換
#const global HTX_ADD_BR        0x00000002  // 改行時に<br>または<br />を追加
#const global HTX_TAB_TO_SPACE  0x00000004  // タブをスペースに変換
#const global HTX_SPACE_TO_NBSP 0x00000008  // スペースを&nbsp;に変換
#const global HTX_SEARCH_OTHER  0x00000010  // 外部のスクリプト(includeしたファイル)を検索

// マークアップ(MarkUp)する要素
#const global HTX_MU_CMD        0x00000100  // 命令
#const global HTX_MU_FUNC       0x00000200  // 関数
#const global HTX_MU_COMMENT    0x00000400  // コメント
#const global HTX_MU_PREPRO     0x00000800  // プリプロセッサ
#const global HTX_MU_SYSVAR     0x00001000  // システム変数
#const global HTX_MU_LABEL      0x00002000  // ラベル
#const global HTX_MU_BASIC      0x00003F00  // HSP3.1付属スクリプトエディタでマークアップされるすべての要素
#const global HTX_MU_STRING     0x00004000  // 文字列
#const global HTX_MU_MACRO      0x00008000  // マクロ
#const global HTX_MU_NUMBER     0x00010000  // 数値
#const global HTX_MU_SCODE      0x00020000  // 文字コード
#const global HTX_MU_ALL        0x00FFFF00  // すべての要素

// エラー
#enum global HTX_ERR_NOERROR = 0    // エラーなし・正常終了
#enum global HTX_ERR_NODB           // データベースが見つからない
#enum global HTX_ERR_NOFILE         // ファイルが見つからない
#enum global HTX_ERR_NODIR          // ディレクトリが見つからない
#enum global HTX_ERR_CONSTRUCTION   // 構文に誤りがある or 循環インクルード

// 定数
#const  CP_ACP                  0
#define DB_HEADER               "_TABLE_FOR_HTX_"   // ヘッダ
#const  HTX_DEFAULT_TAB_WIDTH   4                   // タブ幅 デフォルト値
#const  HTX_EXPAND_SIZE         1024                // スクリプト保存用変数 メモリ確保量・拡張量
#const  HTX_DEFAULT_NEST        5                   // インクルードネスト

#const global HTX_MSG           0xB000

// 改行コード
#const CR   0x0D
#const LF   0x0A
#const CRLF 0x0A0D

// タグ
#define ctype tag_start(%1%2="") _tag_start@HSPtoXHTML(%1%2) + ">"
#defcfunc _tag_start@HSPtoXHTML str tag_name, str class_name
    if class_name != "" {
        return "<" + tag_name + " class=\"" + class_name + "\""
    } else {
        return "<" + tag_name
    }
#define ctype tag_end(%1"</" + (%1) + ">"
#define tag_alone(%1%2="") _tag_alone@HSPtoXHTML(%1%2="")
#deffunc _tag_alone@HSPtoXHTML str tag_name, str class_name
    if mode_xhtml {
        return _tag_start(tag_name, class_name) + " />"
    } else {
        return _tag_start(tag_name, class_name) + ">"
    }

// 状態
#enum STATE_NORMAL = 1
#enum STATE_COMMENT
#enum STATE_MULTILINE_COMMENT
#enum STATE_STRINGS
#enum STATE_MULTILINE_STRINGS

// 状態(行)
#enum LSTATE_NORMAL = 1
#enum LSTATE_DEFFUNC
#enum LSTATE_DEFCFUNC
#enum LSTATE_UNDEF
#enum LSTATE_MACRO
#enum LSTATE_INCLUDE
#enum LSTATE_ADDITION

// データベース名
#define DB_HDL                  "hdlbase.xdb"

// デフォルトクラス名
#define DEFAULT_CLASS_CMD       "cmd"       // 命令
#define DEFAULT_CLASS_FUNC      "func"      // 関数
#define DEFAULT_CLASS_COMMENT   "comment"   // コメント
#define DEFAULT_CLASS_PREPRO    "prepro"    // プリプロセッサ
#define DEFAULT_CLASS_SYSVAR    "sysvar"    // システム変数
#define DEFAULT_CLASS_LABEL     "label"     // ラベル
#define DEFAULT_CLASS_STRING    "str"       // 文字列
#define DEFAULT_CLASS_MACRO     "macro"     // マクロ
#define DEFAULT_CLASS_NUMBER    "num"       // 数値
#define DEFAULT_CLASS_SCODE     "scode"     // 文字コード

#uselib "kernel32.dll"
    #cfunc global IsDBCSLeadByteEx "IsDBCSLeadByteEx" sptr, sptr

// ***************************************************
// 内部で使用する命令

#deffunc insert_data@HSPtoXHTML int markup

    num = stat
    repeat num
        sql_q "INSERT INTO Cache (Key, Data) VALUES ('" + sqesc(sql_v("Name")) + "'," + markup + ")"
        sql_next
    loop
    return

#define ctype to_lower(%1getpath(%116)

// ***************************************************
// 変換で使用する命令

// 文字がスペースかどうか(キーワードとして利用できない文字かどうか)調べる
#defcfunc is_space@HSPtoXHTML int iTarget
    if IsDBCSLeadByteEx(CP_ACP, iTarget)     : return 0
    if ('0' <= iTarget) & (iTarget <= '9') : return 0
    if ('a' <= iTarget) & (iTarget <= 'z') : return 0
    if ('A' <= iTarget) & (iTarget <= 'Z') : return 0
    if (iTarget == '_') : return 0
    return 1


// 文字が数字かどうか調べる
#defcfunc is_number@HSPtoXHTML int iTarget
    return ('0' <= iTarget) & (iTarget <= '9')


// 文字列の追加
#define add_string@HSPtoXHTML(%1,%2=0) _add_string@HSPtoXHTML %1%2
#deffunc _add_string@HSPtoXHTML str str_to_add, int _width_to_add
    if num_include == 0 {
        len = strlen(str_to_add)
        width_to_add = _width_to_add
        if width_to_add == 0 : width_to_add = len
        if code_after_size <= code_after_pos + len {
            code_after_size += HTX_EXPAND_SIZE
            memexpand code_after, code_after_size
        }
        poke code_after, code_after_pos, str_to_add
        code_after_pos += len
        code_after_x += width_to_add
    }
    return

// ***************************************************

// HTXmodule用データベースを作成(更新)する(Cacheテーブルを利用)
// htx_make_DB target_dir
//   target_dir : データベースがあるディレクトリ
#deffunc htx_make_DB str target_dir
    sql_open target_dir + "/" + DB_HDL

    // Cacheテーブルを初期化
    sql_q "DELETE FROM Cache"
    sql_q "BEGIN"
    // HTX用に初期化されていることを判別するためのデータを書きこむ
    sql_q "INSERT INTO Cache (ID, Key) VALUES (1, '" + DB_HEADER + "')"

    // 命令の登録
    sql_q "SELECT Name FROM Help WHERE Prm NOT LIKE '(%' AND Group3 NOT LIKE '%マクロ%' AND Group3 NOT LIKE '%システム変数%' AND Group3 NOT LIKE '%プリプロセッサ%'"
    insert_data@HSPtoXHTML HTX_MU_CMD

    // 関数の登録
    sql_q "SELECT Name FROM Help WHERE Prm LIKE '(%' AND Group3 NOT LIKE '%マクロ%' AND Group3 NOT LIKE '%システム変数%' AND Group3 NOT LIKE '%プリプロセッサ%'"
    insert_data@HSPtoXHTML HTX_MU_FUNC

    // システム変数の登録
    sql_q "SELECT Name FROM Help WHERE Group3 LIKE '%システム変数%'"
    insert_data@HSPtoXHTML HTX_MU_SYSVAR

    // マクロの登録
    sql_q "SELECT Name FROM Help WHERE Group3 LIKE '%マクロ%'"
    insert_data@HSPtoXHTML HTX_MU_MACRO

    // プリプロセッサの登録
    sql_q "SELECT Name FROM Help WHERE Group3 LIKE '%プリプロセッサ%'"
    insert_data@HSPtoXHTML HTX_MU_PREPRO

    sql_q "COMMIT"
    sql_close
    return HTX_ERR_NOERROR


// データベースの存在確認・変数初期化
// 変換実行前に必ず1度実行すること
// path_DB : hdlbase.xdbがあるディレクトリ(デフォルトでdir_exe)
//   stat == HTX_ERR_NOERROR : ロード成功
//   stat == HTX_ERR_NODB    : データベースが見つからない
//   stat == HTX_ERR_NODIR   : ディレクトリが見つからない
#define global htx_init(%1=dir_exe) _htx_init@HSPtoXHTML %1
#deffunc _htx_init@HSPtoXHTML str path_DB

    // ディレクトリの存在を確認する
    dirlist tmp, path_DB, 5
    if stat == 0 {
        // ディレクトリが見つからない
        return HTX_ERR_NODIR
    }

    // データベースの存在を確認する
    dir_current = dir_cur               // 命令実行時のカレントディレクトリを記録
    chdir path_DB : exist DB_HDL
    if strsize == -1 {
        // データベースファイルが見つからない
        // 命令実行時のカレントディレクトリに戻る
        gosub *back_to_first_dir_cur
        return HTX_ERR_NODB
    }

    // CacheテーブルがHTXmodule用かどうか判別し、そうでなければ
    // HDL用データベースからHTXmodule用データベースを作成
    need_to_make = 0
    sql_open DB_HDL
    sql_q "SELECT Key FROM Cache WHERE ID = 1"
    if stat == 0 {
        // ID1のレコードが存在しない
        need_to_make = 1
    } else : if sql_v("Key") != DB_HEADER {
        // ID1のレコードがHTXmodule用のヘッダと異なる
        need_to_make = 1
    }
    sql_close
    if need_to_make : htx_make_DB path_DB

    // 各種変数の初期化
    gosub *init_setting

    // 命令実行時のカレントディレクトリに戻る
    gosub *back_to_first_dir_cur

    return HTX_ERR_NOERROR


// 各種変数の初期化
*init_setting

    // タブ幅を設定
    tab_width = HTX_DEFAULT_TAB_WIDTH

    // データベース名をフルパスで記録
    DB_name = path_DB +"/" + DB_HDL

    // クラス名を設定
    class_cmd     = DEFAULT_CLASS_CMD
    class_func    = DEFAULT_CLASS_FUNC
    class_comment = DEFAULT_CLASS_COMMENT
    class_prepro  = DEFAULT_CLASS_PREPRO
    class_sysvar  = DEFAULT_CLASS_SYSVAR
    class_label   = DEFAULT_CLASS_LABEL
    class_string  = DEFAULT_CLASS_STRING
    class_macro   = DEFAULT_CLASS_MACRO
    class_number  = DEFAULT_CLASS_NUMBER
    class_scode   = DEFAULT_CLASS_SCODE
    return


// 命令実行時のカレントディレクトリに戻る
*back_to_first_dir_cur

    chdir dir_current
    return


// タブ幅を設定
//   幅が1未満の時は強制的にHTX_DEFAULT_TAB_WIDTHにする
#deffunc htx_set_tab_width int _new_width

    new_width = _new_width
    if new_width < 1 : new_width = HTX_DEFAULT_TAB_WIDTH
    tab_width = new_width
    return HTX_ERR_NOERROR


// htx_set_class new_class, mode
// クラス名を設定
//   new_class : 新しいクラス名
//   mode      : 設定するクラスを指す数値(HTX_MU_???)
#deffunc htx_set_class str new_class, int mode

    if mode & HTX_MU_CMD     : class_cmd     = new_class
    if mode & HTX_MU_FUNC    : class_func    = new_class
    if mode & HTX_MU_COMMENT : class_comment = new_class
    if mode & HTX_MU_PREPRO  : class_prepro  = new_class
    if mode & HTX_MU_SYSVAR  : class_sysvar  = new_class
    if mode & HTX_MU_LABEL   : class_label   = new_class
    if mode & HTX_MU_STRING  : class_string  = new_class
    if mode & HTX_MU_MACRO   : class_macro   = new_class
    if mode & HTX_MU_NUMBER  : class_number  = new_class
    if mode & HTX_MU_SCODE   : class_scode   = new_class
    return HTX_ERR_NOERROR


// htx_cnv code, mode, path_code, path_common
// 変換を実行(Shift-JIS限定)
//   source : 変換するスクリプトが代入された変数
//   mode   : 変換モード(HTX_???およびHTX_MU_???の論理和)
//   path_code   : スクリプトのあるパス(デフォルトでdir_exe)
//   path_common : コモンフォルダのパス(デフォルトでdir_exe + "/common")
//   stat == HTX_ERR_NOERROR : 変換成功
//   stat == HTX_ERR_NODB    : データベースが作成されていない・htx_open未実行
//   stat == HTX_ERR_NOFILE  : インクルードするファイルが見つからない
//   stat == HTX_ERR_CONSTRUCTION : 文法が間違っている
#define global htx_cnv(%1%2%3=dir_exe%4="") _htx_cnv@HSPtoXHTML %1%2%3%4
#deffunc _htx_cnv@HSPtoXHTML var source, int mode, str path_code, str _path_common
    exist DB_name
    if strsize == -1 {
        // データベースファイルがない あるいは htx_open未実行
        return HTX_ERR_NODB
    }

    path_common = _path_common
    if path_common == "" : path_common = dir_exe + "\\common"

    gosub *before_cnv                   // 変換の準備
    gosub *convert                      // 変換の実行
    gosub *after_cnv                    // 変換の後処理

    return error

*before_cnv
    sql_open DB_name
    sql_q "BEGIN"

    notesel code
    code_before = source                // 変換前のスクリプトを保存する変数
    code = source                       // 解析対象のスクリプトを保存する変数
    error = HTX_ERR_NOERROR
    first_char_of_prm = 1               // ラベルの判定に使用
    need_to_add = 0
    need_to_del = 0

    sdim code_name, 256HTX_DEFAULT_NEST   // ファイル名(フルパス)
    dim code_size,   HTX_DEFAULT_NEST       // ファイルサイズ
    dim code_pos,    HTX_DEFAULT_NEST       // 解析地点
    dim code_state,  HTX_DEFAULT_NEST       // 状態
    dim code_lstate, HTX_DEFAULT_NEST       // 状態(行)
    num_include = 0                         // 外部ファイル ネスト数

    code_name(0)   = path_code + "\\"
    code_size(0)   = strlen(code_before)
    code_pos(0)    = 0
    code_state(0)  = STATE_NORMAL
    code_lstate(0) = LSTATE_NORMAL

    sdim code_after, EXPAND_SIZE        // 変換結果を保存する変数
    code_after_x = 0
    code_after_pos = 0                  // 変換結果 文字列挿入地点
    code_after_size = EXPAND_SIZE

    // 変換モードの抽出
    mode_xhtml  = (mode & HTX_XHTML) != 0
    mode_add_br = (mode & HTX_ADD_BR) != 0
    mode_tab_to_space  = (mode & HTX_TAB_TO_SPACE) != 0
    mode_space_to_nbsp = (mode & HTX_SPACE_TO_NBSP) != 0
    mode_search_other  = (mode & HTX_SEARCH_OTHER) != 0

    // マークアップする要素の抽出
    markup_cmd     = (mode & HTX_MU_CMD) != 0
    markup_func    = (mode & HTX_MU_FUNC) != 0
    markup_comment = (mode & HTX_MU_COMMENT) != 0
    markup_prepro  = (mode & HTX_MU_PREPRO) != 0
    markup_sysvar  = (mode & HTX_MU_SYSVAR) != 0
    markup_label   = (mode & HTX_MU_LABEL) != 0
    markup_string  = (mode & HTX_MU_STRING) != 0
    markup_macro   = (mode & HTX_MU_MACRO) != 0
    markup_number  = (mode & HTX_MU_NUMBER) != 0
    markup_scode   = (mode & HTX_MU_SCODE) != 0

    return

// まだ書き換え終わっていない
*convert
    repeat
        if error != HTX_ERR_NOERROR : break

        code_pos(num_include) = cnt
        c = peek(code, code_pos(num_include))
        switch c
        case 0
            // ファイル終了
            break
            swbreak
        case CR
        case LF
            // 改行
            gosub *start_new_line
            // 自分自身にメッセージを送信する
            sendmsg hwndHTX_MSG100 * code_pos(num_include) / code_size(num_include), num_include

            if strmid(code, cnt2) == "\n" {
                continue cnt + 2
            }
            swbreak
        case '/'
            if (strmid(code, cnt2) == "/*") & (code_state(num_include) == STATE_NORMAL) {
                // 複数行コメント開始
                gosub *start_multiline_comment
                add_string "/*"
                continue cnt + 2
            }
            if (strmid(code, cnt2) == "//") & (code_state(num_include) == STATE_NORMAL) {
                // 単一行コメント開始
                gosub *start_singleline_comment
                add_string "//"
                continue cnt + 2
            }
            add_string "/"
            swbreak
        case '*'
            // ラベル
            if (first_char_of_prm) & (code_state(num_include) == STATE_NORMAL) {
                gosub *first_char_finished
                gosub *add_label
                continue code_pos(num_include)
            }
            // 複数行コメントの終了
            if (strmid(code, cnt2) == "*/") & (code_state(num_include) == STATE_MULTILINE_COMMENT) {
                add_string "*/"
                gosub *end_multiline_comment
                continue cnt + 2
            }
            add_string "*"
            swbreak
        case ';'
            // 単一行コメント開始
            if (code_state(num_include) == STATE_NORMAL) {
                gosub *start_singleline_comment
            }
            add_string ";"
            swbreak
        case '{'
            // 複数行文字列の開始
            if (strmid(code, cnt2) == "{\"") & (code_state(num_include) == STATE_NORMAL) {
                gosub *start_multiline_strings
                add_string "{\""
                continue cnt + 2
            }
            add_string "{"
            swbreak
        case '\"'
            // 単一行文字列開始
            if (code_state(num_include) == STATE_NORMAL) {
                gosub *start_singleline_strings
                add_string "\""
                continue
            }
            // 単一行文字列終了
            if (code_state(num_include) == STATE_STRINGS) {
                add_string "\""
                gosub *end_singleline_strings
                continue cnt + 1
            }
            if (strmid(code, cnt2) == "\"}") & (code_state(num_include) == STATE_MULTILINE_STRINGS) {
                add_string "\"}"
                gosub *end_multiline_strings
                continue cnt + 2
            }
            add_string "\""
            swbreak
        case '\t'
            if mode_tab_to_space & (code_state(num_include) != STATE_STRINGS) & (code_state(num_include) != STATE_MULTILINE_STRINGS) {
                // タブをスペースへ変換する
                if mode_space_to_nbsp {
                    s = "&nbsp;"
                } else {
                    s = " "
                }
                repeat tab_width - (code_after_x \ tab_width)
                    add_string s, 1
                loop
            } else {
                add_string "\t", tab_width - (code_after_x \ tab_width)
            }
            swbreak
        case ' '
            if mode_space_to_nbsp {
                // 半角スペースを変換(文字列中・コメント中でも実行)
                add_string "&nbsp;"1
            } else {
                add_string " "
            }
            swbreak
        case ':'
            // 文の区切れ
            add_string ":"
            if code_state(num_include) == STATE_NORMAL : gosub *end_line
            swbreak
        case ',' : case '='
            // パラメータの区切れ
            add_string strf("%c", c)
            if code_state(num_include) == STATE_NORMAL : gosub *end_area
            swbreak
        case '\\'
            if (code_state(num_include) == STATE_STRINGS) | (code_state(num_include) == STATE_MULTILINE_STRINGS) {
                if (strmid(code, cnt2) == "\\\\") | (strmid(code, cnt2) == "\\\"") {
                    add_string strmid(code, cnt2)
                    continue cnt + 2
                }
            }
            add_string "\\"; : x++
            swbreak
        case '&' : add_string "&amp;"1 : swbreak
        case '<' : add_string "&lt;"1  : swbreak
        case '>' : add_string "&gt;"1  : swbreak
        default
            // 命令・関数などキーワードの可能性
            if IsDBCSLeadByteEx(CP_ACP, c) {
                // 2バイト文字の場合

                // 現在のバージョンでは変数をマークアップしないので、
                // このように扱ってもOK。
                add_string strmid(code, cnt2); : x += 2
                continue cnt + 2
            }
            // 1バイト文字の場合
            if code_state(num_include) == STATE_NORMAL {
                // 文字列中やコメント中でなければ、数値や命令・関数としてマークアップできるかどうか調べる
                if is_number(c) | (c == '%') | (c == '$') | (c == '-') {
                    // 数値の挿入
                    gosub *add_number
                    continue code_pos(num_include)
                }
                if c == '\'' {
                    // 文字コード
                    gosub *add_stringing_code
                    continue code_pos(num_include)
                }
                if (is_space(c) == 0) | (c == '#') {
                    // 命令・プリプロセッサ・関数etc.の挿入
                    gosub *add_word
                    continue code_pos(num_include)
                }
            }
            // 命令などを構成する文字列ではなかった場合(@,|,(,)など)
            gosub *first_char_finished
            add_string strf("%c", c); : x++
            swbreak
        swend
    loop
    gosub *start_new_line
    return

// 変数の処理
*first_char_finished
    // 空白でない文字を取り出した場合
    first_char_of_prm = 0
    return

*end_line
    // (マルチステートメントを含む論理的な)行の終了に伴う変数の変化
*end_area
    // パラメータの終了に伴う変数の変化
    first_char_of_prm = 1
    return

// *****************************************************************

// 単行コメントの開始
*start_singleline_comment
    code_state(num_include) = STATE_COMMENT
    if markup_comment {
        add_string tag_start("span", class_comment)
    }
    return

// コメント行を終了
*end_singleline_comment
    code_state(num_include) = STATE_NORMAL
    if markup_comment {
        add_string tag_end("span")
    }
    return

// 複数行コメントの開始
*start_multiline_comment
    code_state(num_include) = STATE_MULTILINE_COMMENT
    if markup_comment {
        add_string tag_start("span", class_comment)
    }
    return

// 複数行コメントの終了
*end_multiline_comment
    code_state(num_include) = STATE_NORMAL
    if markup_comment {
        add_string tag_end("span")
    }
    return

// 単行文字列の開始
*start_singleline_strings
    code_state(num_include) = STATE_STRINGS
    if markup_string {
        add_string tag_start("span", class_string)
    }
    if mode_search_other & ((code_lstate(num_include) == LSTATE_INCLUDE) | (code_lstate(num_include) == LSTATE_ADDITION)) {
        // インクルードするファイル名の開始
        inc_filename_pos = code_pos(num_include) + 1
    }
    return

// 単行文字列の終了
*end_singleline_strings
    code_state(num_include) = STATE_NORMAL
    if markup_string {
        add_string tag_end("span")
    }
    if mode_search_other & ((code_lstate(num_include) == LSTATE_INCLUDE) | (code_lstate(num_include) == LSTATE_ADDITION)) {
        // インクルードするファイル名の終了
        inc_filename = strmid(code, inc_filename_pos, code_pos(num_include) - inc_filename_pos)

        // hsファイルがありそうなら検索しない(高速化)
        sql_q "SELECT COUNT(*) FROM Help WHERE Mod LIKE '" + getpath(inc_filename, 9) + "'"
        if sql_i("COUNT(*)") > 0 : return

        exist getpath(code_name(num_include), 32) + inc_filename
        if strsize < 0 {
            // 今のスクリプトと同じフォルダに見つからない場合
            exist path_common + "\\" + inc_filename
            if strsize < 0 {
                if code_lstate(num_include) == LSTATE_ADDITION {
                    // #additionの場合、ファイルが見つからないくても問題ない
                    return
                }
                if code_lstate(num_include) == LSTATE_INCLUDE {
                    // #includeの場合、ファイルが見つからないのは問題
                    error = HTX_ERR_NOFILE
                    return
                }
            } else {
                // commonフォルダにありました
                code_name(num_include + 1) = path_common + "\\" + inc_filename
            }
        } else {
            // 今のスクリプトと同じフォルダにありました
            code_name(num_include + 1) = getpath(code_name(num_include), 32) + inc_filename
        }

        // インクルードするファイル名が求まった!

        // 多重インクルード防止
        // 同じファイルをインクルードしている状態なら、エラーを発行(無限ループ防止)
        repeat num_include + 1
            if code_name(cnt) == code_name(num_include + 1) {
                error = HTX_ERR_CONSTRUCTION
                break
            }
        loop
        if error != HTX_ERR_NOERROR : return
        
        num_include++
        exist code_name(num_include)
        code_size(num_include) = strsize
        code_pos(num_include) = 0
        code_state(num_include) = STATE_NORMAL
        code_lstate(num_include) = LSTATE_NORMAL

        noteload code_name(num_include)
        wait 1

        gosub *convert

        num_include--
        if num_include {
            exist inc_filename(num_include)
            sdim code, strsize + 1
            noteload inc_filename(num_include)
        } else {
            sdim code, code_size(0) + 1
            code = code_before
        }
        wait 1
    }
    return

// 複数行文字列の開始
*start_multiline_strings
    code_state(num_include) = STATE_MULTILINE_STRINGS
    if markup_string {
        add_string tag_start("span", class_string)
    }
    return

// 複数行文字列の終了
*end_multiline_strings
    code_state(num_include) = STATE_NORMAL
    if markup_string {
        add_string tag_end("span")
    }
    return

// *****************************************************************

// code_pos(num_include)の位置以降をラベルとして挿入
*add_label
    label_length = code_size(num_include) - code_pos(num_include)
    repeat code_size(num_include) - code_pos(num_include) - 1, code_pos(num_include) + 1
        if (code_size(num_include) <= cnt) {
            label_length = code_size(num_include) - code_pos(num_include)
            break
        }
        j = peek(code, cnt)
        if IsDBCSLeadByteEx(CP_ACP, j) : continue cnt + 2
        if (cnt == code_pos(num_include) + 1) & is_number(j) {
            label_length = cnt - code_pos(num_include)
            break
        }
        if is_space(j) {
            label_length = cnt - code_pos(num_include)
            break
        }
    loop
    if markup_label & (1 < label_length) {
        add_string tag_start("span", class_label)
    }
    add_string strmid(code, code_pos(num_include), label_length)
    code_pos(num_include) += label_length
    if (markup_label) & (1 < label_length) {
        add_string tag_end("span")
    }
    return

// code_pos(num_include)の位置以降を数字として挿入
*add_number
    if markup_number {
        add_string tag_start("span", class_number)
    }
    number_length = 0
    repeat -11
        j = peek(code, code_pos(num_include) + cnt)
        if is_number(j) | (j == '.') {
            continue
        }
        if (cnt == 1) : if (peek(code, code_pos(num_include)) == '0') & ((j == 'x') | (j == 'b')) {
            continue
        }
        if (j == 'e') | (j == 'E') {
            if code_pos(num_include) + cnt + 1 < code_size(num_include) {
                n = peek(code, code_pos(num_include) + cnt + 1)
                if (n == '+') | (n == '-') : continue cnt + 2
            }
            continue
        }
        if (('a' <= j) & (j <= 'f')) | (('A' <= j) & (j <= 'F')) {
            if ((to_lower(strmid(code, code_pos(num_include), 2)) == "0x") | (peek(code, code_pos(num_include)) == '$')) {
                // 16進数
                continue
            }
        }
        number_length = cnt
        break
    loop
    add_string strmid(code, code_pos(num_include), number_length)
    code_pos(num_include) += number_length
    if markup_number {
        add_string tag_end("span")
    }
    return

// 文字コード
*add_stringing_code
    if markup_scode {
        add_string tag_start("span", class_scode)
    }
    scode_length = 0
    repeat -11
        j = peek(code, code_pos(num_include) + cnt)
        if j == '\\' {
            continue cnt + 2
        }
        if (j == '\'') | (j == 0) {
            scode_length = cnt + 1
            break
        }
    loop
    add_string strmid(code, code_pos(num_include), scode_length)
    code_pos(num_include) += scode_length
    if markup_scode {
        add_string tag_end("span")
    }
    return

// 命令・関数・システム変数など
*add_word
    repeat -11
        if code_pos(num_include) + cnt >= code_size(num_include) {
            keyword = strmid(code, code_pos(num_include), cnt)
            break
        }
        if is_space(peek(code, code_pos(num_include) + cnt)) {
            keyword = strmid(code, code_pos(num_include), cnt)
            break
        }
    loop
    lower_keyword = to_lower(keyword)

    sql_q "SELECT * FROM Cache WHERE Key = '" + sqesc(keyword) + "' LIMIT 1"
    if stat {
        // データベースに載っている文字列は適切なタグで囲む

        // doubleのみ別(データベースに載っているため)
        if (tmp_stock == "#const") & (lower_keyword == "double") & (code_lstate(num_include) == LSTATE_MACRO) {
            gosub *add_word_sub
            return
        }
        need_to_add = 0     // hsファイルなどで先に定義されていた場合を考慮

        switch sql_i("Data")
            case HTX_MU_CMD
                add_string tag_start("span", class_cmd)
                swbreak
            case HTX_MU_FUNC
                if (code_lstate(num_include) == LSTATE_NORMAL) | ((lower_keyword!="int") & (lower_keyword!="str") & (lower_keyword!="double")) {
                    add_string tag_start("span", class_func)
                }
                swbreak
            case HTX_MU_SYSVAR
                add_string tag_start("span", class_sysvar)
                swbreak
            case HTX_MU_PREPRO
                if markup_prepro : add_string tag_start("span", class_prepro)
                if (lower_keyword == "#deffunc") | (lower_keyword == "#modfunc") | (lower_keyword == "#func") {
                    code_lstate(num_include) = LSTATE_DEFFUNC
                    tmp_stock = lower_keyword
                    if markup_cmd : need_to_add = 1
                }
                if (lower_keyword == "#modinit") {
                    code_lstate(num_include) = LSTATE_DEFFUNC
                }
                if (lower_keyword == "#defcfunc") | (lower_keyword == "#cfunc") {
                    code_lstate(num_include) = LSTATE_DEFCFUNC
                    if markup_func : need_to_add = 1
                }
                if (lower_keyword == "#enum") | (lower_keyword == "#const") | (lower_keyword == "#define") {
                    code_lstate(num_include) = LSTATE_MACRO
                    tmp_stock = lower_keyword
                    if markup_macro : need_to_add = 1
                }
                if lower_keyword == "#cmpopt" : tmp_stock = lower_keyword
                if (lower_keyword == "#include") {
                    code_lstate(num_include) = LSTATE_INCLUDE
                }
                if (lower_keyword == "#addition") {
                    code_lstate(num_include) = LSTATE_ADDITION
                }
                if (lower_keyword == "#undef") {
                    code_lstate(num_include) = LSTATE_UNDEF
                    need_to_del = 1
                }
                swbreak
            case HTX_MU_MACRO
                add_string tag_start("span", class_macro)
                swbreak
        swend

        add_string keyword

        switch sql_i("Data")
            case HTX_MU_CMD
            case HTX_MU_SYSVAR
            case HTX_MU_MACRO
                add_string tag_end("span")
                swbreak
            case HTX_MU_PREPRO
                if markup_prepro : add_string tag_end("span")
                swbreak
            case HTX_MU_FUNC
                if (code_lstate(num_include) == LSTATE_NORMAL) | ((lower_keyword != "int") & (lower_keyword != "str") & (lower_keyword != "double")) {
                    add_string tag_end("span")
                }
            swbreak
        swend

        // データベースに載っていてundefする→データベースから一時的に削除
        if need_to_del {
            if code_lstate(num_include) == LSTATE_UNDEF {
                sql_q "DELETE FROM Cache WHERE Key = '" + sqesc(keyword) + "'"
                need_to_del = 0
            }
        }
    } else {
        // データベースに載っていないのにundefする→無効
        if need_to_del {
            if (code_lstate(num_include) == LSTATE_UNDEF) {
                need_to_del = 0
            }
        }
        // データベースに載っていない文字列はそのまま
        if (tmp_stock == "#cmpopt") {
            gosub *add_word_sub : tmp_stock = ""
            return
        }
        if need_to_add {
            if (code_lstate(num_include) == LSTATE_MACRO) & (lower_keyword == "global") {
                gosub *add_word_sub
                return
            }
            if ((code_lstate(num_include) == LSTATE_DEFFUNC) | (code_lstate(num_include) == LSTATE_DEFCFUNC)) & (lower_keyword == "local") {
                gosub *add_word_sub
                return
            }
            if ((code_lstate(num_include) == LSTATE_DEFFUNC) | (code_lstate(num_include) == LSTATE_DEFCFUNC)) & (lower_keyword == "global") & ((tmp_stock != "#deffunc") & (tmp_stock != "#defcfunc")) {
                gosub *add_word_sub
                return
            }
            if (tmp_stock == "#define") & (lower_keyword == "ctype") & (code_lstate(num_include) == LSTATE_MACRO) {
                gosub *add_word_sub
                return
            }

            switch code_lstate(num_include)
                case LSTATE_DEFFUNC
                    sql_q "INSERT INTO Cache (Key, Data) VALUES ('" + sqesc(keyword) + "'," + HTX_MU_CMD + ")"
                    swbreak
                case LSTATE_DEFCFUNC
                    sql_q "INSERT INTO Cache (Key, Data) VALUES ('" + sqesc(keyword) + "'," + HTX_MU_FUNC + ")"
                    swbreak
                case LSTATE_MACRO
                    sql_q "INSERT INTO Cache (Key, Data) VALUES ('" + sqesc(keyword) + "'," + HTX_MU_MACRO + ")"
                    code_lstate(num_include) == LSTATE_NORMAL
                    swbreak
            swend
            need_to_add = 0
            return
        }
        add_string keyword
    }
    gosub *first_char_finished
    if (sql_i("Data") == HTX_MU_CMD) : gosub *end_area
    code_pos(num_include) += strlen(keyword)
    return

*add_word_sub
    if markup_prepro {
        add_string tag_start("span", class_prepro) + keyword + tag_end("span")
    } else {
        add_string keyword
    }
    code_pos(num_include) += strlen(keyword)
    return


// 新しい行の開始(マルチステートメントを除く、物理的に新しい行)
*start_new_line
    if code_state(num_include) == STATE_COMMENT {
        gosub *end_singleline_comment
    }
    if (code_state(num_include) == STATE_STRINGS) {
        error == HTX_ERR_CONSTRUCTION
        return
    }
    code_lstate(num_include) = LSTATE_NORMAL
    need_to_add = 0 : need_to_del = 0
    if mode_add_br {
        add_string tag_alone("br")
    }
    add_string "\n"
    if num_include == 0 : code_after_x = 0
    gosub *end_line
    return


*after_cnv
    sql_q "ROLLBACK"
    sql_close
    if error != HTX_ERR_NOERROR {
        // 何らかのエラーが発生した
        code_after = ""
    }
    source = code_after
    sdim code, 4
    sdim code_after, 4
    sdim code_before, 4
    return


#global
#endif
// テスト用
#if 1
#const STEP 5
#const BROGBAR_HEIGHT 22
#include "hsp3util.as"

    oncmd gosub *jumpHTX_MSG
    notesel code

    // 対象となるスクリプトの選択
    dialog "hsp",16
    if stat == 0 : end
    file_name = refstr

    // GUIコンポーネントの配置
    mesbox code, ginfo_winx / 2ginfo_winy - BROGBAR_HEIGHT
    id_mesbox = stat

    progbar ginfo_winxBROGBAR_HEIGHT
    id_progbar = stat
    progbar_set id_progbar, STEP
    prog_step = 0

    pos ginfo_winx / 20
    axobj ie, "Shell.Explorer.2"ginfo_winx / 2ginfo_winy - BROGBAR_HEIGHT
    if stat == -1 {
        dialog "ActiveXコントロールの配置に失敗しました。"1
    }
    id_ie = stat

    // スクリプトのロード
    noteload file_name

    // 変換の準備
    title "変換の準備をしています..."
    htx_init
    if stat == HTX_ERR_NODB {
        dialog "データベースファイルが見つかりません。"1
        end
    } else : if stat == HTX_ERR_NODIR {
        dialog "ディレクトリが見つかりません。"1
        end
    }

    // 変換実行
    title "変換中..."
    htx_cnv code, HTX_MU_ALL | HTX_SEARCH_OTHERgetpath(file_name, 32)  // すべての属性をマークアップする
    if stat == HTX_ERR_NODB {
        dialog "データベースファイルが見つかりません。"1
        end
    } else : if stat == HTX_ERR_NOFILE {
        dialog "インクルードするファイルが見つかりません。"1
        end
    } else : if stat == HTX_ERR_CONSTRUCTION {
        dialog "構文が異常です。解析できません。"1
        end
    }

    title "変換が終了しました。"
    objprm id_mesbox, code
    gosub *make_html
    stop

// 変換の進行度合いによってプログレスバーを進める(100/5=20段階)
*jump
    if lparam == 0 {
        title strf("変換中(%d%%)..."wparam)
        while(prog_step < wparam)
            prog_step += STEP
            progbar_step id_progbar
        wend
    }
    return

// HTMLファイルの作成
*make_html
    ie -> "Navigate" "about:blank"
    doc = ie("Document")

    header = {"<html><head><style TYPE='text/css'>
<!--
body {color : #ffffff;background-color : #000000;}
.comment {color : olive;}
.str {color : #5f9ea0;}
.cmd {color : #c71585;}
.sysvar {color : yellow;}
.macro {color : #cd853f;}
.prepro {color : orange;}
.func {color : #ff0000;}
.label {color : cyan;}
.num {color : #fff0e0;}
-->
</style></head><body><pre>"}

    doc -> "write" header
    doc -> "write" code
    doc -> "write" "</pre></body></html>"
    return
#endif
機能:

  • HTML・XHTML両対応
  • &lt;,&gt;,&amp;への変換
  • 命令やマクロなど要素のマークアップ(任意)
  • マークアップ時のクラス名変更
  • タブのスペースへの変換(任意)
  • タブ幅の指定
  • &nbsp;への変換(任意)
  • 改行タグの追加(任意)
  • 変換進行状況に応じた処理(ウィンドウメッセージの送信を利用)

2 件のコメント:

匿名 さんのコメント...

tag_alone の部分がおかしいです。
こうではありませんか。
#define ctype tag_alone(%1, %2="") _tag_alone(%1, %2)
#defcfunc _tag_alone str tag_name, str class_name
if mode_xhtml {
return _tag_start(tag_name, class_name) + " />"
} else {
return _tag_start(tag_name, class_name) + ">"
}

include したファイルを検索する設定だと user32.as や kernel32.as などを include していると変換に時間がかかってしまいますね。
スクリプトはじっくり読んでいませんが、よく include するファイルのパース結果のデータベースをキャッシュする仕組みがあれば速くなるのではないでしょうか。

eller さんのコメント...

その通りです。訂正させていただきます。m(_ _)m

> Win32API用ヘッダファイル
そうですね。キーワード名とそのタイプ(命令や関数などを識別する値)だけあれば充分なので、導入も簡単そうです。
ただ、データベースをどこに作るかで少し悩みます。HDL用データベース内のデータはhsファイルが更新されると消えてなくなってしまうので、別の場所を検討した方がいいかも知れません。