ラベル 文字列操作・解析 の投稿を表示しています。 すべての投稿を表示
ラベル 文字列操作・解析 の投稿を表示しています。 すべての投稿を表示

2008年5月17日土曜日

自力型変換(int⇔str)

整数値と文字列を相互に変換します。HSPでは特に気にせず利用できる機能ですが、自力でやると結構面白いものです。

数値の桁数を計算するためにHSPMathのlog10関数を使用。おまけとしてTrim関数がついてます。

2008.05.17 memcpyをなくすことで計量化
// 自力で型変換
//     2008.05.17 計量化
#module
#include "hspmath.as"
// 文字列の始めと終わりにある半角スペースを除去
#defcfunc Trim str s, local target, local target_length, local result
    target = s
    target_length = strlen( target )
    repeat target_length
        start_idx = cnt
        if peek( target, start_idx ) != ' ' : break
    loop
    repeat target_length, 1
        end_idx = target_length - cnt
        if peek( target, end_idx ) != ' ' : break
    loop
    result = strmid( target, start_idx, end_idx + 1 - start_idx )
    return result

#define ctype not_digit(%1) ((%1) < '0' | '9' < (%1))

// 文字列を整数値に変換
#defcfunc str2int str s, local target, local result, local target_length
    result = 0
    target = Trim( s )
    if peek( target ) = '-' : sign = -1 : else : sign = 1
    if peek( target ) = '-' | peek( target ) = '+' {
        target = strmid( target, 1strlen( target ) - 1 )
    }
    target_length = strlen( target )

    for i, iStart, target_length
        if not_digitpeek( target, i ) ) : _break
        result = result * 10 + peek( target, i ) - '0'
    next
    return result * sign

// 整数値を文字列に変換
#defcfunc int2str int p, local target, local result, local result_length
    if p = 0 : return "0"

    target = abs( p )
    result_length = 1 + log10( target ) + ( p < 0 )
    sdim result, result_length + 1
    repeat 1 + log10( target ), 1
        poke result, result_length - cnt, ( target \ 10 ) + '0'
        target /= 10
    loop
    if p < 0 {
        poke result, 0'-'
    }
    return result
#global

2008年3月29日土曜日

文字列のセンタリング

どう書く?orgより。
文字列の長さに応じて、ifで分岐しています。もっとスマートに記述できないものでしょうか?

/*
    どう書く?org より
        文字列のセンタリング
        http://ja.doukaku.org/87/
*/


#runtime "hsp3cl"
#module
#defcfunc spaces int len
    if len <= 0 : return ""

    sdim s, len + 1
    repeat len
        poke s, cnt' '
    loop
    return s

#defcfunc centered_text str target, int len
    if len <= 0 : return ""

    target_length = strlen(target)
    result = target
    if target_length < len {
        result = spaces((len - target_length)/2) + target + spaces((len - target_length + 1)/2)
    } else : if target_length > len {
        result = strmid(result, (target_length - len)/2, len)
    }
    return result
#global

    s = "*"
    repeat 10
        mes centered_text(s, 15)
        s += " *"
    loop
    stop


追記。
ifによる条件分岐をlimit関数に置き換えました。

/*
    どう書く?org より
        文字列のセンタリング
        http://ja.doukaku.org/87/
*/

#runtime "hsp3cl"

#module
#defcfunc centered_text str _source, int len
    if len <= 0 : return ""
    source = _source

    source_strlen = strlen(source)
    sdim result, len + 1
    memset result, ' ', len
    poke result, limit((len - source_strlen)/20, len/2), strmid(source, limit((source_strlen - len)/20, source_strlen), len)
    return result
#global

    s = "*"
    repeat 10
        mes centered_text(s, 15)
        s += " *"
    loop
    stop

2008年2月12日火曜日

アンドゥや再生ができるペイントツール

GoFのCommandパターンにヒントを得て作成。
描画した履歴を文字列型配列変数に記録しておき、必要に応じて取り出します。
今回は単純な文字列型配列変数ではなく、スタックのモジュールを用意してみました。

bregexp.dll(bregonig.dll)および月影ともさんのbregexp.hspが必要です。
// 文字列用スタック
#module string_stack stack, max
#modinit
    max = 0
    sdim stack, 3210
    return
#deffunc new_sstack array v
    newmod v, string_stack@
    return
#modfunc push str s
    stack(max) = s
    max++
    return
#defcfunc pop modvar string_stack@
    if max == 0 {
        logmes "引数の値が異常です。"
        return ""
    }
    max--
    return stack(max)
#defcfunc get_length modvar string_stack@
    return max
#defcfunc get_last modvar string_stack@
    return stack(max-1)
#defcfunc get_at modvar string_stack@, int index
    if index < 0 || max <= index {
        logmes "引数の値が異常です。"
        return ""
    }
    return stack(index)
#modfunc clear_stack
    max = 0
    return
#global

// 矩形の塗りつぶし
// http://rpen.blogspot.com/2007/11/blog-post.html
#include "gdi32.as"
#module
#const FLOODFILLSURFACE 1
#deffunc fill int x, int y
    current_color = ginfo_rginfo_gginfo_b
    CreateSolidBrush (ginfo_b << 16) | (ginfo_g << 8) | ginfo_r
    if stat {
        hBrush = stat
    } else {
        dialog "ブラシの生成に失敗しました。プログラムを終了します。"1
        end
    }
    SelectObject hDC, hBrush
    pget x, y
    ExtFloodFill hdc, x, y, (ginfo_b << 16) | (ginfo_g << 8) | ginfo_rFLOODFILLSURFACE
    DeleteObject hBrush
    color current_color(0), current_color(1), current_color(2)
    return
#global

// 命令を解析して描画するモジュール
#include "bregexp.hsp"
#module drawer
#define ctype result(%1int(_result(%1))
#deffunc draw str _cmd
    cmd = _cmd
    BSplit _result, cmd, "m/[ ,]+/"
    switch _result(0)
        case "moveTo" : pos result(1), result(2) : swbreak
        case "lineTo" : line result(1), result(2) : swbreak
        case "color"  : color result(1), result(2), result(3) : swbreak
        case "fill"   : fill result(1), result(2) : redraw 1 : swbreak
        default : logmes "未知の命令です" : swbreak
    swend
    return
#deffunc draw_all array cmds, int wait_time
    redraw 0
    color 255255255 : boxf
    color
    repeat get_length(cmds)
        draw get_at(cmds, cnt)
        if wait_time {
            redraw 1
            wait wait_time
            redraw 0
        }
    loop
    redraw 1
    return
#global

#define push_and_do(%1,%2push %1%2 : draw %2

#define WM_MOUSEMOVE    $00000200
#define WM_LBUTTONDOWN  $00000201
#define WM_LBUTTONUP    $00000202
#define WM_RBUTTONDOWN  $00000204

*init
    title "左ドラッグで線を描画 / 右クリックで塗りつぶし"
    oncmd gosub *onLButtonDownWM_LBUTTONDOWN
    oncmd gosub *onRButtonDownWM_RBUTTONDOWN
    oncmd gosub *onLButtonUpWM_LBUTTONUP
    oncmd gosub *onMouseMoveWM_MOUSEMOVE

    objsize 80
    button gosub "color change"*color_change
    button gosub "redraw slowly"*all_draw_slowly
    button gosub "clear"*clear
    button gosub "undo"*undo
    new_sstack cmds
    stop

// 色の変更
*color_change
    hsvcolor rnd(192), 255255
    push_and_do cmds, "color " + ginfo_r + "," + ginfo_g + "," + ginfo_b
    return
// 全消去
*clear
    clear_stack cmds
    draw_all cmds, 0
    return
// アンドゥ
*undo
    tmp = pop(cmds)
    draw_all cmds, 0
    return
// すべて描画
*all_draw
    draw_all cmds, 0
    return
// ゆっくりとすべて描画
*all_draw_slowly
    oncmd 0
    gosub *invalidate_buttons
    draw_all cmds, 4
    gosub *validate_buttons
    oncmd 1
    return
// 左ドラッグ開始
*onLButtonDown
    dragging = 1
    push_and_do cmds, "moveTo " + mousex + "," + mousey
    return
// 左ドラッグ終了
*onLButtonUp
    dragging = 0
    return
// 左ドラッグ中
*onMouseMove
    if dragging {
        push_and_do cmds, "lineTo " + mousex + "," + mousey
    }
    return
// 右クリック
*onRButtonDown
    if dragging == 0 {
        push_and_do cmds, "fill " + mousex + "," + mousey
    }
    return

#include "obj.as"
*invalidate_buttons
    repeat 4
        objgray cnt0
    loop
    return
*validate_buttons
    repeat 4
        objgray cnt1
    loop
    return

2008年2月5日火曜日

正規表現でHTMLの見出しを抽出する

bregonig.dllを使いたくて「ヘッダファイル作ろうかなー」と考えていたら既に月影ともさん(と猫太さん)が作成されていました。
COMによる正規表現は何かと不便なので、今後重宝しそうです。
リンク先を変更。[08/05/03]

// つーさのくーかん「物置 > HSP3 > BREGEXP.hsp」
// http://tu-sa.net/0360

#runtime "hsp3cl"
#include "bregexp.hsp"
    dialog "htm;*.html"16"見出しを抽出するHTMLファイル"
    if stat == 0 : end

    notesel file
    noteload refstr
    position = 0
    margin_left = "-""--""---""----""-----""------"
    repeat
        result = BMatch(file, position, "m#<[hH]([1-6])[^>]*>(.*)</[hH]\\1>#k")
        if result == -1 : break

        mes margin_left(int(BMGetStr(1))-1) + BMGetStr(2)
        position += BMGetNextPos()
    loop
    stop

2008年1月24日木曜日

複数行文字列の行数を取得する(2)

FUJIさんのご指摘を受けて、HSP標準のnotemaxとほぼ同等の動作をするモジュール。

参考:複数行文字列の行数を取得する

// 行数取得モジュール
// 変数の型チェックは行っていないので注意
#module
// instr()を利用した行数の取得
#defcfunc get_lines_num1 var p1
    result = 0
    repeat strlen(p1)
        result++
        ins = instr(p1, cnt"\n")
        if ins == -1 : break
        continue cnt + ins + 2
    loop
    return result
// 正規表現を利用した行数の取得
#defcfunc get_lines_num2 var p1
    if vartype(com_regexp) != vartype("comobj") {
        // comオブジェクト型変数の初期化
        newcom com_regexp, "VBScript.RegExp"
        comres com_result
        com_regexp("Pattern") = "\\r\\n"
        com_regexp("Global") = 1
    }
    com_regexp->"Execute" p1
    result = com_result("Count") + 1    // 行数 = 改行の個数 + 1
    s = strmid(p1, -12)
    if s == "\n" | s == "" : result--   // 最後が空行ならその行をカウントしない
    return result
// notemaxを利用した行数の取得
#defcfunc get_lines_num3 var p1
    notesel p1
    result = notemax
    noteunsel
    return result
#global
    sdim s, 323
    s(0) = "Hot\nSoup\nProcessor"
    s(1) = "sample\nstrings\n"
    s(2) = ""
    foreach s
        mes get_lines_num1(s(cnt))
        mes get_lines_num2(s(cnt))
        mes get_lines_num3(s(cnt))
        mes "***"
    loop
    stop

2008年1月23日水曜日

複数行文字列の行数を取得する

notemaxのように複数行文字列の行数を取得します。
正規表現版はパターンを"(\\r\\n)+"に変更することで、空行を無視するようにもできます。
#module
// instr()を利用した行数の取得
#defcfunc get_lines_num var p1
    result = 0
    repeat
        result++
        ins = instr(p1, cnt"\n")
        if ins == -1 : break
        continue cnt + ins + 2
    loop
    return result
// 正規表現を利用した行数の取得
#defcfunc get_lines_num2 var p1
    if vartype(com_regexp) != vartype("comobj") {
        // comオブジェクト型変数の初期化
        newcom com_regexp, "VBScript.RegExp"
        comres com_result
        com_regexp("Pattern") = "\\r\\n"
        com_regexp("Global") = 1
    }
    com_regexp->"Execute" p1
    return com_result("Count") + 1      // 行数 = 改行の個数 + 1
#global

    s = "Hot\nSoup\nProcessor\n\n"
    mes get_lines_num(s)
    mes get_lines_num2(s)
    stop

2008年1月17日木曜日

「ふっかつのじゅもん」っぽい暗号

昔のゲームに出てきそうなひらがな(64種)による暗号です。
ひらがな1文字(16ビット)で6ビットの情報が表現できますので、情報量は16/6=約2.7倍になります。妙にビット演算が多いので、単純な目的の割に複雑なスクリプトになっています。

カタカナも取り入れれば7ビットの情報が表現できるはずです。また、半角カタカナを使えば情報量を8/6=約1.3倍に抑えられます半角カタカナは濁点・半濁点の表現に1ビット要するので一概には言えません
復元時にinstrによる線形検索を行っているなど、速度上の課題はまだまだあるでしょう。
// 文字列型暗号作成スクリプト

// 参考:テキストボックスの編集監視
//      http://lhsp.s206.xrea.com/hsp_object2.html#4

#define ctype HIWORD(%1) (%1 >> 16 & 0xFFFF)
#module
#include "hspmath.as"
#const SPELL_KEY 0
// モジュールの初期化
#deffunc init_spell
    // 文字列型暗号の素 64文字
    sdim base, 64 * 2 + 1
    base  = "あいうえおかきくけこがぎぐげごさしすせそざじずぜぞ"    // 25文字
    base += "たちつてとだぢづでどなにぬねのはひふへほばびぶべぼ"    // 25 + 25 = 50文字
    base += "まみむめもやゆよらりるれろん"                          // 50 + 14 = 64文字
    return

// 指定した位置(ビット)から6ビットを取り出すマクロ
// バッファオーバーフローに注意
#define ctype peek6(%1%2=0) ((((peek(%1, (%2) / 8) << 8) | peek(%11 + (%2) / 8)) >> (10 - (%2) \ 8)) & 0x3F)

// 指定した位置(ビット)から6ビット書き込む命令
// バッファオーバーフローに注意
#deffunc poke6 var target, int index, int value
    i  = (peek(target, index / 8) << 8) | peek(target, index / 8 + 1)
    i ^= peek6(target, index) << (10 - (index \ 8))
    i |= (value & 0x3F) << (10 - (index \ 8))
    poke target, index / 8, (i >> 8) & 0xFF
    poke target, index / 8 + 1, i & 0xFF
    return

// パスワードから通常の文字列データへ変換
#deffunc spell2str var result, str _before
    before = _before
    len = strlen(before)
    randomize SPELL_KEY
    repeat len / 2
        code = (instr(base, 0strmid(before, cnt * 22)) / 2) ^ rnd(0x40)
        poke6 result, cnt * 6, code
    loop
    poke result, int(ceil(8.0 * len / 6.0)), 0
    return

// 通常の文字列データからパスワードに変換
#deffunc str2spell var result, str _before
    before = _before
    randomize SPELL_KEY
    // 対象となる文字列から6ビットずつ切りだし、ひらがなに変換
    for i, 0strlen(before) * 86
        // XOR演算によって より暗号っぽく
        wpoke result, i / 3wpeek(base, (peek6(before, i) ^ rnd(0x40)) * 2)
    next
    poke result, i / 30
    return
#global
    init_spell


    screen 0240200
    // 元の文字を代入する文字列型変数
    sdim before, 512
    // 文字列型暗号が代入される文字列型変数
    sdim result, 1536
    // テキストボックス作成
    mesbox before, ginfo_winxginfo_winy / 2, , 250
    hInput = objinfo_hwnd(stat)
    mesbox result, ginfo_winxginfo_winy / 2
    idResult = stat
    sendmsg objinfo_hwnd(idResult), $CF1  // 編集無効

    oncmd gosub *command0x0111            // WM_COMMAND
    stop

// テキストボックスの編集を監視する
*command
    if lparam == hInput {
        if HIWORD (wparam) = 0x300 {
            // 編集された場合は文字列型暗号を作って表示
            str2spell result, before
            objprm idResult, result
/*          // この行の"/*"を消すとタイトルに復元した文字列を表示
            sdim s, 512
            spell2str s, result
            title s
//*/

        }
    }
    return

2008年1月13日日曜日

数式の分解

正規表現を使って数式を分解し、文字列型配列変数に代入します。
日本語が使えないのが難点です。


関連:インタプリンタ電卓もどき
#runtime "hsp3cl"
#module
// 正規表現を利用した数式の分解
// 英数字およびアンダースコア・半角丸かっこと各種演算子のみ使用可能(日本語は無視)
#deffunc split_calc array result, str exp
    newcom oReg, "VBScript.RegExp"
    comres oMatches
    oReg("Global") = 1

    oReg("Pattern") = "[0-9\\.]+|\\+|-|\\*|/|%|=|\\w*\\(|\\)|\\w+"
    oReg -> "Execute" exp

    sdim result, 16, oMatches("Count")
    bracket_l = 0 : bracket_r = 0
    repeat oMatches("Count")
        oMatch = oMatches("Item"cnt)
        result(cnt) = oMatch("Value")
        s = strmid(result(cnt), -11)
        if s == "(" : bracket_l++ : else : if s == ")" : bracket_r++
    loop
    return bracket_l != bracket_r
#global

    exp = "s(r) = r * r * 3.14"
    mes exp + "\n"

    // 数式を分解
    split_calc result, exp
    if stat : mes "括弧の数が不正です。"

    // 結果の表示
    foreach result
        mes result(cnt)
    loop
    stop

2008年1月7日月曜日

APIを利用して英語を日本語に翻訳するモジュール

英語を日本語に翻訳します。
ネットに接続するため、少し時間がかかります。
// 英語->日本語変換サンプル
// 翻訳APIを使用
//     http://muumoo.jp/news/2007/05/09/0translationapi.html
// 参考:mod_rss.as

#module mod_translate
#deffunc rss2load_init@mod_translate
    newcom oDom,"Microsoft.XMLDOM"
    oDom("async") = "FALSE"
    comres elm_desc
    return

#deffunc rss2load@mod_translate array desc, str url, int p_max
    oDom->"load" url
    oRoot = oDom("documentElement")
    if varuse(oRoot) == 0 : return 1
    if oRoot("tagName") != "rss" : return 2

    maxnum = p_max
    if maxnum <= 0 : maxnum = 5

    oDom->"getElementsByTagName" "description"
    max = limit(elm_desc("length"), 1, maxnum)

    sdim desc, 64, max
    repeat max
        node = elm_desc("item"cnt)
        node2 = node("firstChild")
        desc(cnt) = node2("nodeValue")
    loop

    return 0

#deffunc rss2load_clean onexit
    if vartype(oRoot) == vartype("comobj") {
        delcom node : delcom node2 : delcom oRoot
    }
    delcom elm_desc : delcom oDom
    return

// 英文を和訳します。変換に成功するとstatに0が代入され、第1引数の変数に変換結果が代入されます。
// 変換に失敗するとstatに1が代入されます。
#deffunc eng2jp var result, str before, local after
    rss2load after, "http://pipes.yahoo.com/poolmmjp/ej_translation_api?_render=rss&text=" + before
    if (stat == 0)&(length(after) == 2) {
        // after(0)にはAPIの説明が代入されている
        result = after(1)
        return 0
    } else {
        return 1
    }
#global
    rss2load_init@mod_translate
// モジュールここまで

    target = "Good morning! How are you today?""I'm fine, thank you. And you?""So so."
    foreach target
        mes target(cnt)
        eng2jp result, target(cnt)
        if stat == 0 : mes "-> " + result
    loop
    stop

2007年12月26日水曜日

文字列の分割と結合

ActionScript3(AS3)にあるメソッド、split(文字列の分割)とjoin(文字型配列変数の結合)をHSP標準の文字列操作命令で実装。
splitの方は正規表現で何とかできそうな気がしますが……VBScriptのRegExpにはsplitメソッドが備わっていないようなので、少し難しいのでしょうか。ただいま調査中です。

区切り文字の数が多く、さらに区切り文字で終わる文字列をsplitできなかった不具合を修正。
#module
// 文字列の分割
#deffunc split array arr, str target, str devider
    if devider = "" {
        // 分割する目印は空文字であってはならない
        return 1
    }

    dim part_strlen, 10             // 分割結果の長さ
    part_strlen_max = 0             // 分割結果の長さの最大値
    parts_num = 1                   // 分割結果の数
    _target = target                // instr,strmidを使うために変数に代入する
    target_strlen = strlen(target)  // 分割する文字列の長さ
    devider_strlen = strlen(devider)// 分割する目印の長さ

    // いくつに分割できるか調べる
    repeat
        ins = instr(_target, cnt, devider)
        if ins == -1 {
            part_strlen(parts_num - 1) = target_strlen - cnt
            if part_strlen_max < part_strlen(parts_num - 1) {
                part_strlen_max = part_strlen(parts_num - 1)
            }
            break
        } else {
            part_strlen(parts_num - 1) = ins
            if part_strlen_max < ins {
                part_strlen_max = ins
            }
            parts_num++
            continue cnt + ins + devider_strlen
        }
    loop

    // 分割結果代入用の配列を確保
    sdim arr, part_strlen_max + 1, parts_num

    // 分割の開始
    position = 0
    foreach arr
        arr(cnt) = strmid(_target, position, part_strlen(cnt))
        position += part_strlen(cnt) + devider_strlen
    loop
    return 0

// 文字列の結合
#deffunc join var result, array target, str connecter
    if vartype(target) != 2 {
        // 結合する配列変数は文字列型でなければならない
        return 1
    }

    connecter_strlen = strlen(connecter)
    // 連結結果の長さを調べる
    result_strlen = connecter_strlen * (length(target) - 1)
    repeat length(target)
        target_strlen(cnt) = strlen(target(cnt))
        result_length += target_strlen(cnt)
    loop

    // 連結結果代入用の変数を確保
    sdim result, result_length + 1

    // 連結の開始
    position = 0
    foreach target
        if cnt {
            poke result, position, connecter
            position += connecter_strlen
        }
        poke result, position, target(cnt)
        position += target_strlen(cnt)
    loop
    return 0

// ちょっと遠回りな置換
#deffunc replace var target, str before, str after
    split tmparr, target, before
    if stat : return stat
    join target, tmparr, after
    return stat
#global

// サンプルスクリプト
    font msgothic14
    s = "Hot Soup Processor"
    mes s

    mes "\n半角スペースで分割"
    split arr, s, " "
    foreach arr
        mes "{"+cnt+"}:"+arr(cnt)
    loop

    mes "\nエクスクラメーションマークで結合"
    join result, arr, "!"
    mes result

    mes "\nsplitとjoinを組み合わせで置換"
    replace s, " ""!"
    mes s
    stop

2007年12月25日火曜日

正規表現を利用したURL&メールアドレスのタグ付け

テキストに含まれるURLやメールアドレスにリンクタグ(aタグ)をつけます。
当初は1バイトずつ切りだして判定する方法を利用していたのですが、分かりづらい上に作りにくかったので正規表現を利用したものに切り替えました。ロジックを書かなくて済む分、かなりシンプルなスクリプトになっています。独自のWikiやBBSなどに利用できるかもしれません。
// 正規表現を利用したURL&メルアドのタグ付け
#module
// 置換処理のメイン部分
#deffunc do_cnv str before, var after, str pattern, str replace
    newcom regexp, "VBScript.RegExp"
    comres after
    regexp("Pattern") = pattern
    regexp("Global") = 1
    regexp -> "Replace" before, replace
    delcom regexp
    return

// URLのタグ付け
#defcfunc add_link str before
    do_cnv before, after, "(http://[-/.~_#0-9a-zA-Z]+)""<a href=\"$1\">$1</a>"
    return after

// メールアドレスのタグ付け
#defcfunc add_mailto str before
    do_cnv before, after, "([-/.~_0-9a-zA-Z]+@[-.~_0-9a-zA-Z]+)""<a href=\"mailto:$1\">$1</a>"
    return after
#global

// URLにリンク(日本語ドメイン非対応)
    before = {"文字列中のURL(URI)にリンクします
HSPTV!(http://hsp.tv/)
HSP開発wiki(http://hspdev-wiki.net/)
http://だけではリンクされません"}

    after1 = add_link(before)
    mesbox after1, ginfo_winxginfo_winy / 2

// メールアドレスにリンク
    before = {"文字列中のメールアドレスにリンクします
例えばsample2007@yaboo.co.jpとか!"}

    after2 = add_mailto(before)
    mesbox after2, ginfo_winxginfo_winy / 2
    stop

2007年12月16日日曜日

高速な文字列の結合(連結)

pokeとmemexpandを利用した文字列の結合(連結)。
HSP標準の+=やnoteaddよりも高速に実行することができます。

このモジュール最大の利点は、memexpandによる変数の自動拡張にあります。(X)HTMLコンバータのように出力結果の大きさが分からないスクリプトで重宝するでしょう。
サンプルスクリプトのように決まった文字列を結合(連結)し続ける場合(リフレイン)、この命令よりもFujiさんのブログの記事で紹介されている方法の方がより高速です。

先日の(X)HTML変換スクリプトの他、開発wikiの置換モジュールでも似たような処理が使われています。

#module
#const EXPAND_SIZE 1024
// 文字列の初期値を設定する
#deffunc set_string str str_to_set
    string_length = strlen(str_to_set)
;   string_size = EXPAND_SIZE * (1 + string_length / EXPAND_SIZE)
    string_size = 64            // 測定のために公平化
    sdim string, string_size
    poke string, 0, str_to_set
    return

// 文字列を返す
#deffunc get_string var target
    target = string
    return

// 文字列の長さを返す
#defcfunc get_string_length
    return string_length

// 文字列を連結する
#deffunc add_string str str_to_add
    len = strlen(str_to_add)
    if string_size <= string_length + len {
        string_size += EXPAND_SIZE
        memexpand string, string_size
    }
    poke string, string_length, str_to_add
    string_length += len
    return
#global

#const TRIAL_TIME 10000
#uselib "winmm.dll"
    #cfunc timeGetTime "timeGetTime"

    mes "充分なサイズを確保せずに文字列の結合(連結)を実行。"

    mes "独自命令版計測中……"
    set_string ""
    time_start = timeGetTime()
    repeat TRIAL_TIME
        add_string "sample string\n"
    loop
    time(0) = timeGetTime() - time_start
    mes strf("独自命令を使用:%d[ms]", time(0))
    mes "独自命令版計測終了。\n"

    mes "+=使用版計測開始……"
    string = ""
    time_start = timeGetTime()
    repeat TRIAL_TIME
        string += "sample string\n"
    loop
    time(1) = timeGetTime() - time_start
    mes strf("標準の+=を使用:%d[ms]", time(1))
    mes "+=使用版計測終了。\n"

    mes "noteadd使用版計測開始……"
    notesel string
    sdim string, 64
    time_start = timeGetTime()
    repeat TRIAL_TIME
        noteadd "sample string"
    loop
    time(2) = timeGetTime() - time_start
    mes strf("noteaddを使用:%d[ms]", time(2))
    mes "noteadd使用版計測終了。"
    stop

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;への変換(任意)
  • 改行タグの追加(任意)
  • 変換進行状況に応じた処理(ウィンドウメッセージの送信を利用)