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

前に一度触れましたが、FreeBASICの標準データ型でユニコードを扱う文字列型ということになっているWString、これが結構不便なんです。
WString型は基本的に固定長でマニュアルでは可変長にするには自分で確保/拡張/解放(Allocate/Reallocate/Deallocate)するしかないようなことが書かれてますが、これは「BASIC言語としてどうなの?」と思いますよね。
そしてそんな仕様の為、変数宣言は必ずサイズを指定、もしくはポインタとして宣言が強要されます。
Dim w As WString * 16 = "ワイド" 'サイズを指定
Dim z As 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
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
Dim m As MyString = "すとりんぐ" 'コンストラクタで代入
Dim n As MyString
n = "文字列" '演算子で代入
m = m + n 'ユーザー定義型どうしで演算!?
結果もちゃんと二つを結合した文字列になっていました。
これはどうやら、コンパイラが、MyStringがWStringにキャスト可能な事を検出して、WString + WString の関数を呼び出すコードを生成してくれてるようなんです。これが前の記事で「コンパイラが優秀」とした理由です。
これはWStringを引数にする手続きや関数でも同様でした。
Sub MyWStringProc(w As WString) ' 引数がWString
'何か処理
End Sub
Dim u As MyString = "てすと"
MyWStringProc u ' ←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
また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
(※若干、領域の再確保のあたり、異常系が弱い気がしますがこれはあとで考えます)
でもまだ制限があって、WStringを扱ういくつかの組込み関数が正常動作しません。
とりあえず判っているものとして、Mid、Left、RightはMyStringをそのまま渡すとコンパイルは通るものの、多バイト文字が全て'?'になってしまいます。
これには、@演算子がWStringのポインタを返すようにしたので*@valueのように書いて明示的にWStringで渡すしかないでしょう。
今のFreeBASICの仕様ではこれが限界のようです。
余談ですが、関係ある話。
Windows版FreeBASICでの話ですが、Print命令等、コンソールに出力するステートメントには不具合があってWStringをまともに表示できません。出力文字数の計算に間違いがあるようで、漢字ひらがなカタカナ等多バイト文字を出そうとすると文字列が途中で途切れてしまいます。多分、ワイド文字での文字数分のバイト数(WindowsなのでSJISのバイト数)しか出力してないんだと思います。
さて、以上のように何かと細かいところで足りないFreeBASICですが、基本的な所の実装が頑張って強力な仕様になっているので、今回の記事のように自分で補っていけるのが「強み」とも言えるでしょう。
まあ、それゆえに「よく判ってる人が趣味的に使う言語」とか言われてしまうわけですが……。
Comment
2014.11.06 Thu 23:02 ご参考:テキスト・ファイルの文字コード変換
Ascii → Unicode → Ascii
http://makoto-watanabe.main.jp/freebasic/Window9/A2U2A.html
ENCODING(付録) ShiftJIS
http://makoto-watanabe.main.jp/freebasic/PgEncodingShiftJIS.html
http://makoto-watanabe.main.jp/freebasic/Window9/A2U2A.html
ENCODING(付録) ShiftJIS
http://makoto-watanabe.main.jp/freebasic/PgEncodingShiftJIS.html
