飴屋ぷろじぇくと

 ちょっとカテゴリを分けてみようかなっと。

--.--.--[--] スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

2014.11.05[水] FreeBASICのワイド文字列が使いにくい件


unicode.png

前に一度触れましたが、FreeBASICの標準データ型でユニコードを扱う文字列型ということになっているWString、これが結構不便なんです。
WString型は基本的に固定長でマニュアルでは可変長にするには自分で確保/拡張/解放(Allocate/Reallocate/Deallocate)するしかないようなことが書かれてますが、これは「BASIC言語としてどうなの?」と思いますよね。
そしてそんな仕様の為、変数宣言は必ずサイズを指定、もしくはポインタとして宣言が強要されます。
Dim w As WString * 16 = "ワイド" 'サイズを指定
Dim z As WString Ptr   'ポインタとして宣言
一応、宣言した長さ以上の文字列をセットすると自動的にそのサイズに収まるように切り詰められるのは「BASICらしい」とも言えますが、この制限のせいなのかリテラル文字列が文脈によってWString扱いになったりWString Ptr扱いになったりと仕様がちょっと歪なことになっています。

しかしこの制限の影響はこれだけにはとどまりません。

例えばプロシージャの引数や返り値にするにも制限があります。
WStringを引数にする場合、参照渡し(ByRef)にするか、値渡し(ByVal)の場合はポインタにしなければいけません。まあこれは百歩譲るとしても、WStringの戻り値がByRefもしくは “ByValのポインタ”だけになるのはいただけません。
どういうことかというと、ローカル変数から値を返せないわけで、返せるのは関数が終わっても存続する領域をもつ文字列だけってことになります。要は、
・後で解放する必要があるAllocateした文字列
・Sharedしたモジュールレベルの文字列
・引数で渡しておいて、その呼び出し側の文字列を返す

この三つしか返せません。使い方が面倒ですよね。
Function Func1(x As WString) As WString
    Dim ret As WString
    'なんか処理
    Return ret
End Function

Dim s As WString = Func1("TestString")
こんなことは出来ないんです。
普通のStringでは出来るのにこれは理不尽ってものです。
ちなみに配列も関数の返り値にできませんので、どうもこのあたりはまだFreeBASICの弱点のようです。
何処かのそう古くない時点で「ユーザー定義型のメンバーに可変長配列を持てるようになった」とあったので、まだこのあたり発展途上なのでしょう。
そう、話はちょと変わりますがFreeBASICには、いや、BASICにはユーザー定義型というものがあって、
Type MyType
     i As Integer
     d As Double
End Type

Cでいう所の構造体みたいなものを定義できます。
FreeBASICではこれを拡張してC++のクラスみたいに出来るようになってます。
Type MyType
    i As Integer
    d As Double
    Declare Sub MyProc(n As Integer)
End Type

Sub MyType.MyProc(n As Integer)
    i = n
End Sub

Dim m As MyType

m.MyProc 123
Print m.i

よくある例ですがこんな感じにできます。
そしてこのユーザー定義型に関してはプロシージャの引数にByRef、ByValの制限はなく、関数の返り値にするにもそんな制限はありません。ローカル変数をReturnも出来てしまいます。

ここでWStringの話に戻りますが、つまりWStringや配列もユーザー定義型で上手く包んでやればあんな面倒な制限から解放することが出来るかもしれないのです。

そこで前の記事の話になるわけですが、あの記事ではいきなり結論だけ紹介してて判り辛かったと思うので改めて解説します。

まずWStringを格納したユーザー定義型を考えてみます。
Type MyString
    st As WString Ptr '可変長なのでポインタ
    Declare Constructor(s As WString)
    Declare Destructor
End Type

Constructor MyString(s As WString)
    st = Allocate((Len(s) + 1) * Len(WString))
    *st = a  '代入できます
End Constructor

Destructor MyString
    If st <> 0 Then
        Deallocate(st)
    End If
End Destructor

こんな感じ。オペレータも定義できます
   Declare Operator Let(s As WString) ' = の定義
   Declare Operator +=(s As WString)
   Declare Operator &=(s As WString)
   Declare Operator +(s As WString)
   Declare Operator &(s As WString)

演算子&は変換を伴うってことなので基本データ型全てのオーバーロードを定義しないといけません。
あと自分と同じ型同士の演算も抜けてましたね。
   Declare Operator &=(u As MyString) 
   Declare Operator &=(n As Byte)
   Declare Operator &=(n As UByte)
   Declare Operator &=(n As Short)
   Declare Operator &=(n As UShort)
   Declare Operator &=(n As Integer)
   Declare Operator &=(n As UInteger)
   Declare Operator &=(n As Long)
   Declare Operator &=(n As ULong)
   '以下略

結構大掛かりです……

が、実はもっと楽ができることに気が付きました。
    Declare Operator Let(s As WString) 
    Declare Operator Cast As ByRef WString

こうやってWStringの代入とWStringへのキャストを定義しておくと、
Dim m As MyString = "すとりんぐ" 'コンストラクタで代入
Dim n As MyString
n = "文字列"  '演算子で代入
m = m + n       'ユーザー定義型どうしで演算!?

こうやって演算させてもコンパイルを通ってしまいます。MyStringどうしの + 演算子は定義していないのにです。
結果もちゃんと二つを結合した文字列になっていました。

これはどうやら、コンパイラが、MyStringがWStringにキャスト可能な事を検出して、WString + WString の関数を呼び出すコードを生成してくれてるようなんです。これが前の記事で「コンパイラが優秀」とした理由です。
これはWStringを引数にする手続きや関数でも同様でした。
Sub MyWStringProc(w As WString) ' 引数がWString
    '何か処理
End Sub

Dim u As MyString = "てすと"
MyWStringProc u  ' ←MyStringを渡している。

これでもコンパイルは通り、実行もちゃんとMyStringに格納した文字列が渡ります。

というわけで最終形はこんなです。
mystring.bas
Type MyString
Private:
    st As WString Ptr
Public:
    Declare Constructor
    Declare Constructor(s As WString)
    Declare Constructor(m As MyString)
    Declare Destructor
    Declare Operator Let(s As WString)
    Declare Operator Let(m As MyString)
    Declare Operator Cast ByRef As WString
    Declare Operator @ As WString Ptr
    Declare Operator [](i As Integer) As Integer
End Type

前の記事ではUShortの配列を使っていましたがこちらの方が実装がすっきりします。
またWindowsとLinuxでワイド文字のサイズが違っているのでその差を吸収するのにもこの方が都合が良いんです。

実装は以下。
mystring.bas
#include "mystring.bi"

Constructor MyString
    st = 0
End Constructor

Constructor MyString(s As WString)
    This = s
End Constructor

Constructor MyString(m As MyString)
    This = m
End Constructor

Destructor MyString
    if st <> 0 Then
        Deallocate(st)
    End If
End Destructor

Operator MyString.Let(s As WString)
    st = Reallocate(st, (Len(s) + 1) * Len(WString))
    *st = s
End Operator

Operator MyString.Let(m As MyString)
    st = Reallocate(st, (Len(*m.st) + 1) * Len(WString))
    *st = *m.st
End Operator

Operator MyString.Cast ByRef As WString
    return *st
End Operator

Operator MyString.@ As WString Ptr
    return st
End Operator

Operator MyString.[](i As Integer) As Integer
    return (*st)[i]
End Operator

これで、MyStringをWStringの代わりに使うことでユニコード文字列の扱いがだいぶ楽になると思います。
(※若干、領域の再確保のあたり、異常系が弱い気がしますがこれはあとで考えます)

でもまだ制限があって、WStringを扱ういくつかの組込み関数が正常動作しません
とりあえず判っているものとして、MidLeftRightはMyStringをそのまま渡すとコンパイルは通るものの、多バイト文字が全て'?'になってしまいます。
これには、@演算子がWStringのポインタを返すようにしたので*@valueのように書いて明示的にWStringで渡すしかないでしょう。
今のFreeBASICの仕様ではこれが限界のようです。

余談ですが、関係ある話。
Windows版FreeBASICでの話ですが、Print命令等、コンソールに出力するステートメントには不具合があってWStringをまともに表示できません。出力文字数の計算に間違いがあるようで、漢字ひらがなカタカナ等多バイト文字を出そうとすると文字列が途中で途切れてしまいます。多分、ワイド文字での文字数分のバイト数(WindowsなのでSJISのバイト数)しか出力してないんだと思います。

さて、以上のように何かと細かいところで足りないFreeBASICですが、基本的な所の実装が頑張って強力な仕様になっているので、今回の記事のように自分で補っていけるのが「強み」とも言えるでしょう。

まあ、それゆえに「よく判ってる人が趣味的に使う言語」とか言われてしまうわけですが……。

Comment

2014.11.06 Thu 23:02  ご参考:テキスト・ファイルの文字コード変換






(編集・削除用)

Trackback

http://utau2009.blog114.fc2.com/tb.php/27-d8810873

この記事にトラックバック(FC2Blog User)

Copyright © 飴屋/菖蒲

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。