しょぼいマシンで自作プログラミング言語を作ってみる (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
}

 これを以降の開発で利用できるようにするわけですね。うん。
 でも、ひとつ面倒な実装が片付いたのでよかったです。

 では、今回はここまでです。次回は文字型だったり、整数型だったりについて、触れられればいいかなと思います。

いいなと思ったら応援しよう!