Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ dependency(
DEPENDENCY_LINK_TARGETS tree-sitter-typescript
)

dependency(
DEPENDENCY_NAME tree-sitter-r
DEPENDENCY_RESOURCE kilo52/tree-sitter-r
DEPENDENCY_VERSION v1.2.0-patch-cmakeliststxt
DEPENDENCY_LINK_TARGETS tree-sitter-r
)

dependency(
DEPENDENCY_NAME utf8proc
DEPENDENCY_RESOURCE JuliaStrings/utf8proc
Expand Down
2 changes: 1 addition & 1 deletion Dependencies.cmake.sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8648e7957901a3029837287b247ad68b2b2dce86ab816eaa4fc18505449f6701
a2eff11c1a578a2fa505398b70101f4b92a544c7bb692daaf612f79204a9342f
6 changes: 6 additions & 0 deletions cmake/ConfigTree-sitter-r.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Configuration options for the Tree-Sitter-R dependency

# Define the program variable to avoid errors when custom command
# is executed since the CLI might not actually be available.
set(TREE_SITTER_CLI "")
set(BUILD_TESTING OFF)
1 change: 1 addition & 0 deletions src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ target_sources(
"c/lang_python.c"
"c/lang_javascript.c"
"c/lang_typescript.c"
"c/lang_r.c"
"c/lang_bash.c"
"c/logical.c"
"c/physical.c"
Expand Down
10 changes: 8 additions & 2 deletions src/lib/c/factories.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ TSParser* createParserJava(void);
TSParser* createParserPython(void);
TSParser* createParserJavaScript(void);
TSParser* createParserTypeScript(void);
TSParser* createParserR(void);
TSParser* createParserBash(void);

void evaluateNodeC(TSNode node, NodeEvalTrace* trace);
void evaluateNodeJava(TSNode node, NodeEvalTrace* trace);
void evaluateNodePython(TSNode node, NodeEvalTrace* trace);
void evaluateNodeJavaScript(TSNode node, NodeEvalTrace* trace);
void evaluateNodeTypeScript(TSNode node, NodeEvalTrace* trace);
void evaluateNodeR(TSNode node, NodeEvalTrace* trace);
void evaluateNodeBash(TSNode node, NodeEvalTrace* trace);

TSParser* createParser(RcnTextFormat language) {
Expand All @@ -48,6 +50,8 @@ TSParser* createParser(RcnTextFormat language) {
return createParserJavaScript();
case RCN_LANG_TYPESCRIPT:
return createParserTypeScript();
case RCN_LANG_R:
return createParserR();
case RCN_LANG_BASH:
return createParserBash();
default:
Expand All @@ -67,6 +71,8 @@ NodeVisitor createEvaluationFunction(RcnTextFormat language) {
return evaluateNodeJavaScript;
case RCN_LANG_TYPESCRIPT:
return evaluateNodeTypeScript;
case RCN_LANG_R:
return evaluateNodeR;
case RCN_LANG_BASH:
return evaluateNodeBash;
default:
Expand All @@ -77,6 +83,7 @@ NodeVisitor createEvaluationFunction(RcnTextFormat language) {
const char* getInlineSourceCommentString(RcnTextFormat language) {
switch (language) {
case RCN_LANG_PYTHON:
case RCN_LANG_R:
case RCN_LANG_BASH:
return "#";
case RCN_LANG_C:
Expand Down Expand Up @@ -158,9 +165,8 @@ SourceFormatDetection detectSourceFormat(const RcnSourceFile* file) {
detection.format = RCN_TEXT_YAML;
} else if (strcmp(extension, "R") == 0
|| strcmp(extension, "r") == 0) {
// isProgrammingLanguage is false for R format due to missing
// support for logical line counting in R files
detection.isSupportedFormat = true;
detection.isProgrammingLanguage = true;
detection.format = RCN_LANG_R;
} else if (strcmp(extension, "txt") == 0) {
detection.isSupportedFormat = true;
Expand Down
105 changes: 105 additions & 0 deletions src/lib/c/lang_r.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright (C) 2026 Raven Computing
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <stdbool.h>

#include "tree_sitter/api.h"

#include "reckon/reckon.h"
#include "reckon_export.h"
#include "evaluation.h"

RECKON_NO_EXPORT const TSLanguage* tree_sitter_r(void);

/**
* These are the symbol identifiers as defined by the R language parser
* of tree-sitter. We have only copied the symbol identifiers that we are
* interested in evaluating or counting. Others do not contribute to the weight
* of a node in the AST.
*/
enum SymbolIdentifiersR {
sym_return = 52,
sym_next = 53,
sym_break = 54,
sym_program = 81,
sym_function_definition = 82,
sym_if_statement = 88,
sym_for_statement = 89,
sym_while_statement = 90,
sym_repeat_statement = 91,
sym_braced_expression = 92,
sym_parenthesized_expression = 93,
sym_call = 94,
sym_unary_operator = 104,
sym_binary_operator = 105,
};

TSParser* createParserR(void) {
TSParser* parser = ts_parser_new();
if (parser) {
if (!ts_parser_set_language(parser, tree_sitter_r())) {
// LCOV_EXCL_START
ts_parser_delete(parser);
return NULL;
// LCOV_EXCL_STOP
}
}
return parser;
}

/**
* Checks whether the given node's immediate parent is the program node
* or a braced expression, i.e. the node is at statement level.
*/
static bool isAtStatementLevel(TSNode node) {
TSNode parent = ts_node_parent(node);
if (ts_node_is_null(parent)) {
return false;
}
TSSymbol sym = ts_node_grammar_symbol(parent);
return sym == sym_program || sym == sym_braced_expression;
}

static RcnCount evaluateNodeWeightRImpl(TSNode node, NodeEvalTrace* trace) {
RcnCount weight = 0;
TSSymbol sym = ts_node_grammar_symbol(node);
switch (sym) {
case sym_binary_operator:
case sym_call:
case sym_unary_operator:
case sym_function_definition:
case sym_parenthesized_expression:
case sym_if_statement:
case sym_for_statement:
case sym_while_statement:
case sym_repeat_statement:
case sym_next:
case sym_break:
case sym_return:
if (isAtStatementLevel(node)) {
weight += 1;
}
break;
default:
break;
}
return weight;
}

void evaluateNodeR(TSNode node, NodeEvalTrace* trace) {
trace->result->count += evaluateNodeWeightRImpl(node, trace);
trace->idx++;
}
7 changes: 7 additions & 0 deletions src/lib/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ add_test_suite(
TEST_SUITE_LINK ${RECKON_TARGET_LIB_OBJ}
)

add_test_suite(
TEST_SUITE_NAME RLanguageUnitTest
TEST_SUITE_TARGET test_lang_r
TEST_SUITE_SOURCE unit/c/test_lang_r.c
TEST_SUITE_LINK ${RECKON_TARGET_LIB_OBJ}
)

add_compile_definitions(
RECKON_TEST_PATH_RES_BASE="${CMAKE_CURRENT_SOURCE_DIR}/res"
)
3 changes: 1 addition & 2 deletions src/lib/tests/integration/c/test_fileio.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,7 @@ void testDetectSourceFormatR(void) {
TEST_ASSERT_NOT_NULL(file);
SourceFormatDetection detection = detectSourceFormat(file);
TEST_ASSERT_TRUE(detection.isSupportedFormat);
// Special case: Missing support for LLC metric.
TEST_ASSERT_FALSE(detection.isProgrammingLanguage);
TEST_ASSERT_TRUE(detection.isProgrammingLanguage);
TEST_ASSERT_EQUAL_INT(RCN_LANG_R, detection.format);
freeSourceFile(file);
}
Expand Down
Loading
Loading