しょぼいマシンで自作プログラミング言語を作ってみる (16)
続・fmainブロック内に文字列を式として配置するまで
前回に引き続き、fmainブロック内に記述した文字列リテラルの解析について、説明します。
文字列型、文字型のエスケープシーケンスについて(非文字コード指定)
文字列型において、重要となるのはエスケープシーケンスの扱いですね。
まず、文字コード指定以外のエスケープシーケンスについて、gallop言語が許容するのは以下の通りとなります。
’\a’。ベル文字。
'\f'。ページ送り。
'\n'。改行(ラインフィード)。
'\r'。改行(キャリッジリターン)。
'\t'。水平タブ。
'\v'。垂直タブ。
'\\'。バックスラッシュ1文字の出力。
'\e'。ANSIエスケープの開始文字(0x1b)。ANSIエスケープの仕様については後述します。
'\b'。バックスペース。直前の文字を消去。
'\0'。ヌル文字終端。この文字が現れた時点で後続の文字はなかったものとして扱い、字句解析済トークンはデリミタが現れるまで読み進める。
'\''、'\"'。文字型ならシングルクォート、文字列型ならダブルクォートの前のバックスラッシュを読み捨てる。
文字列型、文字型のエスケープシーケンスについて(文字コード指定)
次に文字コード指定のエスケープシーケンスについては以下の通りです。
'\o'。8進数UTF-8文字コード。最大11文字(\o36443737677)まで指定できる。左詰め。余った文字、0パディングについては後述。
'\x'。16進数UTF-8文字コード。最大8文字(\xf48fbfbf)まで指定できる。左詰め。余った文字、0パディングについては後述。
'\u'。16進数UTF-32文字コード。最大6文字(\x10ffff)まで指定できる。左詰め。余った文字、0パディングについては後述。
この文字コード指定において、苦労したのは8進数の文字コードの解析。0パディング時の処理が面倒でしたね。
0パディング、余った文字について、説明しますね。
8進数指定の場合は2文字までは0パディングできます。3文字以上0パディングされたら、その時点でヌル文字終端扱いとします。
例えば、"\o071111"と指定された場合、まず先頭の071を8ビット分として読み込みます。そして、後続の111を変換対象とするか? というとしません。一見、"071111"を2進数にした場合はパディング分を合わせても17ビット分はあるのですが、UTF-8の2バイト文字のビット列の範囲に収まってないので、先頭の8ビット分のみが対象です。なので、コンパイラを通すと"9111"に変換されます。
16進数指定の場合は、UTF-8文字コード、UTF-32文字コードを問わずに1文字まで0パディングできます。2文字以上0パディングされたら、その時点でヌル文字終端扱いとします。
例えば、"\x09ff";と指定された場合、まず先頭の09を8ビット分として読み込みます。そして、後続のffを変換対象とするか? というとしません。一見、"09ff"を2進数にした場合はパディング分を合わせても16ビット分はあるのですが、UTF-8の2バイト文字のビット列の範囲に収まってないので、先頭の8ビット分のみが対象です。なので、コンパイラを通すと"\tff"に変換されます。
処理の流れとしては、以下のとおりですかね?
UTF-8指定の場合、最大32ビット分、読み込めるだけ読み込んでしまって、ビット列に変換。
あとはUTF-8文字コードのビット列のルールに従って、適合しているか判定します。4バイト文字から判定します。マッチしていれば変換します。していなければ、右から余分な文字を切り落とします。これを繰り返しながら、3バイト文字、2バイト文字、1バイト文字と判定を進めていきます。1バイト文字の判定までいって、判定に失敗した場合は更にビット列を切り詰めてから文字コードに変換します。
UTF-32コード指定もほぼ同様だったりします。ひとまず、最大24ビット分読み込んでしまいます。
あとはUTF-8文字コードに対応するUTF-32文字コードのビット列のルールに従って、判定をしながら文字コードに変換する感じですね。
実際にASTはどうなる?
実際に以下のコードを変換してみましょう。
fmain link {
/# fmain block
// comment block
"\o001767777"
" \\
this is \
string \\ \"\"\'
\a\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\n\s\t\u\v\w\x\y\z
aa\b
\o001013
\x00192343
\u2831 \0 a"
"\o00";
"\x00";
"\u000";
"\o36053724234\o71100201";
"\xf0afa89c\xe39683";
"\u61\u7361e1c";
"\e[38;2;0;0;255maaaa\e[0m";
}
ASTはこんな感じになります。
-- to AST --
ast::Root(File)
=> Module(File)
|-> filename: ../example/bootstrap/fmain_str1.trot
|-> package name: main
|-> module name: fmain_str1
=> fmain
|-> entryPointId: link
=> Control block
|-> beginLocation: [3,12]
|-> endLocation: [22,1]
=> CommentOut For Doc
|-> location: [4,5]
|-> body: fmain block
=> CommentOut
|-> location: [5,5]
|-> body: comment block
=> String
|-> beginLocation: [6,1]
|-> endLocation: [6,13]
|-> value: "767777"
|-> length: 7
=> String
|-> beginLocation: [7,5]
|-> endLocation: [14,13]
|-> value: " \
this is string \ ""\'
\c\d
g\h\i\j\k\l\m
\o\p\q
\s \u
\w\x\y\z
a
013
"
|-> length: 80
=> String
|-> beginLocation: [15,2]
|-> endLocation: [15,7]
=> String
|-> beginLocation: [16,1]
|-> endLocation: [16,6]
=> String
|-> beginLocation: [17,1]
|-> endLocation: [17,7]
=> String
|-> beginLocation: [18,1]
|-> endLocation: [18,25]
|-> value: "鼻䀁"
|-> length: 7
=> String
|-> beginLocation: [19,1]
|-> endLocation: [19,20]
|-> value: "鼻㖃"
|-> length: 7
=> String
|-> beginLocation: [20,1]
|-> endLocation: [20,15]
|-> value: "a獡e1c"
|-> length: 7
=> String
|-> beginLocation: [21,1]
|-> endLocation: [21,27]
|-> value: "aaaa"
|-> length: 23
ちなみにですが、例文の'\e'エスケープシーケンス、文字を青色に変えるんですけれど、llvm::outs()へ通すと、ちゃんと色が変わるんですね。
(参考資料)ANSIエスケープの挙動
上記リンクのページにターミナル上でのANSIエスケープの挙動について、まとめたものがあったりします。
LLVM IRへと出力してみると?
LLVM IRへの変換処理は以下のようになります。
llvm::Value *
AstNodeStringLiteral::generateLlvmIr(IrGenContext *const context_) {
llvm::IRBuilder<> *lBuilder = context_->getBuilder();
llvm::Value *valueP = lBuilder->CreateGlobalStringPtr(value);
return valueP;
};
で、実際にLLVM IRへと出力した結果は以下のとおりになります。
-- ../example/bootstrap/fmain_str1.trot to LLVM IR --
; ModuleID = 'main.fmain_str1'
source_filename = "../example/bootstrap/fmain_str1.trot"
@0 = private unnamed_addr constant [8 x i8] c"\01767777\00", align 1
@1 = private unnamed_addr constant [81 x i8] c" \\\0A this is string \\ \22\22\\'\0A \07\\c\\d\1B\0C\\g\\h\\i\\j\\k\\l\\m\0A\\o\\p\\q\0D\0A\\s\09\\u\0B\\w\\x\\y\\z\0A a\0A\01013\0A\00", align 1
@2 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1
@3 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1
@4 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1
@5 = private unnamed_addr constant [8 x i8] c"\F0\AF\A8\9C\E4\80\81\00", align 1
@6 = private unnamed_addr constant [8 x i8] c"\F0\AF\A8\9C\E3\96\83\00", align 1
@7 = private unnamed_addr constant [8 x i8] c"a\E7\8D\A1e1c\00", align 1
@8 = private unnamed_addr constant [24 x i8] c"\1B[38;2;0;0;255maaaa\1B[0m\00", align 1
define i32 @main() {
entrypoint:
ret i32 0
}
これを以降の開発で利用できるようにするわけですね。うん。
でも、ひとつ面倒な実装が片付いたのでよかったです。
では、今回はここまでです。次回は文字型だったり、整数型だったりについて、触れられればいいかなと思います。