メモ:formatRのtidy_source()がやっていること

動作

こんな感じ。

tidy_source(text = "x")
#> x

tidy_source(text = "function(x){x+y}")
#> function(x) {
#>   x + y
#> }

途中にコメントが挟まるとエラーになる。

# これはOK
tidy_source(text = c("iris %>%",
                     "  group_by(Species) # Comment Here"))
#> iris %>% group_by(Species)  # Comment Here

# これはNG
tidy_source(text = c("iris %>%            # Comment Here",
                     "  group_by(Species)"))
#> Error in base::parse(text = code, keep.source = FALSE) : 
#>   <text>:1:10: unexpected SPECIAL
#> 1: iris %>% %InLiNe_IdEnTiFiEr%
#>              ^

動作

仮にこういうやつを考える

txt <- "function(x) {
print(x)
x+1         #comment here
}"

mask_comment()

7. How does tidy_source() actually work?に書かれているように、deparseはコメントを消してしまうので、まずはコメントを保護する。

txt <- formatR:::mask_comments(txt, 40L, TRUE)
txt
#> [1] "function ( x ) {"                            "print ( x )"                                
#> [3] "x + 1 %InLiNe_IdEnTiFiEr% \"#comment here\"" "}"                                          

cat(txt, sep = "\n")
#> function ( x ) {
#> print ( x )
#> x + 1 %InLiNe_IdEnTiFiEr% "#comment here"
#> }

tidy_block()

インデントを付けてひとつの文字列に結合する。この中でdeparse()が使われるが、%inLiNe_IdEnTiFiEr%があるのでコメントは消えない。

txt <- formatR:::tidy_block(txt, 40L, FALSE)
txt
#> [1] "function(x) {\n    print(x)\n    x + 1 %InLiNe_IdEnTiFiEr% \"#comment here\"\n}"

cat(txt, sep = "\n")
#> function(x) {
#>     print(x)
#>     x + 1 %InLiNe_IdEnTiFiEr% "#comment here"
#> }

unmask_source()

マスクしていたコメントを文字列に戻す。

txt <- formatR:::unmask_source(txt)
txt
#> [1] "function(x) {\n    print(x)\n    x + 1  #comment here\n}"

cat(txt, sep = "\n")
#> function(x) {
#>     print(x)
#>     x + 1  #comment here
#> }

reindent_lines()

デフォルトだとインデントは4つのスペースだが、違うインデントを用いる場合はgsub()で置き換える。

感想

これが黒魔術か…という感じ。ASTで解釈しようとしてもコメントはそこに入ってこないので、まあこうするしかないといえばそうなような、別のやり方もあるような。ほかの言語だとどうやるんでしょう。