измените функцию так, чтобы конструкции потока управления использовали `{...}` И оставляли комментарии в нужном месте - PullRequest
3 голосов
/ 18 октября 2019

Я хотел бы изменить функцию ввода, чтобы выражения всегда вызывали `{`(), и, таким образом, комментарии оставались в нужном месте.

Вот пример:

input_fun <- function(){

  if(TRUE)
    foo
  else
    # bar
    bar

  if(FALSE) {
    this
    # baz
    baz
    that
  }

  repeat
    while(condition)
      # qux
      qux
}

cat(deparse(input_fun, control = "useSource"),sep ="\n")
#> function(){
#>   
#>   if(TRUE)
#>     foo
#>   else
#>     # bar
#>     bar
#>   
#>   if(FALSE) {
#>     this
#>     # baz
#>     baz
#>     that
#>   }
#>   
#>   repeat
#>     while(condition)
#>       # qux
#>       qux
#> }

Вывод будет следующим output_fun или аналогичным, где подобное означает, что вставка или удаление новых строк до / после { или } не важно и не имеет отступов.

Я также не против потерять комментарии, которые не находятся в отдельной строке (хотя мне немного лучше их оставить).

output_fun <- function(){
  if(TRUE){
    foo
  } else {
    # bar
    bar
  }

  if(FALSE) {
    this
    # baz
    baz
    that
  }

  repeat {
    while(condition){
    # qux
    qux
    }
  }
}

cat(deparse(output_fun, control = "useSource"),sep ="\n")
#> function(){
#>   if(TRUE){
#>     foo
#>   } else {
#>     # bar
#>     bar
#>   }
#>     
#>   if(FALSE) {
#>     this
#>     # baz
#>     baz
#>     that
#>   }
#>   
#>   repeat {
#>     while(condition){
#>     # qux
#>     qux
#>     }
#>   }
#> }

Может быть, что-то можно сделать, сохранив количество конструкций потока управления и открытых скобок, или, может быть, мы должны пройти через дерево разбора функции ввода, отредактировать, чтобы добавить {, и найти способ вернуть комментарии из оригиналаsrcref в нужном месте, но я немного застрял, подойдет любой метод.


edit:

Мы могли бы использовать это:

repair <- function(call){
  if(!is.call(call)) {
    return(call)
  }

  # if
  if(call[[1]] == quote(`if`)) {
    if(!is.call(call[[3]]) || call[[3]][[1]] != quote(`{`)){
      call[[3]] <- as.call(list(quote(`{`), call[[3]]))
    } 
    if(length(call) == 4 && (!is.call(call[[4]]) || call[[4]][[1]] != quote(`{`))){
      call[[4]] <- as.call(list(quote(`{`), call[[4]]))
    }
    call[-1] <- lapply(as.list(call[-1]), repair)
    return(call)
  } 

  # for
  if(call[[1]] == quote(`for`)) {
    if(!is.call(call[[4]]) || call[[4]][[1]] != quote(`{`)){
      call[[4]] <- as.call(list(quote(`{`), call[[4]]))
    } 
    call[-1] <- lapply(as.list(call[-1]), repair)
    return(call)
  } 

  # repeat
  if(call[[1]] == quote(`repeat`)) {
    if(!is.call(call[[2]]) || call[[2]][[1]] != quote(`{`)){
      call[[2]] <- as.call(list(quote(`{`), call[[2]]))
    } 
    call[-1] <- lapply(as.list(call[-1]), repair)
    return(call)
  } 

  # while
  if(call[[1]] == quote(`while`)) {
    if(!is.call(call[[3]]) || call[[3]][[1]] != quote(`{`)){
      call[[3]] <- as.call(list(quote(`{`), call[[3]]))
    } 
    call[-1] <- lapply(as.list(call[-1]), repair)
    return(call)
  } 

  #
  call[] <- lapply(call, repair)
  call  
}

output_fun0 <- input_fun
body(output_fun0) <- repair(body(input_fun))
output_fun0
#> function () 
#> {
#>     if (TRUE) {
#>         foo
#>     }
#>     else {
#>         bar
#>     }
#>     if (FALSE) {
#>         this
#>         baz
#>         that
#>     }
#>     repeat {
#>         while (condition) {
#>             qux
#>         }
#>     }
#> }

1 Ответ

0 голосов
/ 19 октября 2019

Отказ от ответственности: это будет длинным и скрученным

Я приведу здесь улучшенный пример, включающий угловые случаи, и покажу основные шаги.

Функции IИспользуются в нижней части. Они не очень хорошо прокомментированы, поэтому стреляйте, если они вам нужны, отредактированные с пояснениями.

data

input_fun <- function(){

  if(TRUE)
    foo
  else
    # bar_com1
    # bar_com2
    bar({
      x({y})
    }) %in% z

  # if
  if(
    FALSE) {
    this
    # baz_com
    baz
    that
  }

  repeat
    while(condition)
      # qux_com
      qux
}

Solution

Мы вкладываем комментарии в код, пряча их в следующем вызове в качестве первого аргумента `#`() функции

output_fun <- nest_comments(input_fun)
output_fun
#> function () 
#> {
#>     if (TRUE) 
#>         foo
#>     else `#`("    # bar_com1\n    # bar_com2", bar)({
#>         x({
#>             y
#>         })
#>     }) %in% z
#>     `#`("  # if", if (FALSE) {
#>         this
#>         `#`("    # baz_com", baz)
#>         that
#>     })
#>     repeat while (condition) `#`("      # qux_com", qux)
#> }

Мы "исправляем" функцию, добавляя явные вызовы { там, где их нет вконструкции потока управления

body(output_fun) <- repair_call(body(output_fun))
output_fun
#> function () 
#> {
#>     if (TRUE) {
#>         foo
#>     }
#>     else {
#>         `#`("    # bar_com1\n    # bar_com2", bar)({
#>             x({
#>                 y
#>             })
#>         }) %in% z
#>     }
#>     `#`("  # if", if (FALSE) {
#>         this
#>         `#`("    # baz_com", baz)
#>         that
#>     })
#>     repeat {
#>         while (condition) {
#>             `#`("      # qux_com", qux)
#>         }
#>     }
#> }

Возвращаемся к новому дереву разбора и извлекаем вызовы # () в независимые вызовы выше вызова "host"

body(output_fun) <- unnest_comments(body(output_fun))
output_fun
#> function () 
#> {
#>     if (TRUE) {
#>         foo
#>     }
#>     else {
#>         `#`("    # bar_com1\n    # bar_com2")
#>         bar({
#>             x({
#>                 y
#>             })
#>         }) %in% z
#>     }
#>     `#`("  # if")
#>     if (FALSE) {
#>         this
#>         `#`("    # baz_com")
#>         baz
#>         that
#>     }
#>     repeat {
#>         while (condition) {
#>             `#`("      # qux_com")
#>             qux
#>         }
#>     }
#> }

Теперь мы можемиспользуйте regex, чтобы вернуть комментарии к их стандартной форме.

output_fun <- regularize_comments(output_fun)
output_fun
#> function () 
#> {
#>     if (TRUE) {
#>         foo
#>     }
#>     else {
#>         # bar_com1
#>     # bar_com2
#>         bar({
#>             x({
#>                 y
#>             })
#>         }) %in% z
#>     }
#>     # if
#>     if (FALSE) {
#>         this
#>         # baz_com
#>         baz
#>         that
#>     }
#>     repeat {
#>         while (condition) {
#>             # qux_com
#>             qux
#>         }
#>     }
#> }

функции

regularize_comments <- function(fun) {
  env <- environment(fun)
  fun <- deparse(fun)
  #fun <- gsub("(\\s*`#`\\(\")(.*?)\\\"\\)$","\\2", fun)
  fun <- gsub("(\\s*)`#`\\(\"(\\s*)(.*?)\\\"\\)$","\\1\\3", fun)
  fun <- gsub("\\\\n","\n",fun)
  eval(parse(text=paste(fun, collapse = "\n"))[[1]],envir = env)
}
unnest_comments <- function(call) {
  if(!is.call(call)) {
    return(call)
  }

  call0 <- lapply(call, function(x) {
    call_str <- paste(deparse(x), collapse ="\n")
    if(startsWith(call_str, "`#`(")){
      #is.call(x) && x[[1]] == quote(`#`) && length(x) == 3){
      # browser()
      x <- list(extract_comment(x),
                clean_call(x))
    }
    x
  })
  call <- as.call(unlist(call0))
  call[] <- lapply(call, unnest_comments)
  call
}
# helper for unnest_comments
extract_comment <- function(call){
  if(!is.call(call)) {
    return(NULL)
  }
  if(identical(call[[1]], quote(`#`))){
    return(call[1:2])
  }
  unlist(lapply(call, extract_comment))[[1]]
}
# helper for unnest_comments
clean_call <- function(call){
  if(!is.call(call)) {
    return(call)
  }
  if(identical(call[[1]], quote(`#`))){
    return(call[[3]])
  }
  call[] <- lapply(call, clean_call)
  call
}
is_syntactic <- function(x){
  tryCatch({str2lang(x); TRUE},
           error = function(e) FALSE)
}
nest_comments <- function(fun){
  src <- deparse(fun, control = "useSource")
  # positions of comments
  commented_lgl <- grepl("^\\s*#",src)
  # positions of 1st comments of comment blocks
  first_comments_lgl <- diff(c(FALSE, commented_lgl)) == 1
  # ids of comment blocks along the lines
  comment_ids <- cumsum(first_comments_lgl) * commented_lgl
  # positions of 1st lines after comment blocks
  first_lines_lgl <- diff(!c(FALSE, commented_lgl)) == 1
  first_lines_ids <- cumsum(first_lines_lgl) * first_lines_lgl

  # we iterate through these ids, taking max from lines so if code ends with a
  # comment it will be ignored
  for(i in seq(max(first_lines_ids))){
    comments <- src[comment_ids == i]
    line_num <- which(first_lines_ids == i)
    line <- src[line_num]
    # we move forward character by character until we get a syntactic replacement
    # the code replacement starts with "`#`(" and we try all positions of 2nd
    # parenthese until something works, then deal with next code block

    j <- 0
    repeat {
      break_ <- FALSE
      j <- j+1
      line <- src[line_num]
      if(j == 1) code <- paste0("`#`('", paste(comments,collapse="\n"),"', ") else code[j] <- ""
      for(n_chr in seq(nchar(src[line_num]))){
        code[j] <- paste0(code[j], substr(line, n_chr, n_chr))
        if (n_chr < nchar(line))
          code_last_line <- paste0(code[j],")", substr(line, n_chr+1, nchar(line)))
        else
          code_last_line <- paste0(code[j],")")
        #print(code_last_line)
        src_copy <- src
        src_copy[(line_num-j+1):line_num] <- c(head(code,-1), code_last_line)
        if (is_syntactic(paste(src_copy,collapse="\n"))){
          src <- src_copy
          break_ <- TRUE
          break}
      }
      if(break_ || j == 7) break
      line_num <- line_num + 1
    }
  }
  eval(str2lang(paste(src, collapse = "\n")),envir = environment(fun))
}
repair_call <- function(call){
  if(!is.call(call)) {
    return(call)
  }
  # if
  if(call[[1]] == quote(`if`)) {
    if(!is.call(call[[3]]) || call[[3]][[1]] != quote(`{`))
      call[[3]] <- as.call(list(quote(`{`), call[[3]]))
    if(length(call) == 4 && (!is.call(call[[4]]) || call[[4]][[1]] != quote(`{`)))
      call[[4]] <- as.call(list(quote(`{`), call[[4]]))
    call[-1] <- lapply(as.list(call[-1]), repair_call)
    return(call)}
  # for
  if(call[[1]] == quote(`for`)) {
    if(!is.call(call[[4]]) || call[[4]][[1]] != quote(`{`))
      call[[4]] <- as.call(list(quote(`{`), call[[4]]))
    call[-1] <- lapply(as.list(call[-1]), repair_call)
    return(call)}
  # repeat
  if(call[[1]] == quote(`repeat`)) {
    if(!is.call(call[[2]]) || call[[2]][[1]] != quote(`{`))
      call[[2]] <- as.call(list(quote(`{`), call[[2]]))
    call[-1] <- lapply(as.list(call[-1]), repair_call)
    return(call)}
  # while
  if(call[[1]] == quote(`while`)) {
    if(!is.call(call[[3]]) || call[[3]][[1]] != quote(`{`)){
      call[[3]] <- as.call(list(quote(`{`), call[[3]]))
    }
    call[-1] <- lapply(as.list(call[-1]), repair_call)
    return(call)}
  call[] <- lapply(call, repair_call)
  call
}

...