昨天我从Github 的一个问题中了解到 higr包的一个意想不到但有趣的用法。此包用于突出显示 R 代码的语法,但用户希望从给定的 R 代码中识别函数调用。他所做的是首先语法高亮代码,然后在结果中查找 LaTeX 代码\kwd{}
。我告诉他这个任务可以用getParseData()
完成,但是有一些边缘情况。例如:
getParseData(parse(text = c('lapply(1:10, paste)')))
line1 col1 line2 col2 id parent token text 1 1 1 1 6 1 3 SYMBOL_FUNCTION_CALL lapply 2 1 7 1 7 2 20 '(' ( 4 1 8 1 8 4 5 NUM_CONST 1 6 1 9 1 9 6 10 ':' : 7 1 10 1 11 7 8 NUM_CONST 10 9 1 12 1 12 9 20 ',' , 14 1 14 1 18 14 16 SYMBOL paste 15 1 19 1 19 15 20 ')' )
在这种情况下, lapply
被正确识别为SYMBOL_FUNCTION_CALL
,但paste
不是(相反,它被识别为SYMBOL
)。我们可以尝试评估符号并检查它是否是一个函数:
find_funs = function(code) { d = getParseData(parse(text = code)) f = d[d$token == 'SYMBOL_FUNCTION_CALL', 'text'] for (s in d[d$token == 'SYMBOL', 'text']) { tryCatch({ ev = eval(as.symbol(s), parent.frame()) if (is.function(ev)) f = c(f, s) }, error = function(e) NULL) } f }
然后find_funs('lapply(1:10, paste)')
可以同时找到lapply
和paste
。
需要注意的是,这种方法不会评估代码,而只是对其进行解析,因此默认情况下它无法识别附加包中的函数。解决此问题的一种方法是检测library()
或require()
调用并在包中搜索可能的函数名称。这不会完全可靠(例如,对于 case library(x, character.only = TRUE)
)。另一种方法是在尝试检测符号是否为函数之前实际评估代码,这更昂贵。
另一个极端情况是glue::glue()
中的函数调用,例如,在以下情况下的str_to_title
和as.character
:
glue("This number is {str_to_title(as.character(123))}")
我们当然可以检测glue
调用并尝试解析胶水模板。我对走那么远不感兴趣,所以我就到此为止。
除了getParseData()
,我想codetools::makeCodeWalker()
也可能有效。十年前我第一次从 Kohske那里了解到它。