codefinetuner 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ import ctypes
2
+ import logging
3
+ from pathlib import Path
4
+ from typing import Iterator, Tuple
5
+
6
+ import tree_sitter as ts
7
+ from tree_sitter_language_pack import get_parser
8
+
9
+ from .config import Config
10
+
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def get_custom_tree_sitter_parser(tree_sitter_lib_path: Path, data_language: str) -> ts.Parser:
16
+ if not tree_sitter_lib_path.exists():
17
+ raise FileNotFoundError(f"Library not found: {tree_sitter_lib_path}")
18
+ try:
19
+ lib = ctypes.CDLL(str(tree_sitter_lib_path)) # Load C Dynamic Link Library, makes all the public C functions inside the .dylib file available to be called from the Python script.
20
+ entry_point_func_name = f"tree_sitter_{data_language}"
21
+ lang_func = getattr(lib, entry_point_func_name) # Retrieves the entry point function of the loaded lib
22
+ lang_func.restype = ctypes.c_void_p # Tells ctypes that this function returns a C-style pointer (void *)
23
+ grammar_rules = ts.Language(lang_func()) # Call lang_func() function and wrap the returned raw C pointer into a tree-sitter Language object
24
+ return ts.Parser(grammar_rules)
25
+ except (AttributeError, OSError) as e:
26
+ raise RuntimeError(f"Failed to load custom tree sitter language parser: {e}") from e
27
+
28
+
29
+ def get_tree_sitter_language_pack_parser(data_language: str) -> ts.Parser:
30
+ return get_parser(data_language)
31
+
32
+
33
+ def auto_create_split_paths(config: Config) -> Tuple[list[Path], list[Path], list[Path]]:
34
+ all_file_paths = []
35
+ for filepath in config.raw_data_path.rglob("*"):
36
+ if not filepath.is_file():
37
+ continue
38
+ if filepath.suffix.lower() not in config.data_extensions:
39
+ continue
40
+ all_file_paths.append(filepath)
41
+
42
+ config.rng.shuffle(all_file_paths)
43
+
44
+ num_files = len(all_file_paths)
45
+ if num_files == 0:
46
+ logger.warning(f"No source files found under {config.raw_data_path} with extensions {config.data_extensions}")
47
+ return [], [], []
48
+
49
+ train_end = int(num_files * config.train_ratio)
50
+ eval_end = int(num_files * (config.train_ratio + config.eval_ratio))
51
+
52
+ train_file_paths = all_file_paths[:train_end]
53
+ eval_file_paths = all_file_paths[train_end:eval_end]
54
+ test_file_paths = all_file_paths[eval_end:]
55
+
56
+ logger.info(f"Split {num_files} files into {len(train_file_paths)} train, {len(eval_file_paths)} eval, and {len(test_file_paths)} test files.")
57
+
58
+ return train_file_paths, eval_file_paths, test_file_paths
59
+
60
+
61
+ def _extract_code_blocks_rec(config: Config, node: ts.Node, source_code_utf8: bytes, max_depth: int) -> list[Tuple[bytes, ts.Node]]:
62
+ """
63
+ Recursively extract code blocks (e.g. functions) that are used for FIM example generation later
64
+ """
65
+ if max_depth <= 0:
66
+ return []
67
+
68
+ code_blocks = []
69
+ if node.type in config.tree_sitter_block_types:
70
+ code_utf8 = source_code_utf8[node.start_byte:node.end_byte]
71
+ code_blocks.append((code_utf8, node))
72
+
73
+ for child in node.children:
74
+ child_blocks = _extract_code_blocks_rec(config, child, source_code_utf8, max_depth-1)
75
+ code_blocks.extend(child_blocks)
76
+ return code_blocks
77
+
78
+
79
+ def get_code_blocks_from_paths(config: Config, file_paths: list[Path]) -> Iterator[Tuple[bytes, ts.Node]]:
80
+ """
81
+ Generator function yielding code blocks from files one-by-one.
82
+
83
+ How generators work:
84
+ 1. Call function: get iterator object (doesn't run code yet)
85
+ 2. next(iterator): processes 1 file, yields first block, then pauses
86
+ 3. next(iterator): resumes, yields next block, then pauses
87
+ 4. Repeat until end of code blocks, then the generator is exhausted. Generators can only be used once.
88
+ """
89
+ for path in file_paths:
90
+ if not path.is_file():
91
+ continue
92
+ try:
93
+ source_code_unicode = path.read_text(encoding='utf8')
94
+ except UnicodeDecodeError:
95
+ logger.warning(f"Skipping file '{path}': Not a valid UTF-8 file.")
96
+ continue
97
+
98
+ source_code_utf8 = source_code_unicode.encode('utf8')
99
+ try:
100
+ tree = config.tree_sitter_parser.parse(source_code_utf8)
101
+ except Exception as exc:
102
+ logger.warning(f"Skipping file '{path}': failed to parse with tree-sitter: {exc}")
103
+ continue
104
+ root_node = tree.root_node
105
+
106
+ code_blocks = _extract_code_blocks_rec(config, root_node, source_code_utf8, max_depth=config.max_code_blocks_ast_depth)
107
+ config.rng.shuffle(code_blocks) # shuffle code blocks extracted from a single file
108
+ for block in code_blocks:
109
+ yield block # yields one block at the time
110
+
111
+
112
+ def get_code_blocks_from_auto_split(config: Config) -> Tuple[Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]]]:
113
+ """
114
+ Auto-split source code files from /data into train/eval/test paths, then extract top-level
115
+ code blocks such as functions from each split.
116
+ """
117
+ train_file_paths, eval_file_paths, test_file_paths = auto_create_split_paths(config)
118
+ train_code_blocks_iter = get_code_blocks_from_paths(config, train_file_paths)
119
+ eval_code_blocks_iter = get_code_blocks_from_paths(config, eval_file_paths)
120
+ test_code_blocks_iter = get_code_blocks_from_paths(config, test_file_paths)
121
+ return train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter
122
+
123
+
124
+ def _check_required_directories(root_path: Path, required_dirs: list[str]) -> None:
125
+ missing_paths = []
126
+ for dir_name in required_dirs:
127
+ dir_path = root_path / dir_name
128
+
129
+ if not dir_path.is_dir():
130
+ missing_paths.append(str(dir_path))
131
+
132
+ if missing_paths:
133
+ raise FileNotFoundError(
134
+ f"Required directories under {root_path}. "
135
+ f"Missing directories: {', '.join(missing_paths)}"
136
+ )
137
+
138
+
139
+ def _get_filtered_paths(config: Config, directory: Path) -> list[Path]:
140
+ filtered_paths = []
141
+
142
+ for entry in directory.rglob("*"):
143
+ if not entry.is_file():
144
+ continue
145
+ if entry.suffix.lower() not in config.data_extensions:
146
+ continue
147
+ filtered_paths.append(entry)
148
+
149
+ return filtered_paths
150
+
151
+
152
+ def get_code_blocks_from_manual_split(config: Config) -> Tuple[Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]], Iterator[Tuple[bytes, ts.Node]]]:
153
+ """
154
+ Extract top-level code blocks such as functions from manually split train/eval/test files.
155
+ """
156
+ _check_required_directories(config.raw_data_path, ["train", "eval", "test"])
157
+ train_file_paths = _get_filtered_paths(config, config.raw_data_path / "train")
158
+ eval_file_paths = _get_filtered_paths(config, config.raw_data_path / "eval")
159
+ test_file_paths = _get_filtered_paths(config, config.raw_data_path / "test")
160
+
161
+ train_code_blocks_iter = get_code_blocks_from_paths(config, train_file_paths)
162
+ eval_code_blocks_iter = get_code_blocks_from_paths(config, eval_file_paths)
163
+ test_code_blocks_iter = get_code_blocks_from_paths(config, test_file_paths)
164
+ return train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter
@@ -0,0 +1,180 @@
1
+ import json
2
+ import logging
3
+ from pathlib import Path
4
+ from typing import Iterator, List, Mapping, Tuple
5
+
6
+ from transformers import AutoTokenizer
7
+ import tree_sitter as ts
8
+
9
+ from .config import Config
10
+ from .extract import auto_create_split_paths, get_code_blocks_from_paths
11
+
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def estimate_bytes_per_token_ratio(config: Config, tokenizer: AutoTokenizer, number_of_code_blocks: int) -> float:
17
+ """
18
+ Estimate bytes per token ratio from first `number_of_code_blocks` code blocks in training split.
19
+ Always uses auto-split (ignores config.split_mode).
20
+ Source code is almost entirely ASCII characters (1 char = 1 byte)
21
+ except e.g. specific string literals (printf("π ≈ 3.14159\n");).
22
+ ASCII is a subset of UTF-8, so len(bytes) ≈ character count.
23
+ """
24
+ train_file_paths, _, _ = auto_create_split_paths(config) # no matter the split mode always use the auto split
25
+ total_bytes = 0
26
+ total_tokens = 0
27
+ i = 0
28
+ block_iter = get_code_blocks_from_paths(config, train_file_paths)
29
+ for block in block_iter:
30
+ total_bytes += len(block[0]) # bytes from code block
31
+ tokenized_block = tokenizer(block[0].decode('utf-8')).tokens()
32
+ total_tokens += len(tokenized_block)
33
+ i += 1
34
+ if i >= number_of_code_blocks:
35
+ break
36
+
37
+ if total_tokens <= 0 or total_bytes <= 0:
38
+ raise ValueError("Failed to estimate bytes per token ratio")
39
+ bytes_per_token_ratio = total_bytes / total_tokens
40
+ return bytes_per_token_ratio
41
+
42
+
43
+ def _extract_subblock_ranges(config: Config, node: ts.Node, base_offset: int) -> list[Tuple[int, int]]:
44
+ """
45
+ Recursively depth-first search (DFS) the Abstract syntax tree (AST) and collect all subblock indices.
46
+ Returns subblock ranges relative to the containing code block.
47
+ E.g., a subblock range of (2, 30) means the subblock starts at byte position 2
48
+ and ends at byte position 30, counting from the beginning of the specific code block
49
+ (not the file).
50
+ """
51
+ subblock_ranges = []
52
+
53
+ if node.type in config.tree_sitter_subblock_types:
54
+ relative_start_byte = node.start_byte - base_offset
55
+ relative_end_byte = node.end_byte - base_offset
56
+ subblock_ranges.append((relative_start_byte, relative_end_byte))
57
+ for child in node.children:
58
+ child_subblock_ranges = _extract_subblock_ranges(config, child, base_offset)
59
+ subblock_ranges.extend(child_subblock_ranges)
60
+
61
+ return subblock_ranges
62
+
63
+
64
+ def _filter_subblocks(subblock_ranges: list[Tuple[int, int]], max_bytes_per_subblock: int) -> list[Tuple[int, int]]:
65
+ """
66
+ Discard subblocks that have a larger end index than max_bytes_per_subblock
67
+ """
68
+ subblock_ranges = sorted(subblock_ranges, key=lambda x: x[1]) # sort ranges by end index
69
+ i = 0
70
+ while i < len(subblock_ranges) and subblock_ranges[i][1] <= max_bytes_per_subblock:
71
+ i += 1
72
+ return subblock_ranges[:i]
73
+
74
+
75
+ def _generate_fim_examples_from_code_block(config: Config, code_utf8: bytes, subblock_ranges: list[Tuple[int, int]], bytes_per_token_ratio: float) -> list[bytes]:
76
+ fim_prefix_token_utf8 = config.fim_prefix_token.encode('utf8')
77
+ fim_middle_token_utf8 = config.fim_middle_token.encode('utf8')
78
+ fim_suffix_token_utf8 = config.fim_suffix_token.encode('utf8')
79
+ eos_token_utf8 = config.eos_token.encode('utf8')
80
+
81
+ num_of_subblocks = len(subblock_ranges)
82
+ num_of_fim_examples = max(1, int(num_of_subblocks * config.fim_examples_per_subblock_ratio))
83
+ unique_random_subblock_indices = config.rng.choice(len(subblock_ranges), size=num_of_fim_examples, replace=False)
84
+
85
+ fim_examples = []
86
+
87
+ for idx in unique_random_subblock_indices:
88
+ middle_start_byte = subblock_ranges[idx][0]
89
+ middle_end_byte = subblock_ranges[idx][1]
90
+ middle_bytes_length = middle_end_byte - middle_start_byte
91
+ middle_tokens_length = middle_bytes_length / bytes_per_token_ratio
92
+ # allow only examples within a certain range
93
+ if (middle_tokens_length < config.min_middle_tokens_length) or (middle_tokens_length > config.max_middle_tokens_length):
94
+ continue
95
+
96
+ prefix = code_utf8[:middle_start_byte]
97
+ middle = code_utf8[middle_start_byte:middle_end_byte]
98
+ suffix = code_utf8[middle_end_byte:]
99
+
100
+ fim_example = (
101
+ fim_prefix_token_utf8 + prefix +
102
+ fim_suffix_token_utf8 + suffix +
103
+ fim_middle_token_utf8 + middle +
104
+ eos_token_utf8
105
+ )
106
+
107
+ fim_examples.append(fim_example)
108
+
109
+ return fim_examples
110
+
111
+
112
+ def create_fim_examples(config: Config, code_blocks_iter: Iterator[Tuple[bytes, ts.Node]], bytes_per_token_ratio: float) -> Iterator[bytes]:
113
+ """
114
+ Generator function that takes a generator iterator of code blocks as input,
115
+ generates FIM examples from each code block and returns a generator iterator
116
+ over all created FIM examples.
117
+ """
118
+ for code_block_utf8, node in code_blocks_iter:
119
+ base_offset = node.start_byte
120
+ subblock_ranges = _extract_subblock_ranges(config, node, base_offset)
121
+
122
+ if not subblock_ranges:
123
+ continue
124
+
125
+ max_bytes_per_subblock = int(config.max_token_sequence_length * bytes_per_token_ratio)
126
+
127
+ subblock_ranges = _filter_subblocks(subblock_ranges, max_bytes_per_subblock)
128
+
129
+ code_block_utf8 = code_block_utf8[:max_bytes_per_subblock] # Trunctate code block code if it is larger than bytes_per_code_block, we only consider subblocks inside this range.
130
+
131
+ fim_examples = _generate_fim_examples_from_code_block(config, code_block_utf8, subblock_ranges, bytes_per_token_ratio)
132
+ for fim_example in fim_examples:
133
+ yield fim_example
134
+
135
+
136
+ def _save_tokenized_batch_as_jsonl(file_path: Path, batch: Mapping[str, List[List[int]]]) -> None:
137
+ with open(file_path, 'a', encoding='utf-8') as f:
138
+ batch_size = len(batch['input_ids'])
139
+ for i in range(batch_size):
140
+ input_ids = batch['input_ids'][i]
141
+ attention_mask = batch['attention_mask'][i]
142
+ labels = batch['labels'][i]
143
+
144
+ example = {
145
+ 'input_ids': input_ids,
146
+ 'attention_mask': attention_mask,
147
+ 'labels': labels
148
+ }
149
+ f.write(json.dumps(example, ensure_ascii=False) + '\n') # ensre utf8 encoding
150
+
151
+
152
+ def tokenize_and_save_fim_examples(config: Config, file_path: Path, fim_examples_iter: Iterator[bytes], tokenizer: AutoTokenizer) -> None:
153
+ examples_counter = 0
154
+ batch = []
155
+ for fim_example in fim_examples_iter:
156
+ batch.append(fim_example.decode('utf-8'))
157
+ examples_counter += 1
158
+ if (len(batch) == config.tokenizer_batch_size):
159
+ tokenized_batch = tokenizer(
160
+ batch,
161
+ padding=False,
162
+ return_tensors=None,
163
+ return_attention_mask=True
164
+ )
165
+ tokenized_batch["labels"] = tokenized_batch["input_ids"]
166
+ _save_tokenized_batch_as_jsonl(file_path, tokenized_batch)
167
+ batch = []
168
+
169
+ # last batch is smaller than config.tokenizer_batch_size, the "rest"
170
+ if batch:
171
+ tokenized_batch = tokenizer(
172
+ batch,
173
+ padding=False,
174
+ return_tensors=None,
175
+ return_attention_mask=True
176
+ )
177
+ tokenized_batch["labels"] = tokenized_batch["input_ids"]
178
+ _save_tokenized_batch_as_jsonl(file_path, tokenized_batch)
179
+
180
+ logger.info(f"Processed and saved {examples_counter} FIM examples to {file_path}")
@@ -0,0 +1,133 @@
1
+ import argparse
2
+ import logging.config
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from transformers import AutoTokenizer
7
+
8
+ from .config import Config
9
+ from .extract import get_code_blocks_from_auto_split, get_code_blocks_from_manual_split
10
+ from .process import create_fim_examples, estimate_bytes_per_token_ratio, tokenize_and_save_fim_examples
11
+
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def _setup_logger(log_level: str) -> None:
17
+ """
18
+ Configure the root logger ("") to capture logs from the entry point (__main__),
19
+ all internal sub-modules, and third-party libraries via a single handler.
20
+ """
21
+ logger_config = {
22
+ "version": 1,
23
+ "disable_existing_loggers": False,
24
+ "formatters": {
25
+ "standard": {
26
+ "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
27
+ }
28
+ },
29
+ "handlers": {
30
+ "stderr_handler": {
31
+ "level": log_level,
32
+ "class": "logging.StreamHandler",
33
+ "formatter": "standard",
34
+ }
35
+ },
36
+ "loggers": {
37
+ "": { # "" corresponds to root logger
38
+ "handlers": ["stderr_handler"],
39
+ "level": log_level,
40
+ "propagate": False,
41
+ }
42
+ }
43
+ }
44
+ logging.config.dictConfig(logger_config)
45
+
46
+
47
+ def _parse_args() -> argparse.Namespace:
48
+ parser = argparse.ArgumentParser(description="Preprocess code dataset for FIM fine-tuning.")
49
+ parser.add_argument(
50
+ "--config",
51
+ type=Path,
52
+ required=True,
53
+ help="YAML config file path with 'preprocess:' section. All Config.MISSING fields must be provided."
54
+ )
55
+ parser.add_argument(
56
+ "--log-level",
57
+ type=str,
58
+ default="INFO",
59
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
60
+ help="Logging level.",
61
+ )
62
+ return parser.parse_args()
63
+
64
+
65
+ def _clear_existing_datasets(config: Config) -> None:
66
+ for file in [config.train_dataset_path, config.eval_dataset_path, config.test_dataset_path]:
67
+ if file.exists():
68
+ file.unlink()
69
+ logger.info(f"Deleted old dataset: {file}")
70
+
71
+
72
+ def _validate_and_configure_tokenizer(config: Config, tokenizer: AutoTokenizer):
73
+ required_tokens = [
74
+ config.fim_prefix_token,
75
+ config.fim_middle_token,
76
+ config.fim_suffix_token,
77
+ config.fim_pad_token,
78
+ config.eos_token
79
+ ]
80
+
81
+ for token in required_tokens:
82
+ token_id = tokenizer.convert_tokens_to_ids(token)
83
+
84
+ if token_id == tokenizer.unk_token_id or token_id is None:
85
+ raise ValueError(f"Token '{token}' defined in Config is missing from Tokenizer vocabulary.")
86
+
87
+ tokenizer.pad_token = config.fim_pad_token
88
+ tokenizer.eos_token = config.eos_token
89
+
90
+ logger.info("Tokenizer validated and configured successfully.")
91
+
92
+
93
+ def run(config: Config) -> None:
94
+ _clear_existing_datasets(config)
95
+
96
+ if config.split_mode == "auto":
97
+ logger.info("Using auto-generated dataset split.")
98
+ train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter = get_code_blocks_from_auto_split(config)
99
+ else: # config.split_mode == "manual":
100
+ logger.info("Using manual dataset split from directories.")
101
+ train_code_blocks_iter, eval_code_blocks_iter, test_code_blocks_iter = get_code_blocks_from_manual_split(config)
102
+
103
+ tokenizer = AutoTokenizer.from_pretrained(config.model_name)
104
+ _validate_and_configure_tokenizer(config, tokenizer)
105
+
106
+ bytes_per_token_ratio = estimate_bytes_per_token_ratio(config, tokenizer, number_of_code_blocks=20000)
107
+ logger.info(f"Estimated bytes_per_token_ratio: {bytes_per_token_ratio}")
108
+
109
+ train_fim_examples_iter= create_fim_examples(config, train_code_blocks_iter, bytes_per_token_ratio)
110
+ eval_fim_examples_iter = create_fim_examples(config, eval_code_blocks_iter, bytes_per_token_ratio)
111
+ test_fim_examples_iter = create_fim_examples(config, test_code_blocks_iter, bytes_per_token_ratio)
112
+
113
+ tokenize_and_save_fim_examples(config, config.train_dataset_path, train_fim_examples_iter, tokenizer)
114
+ tokenize_and_save_fim_examples(config, config.eval_dataset_path, eval_fim_examples_iter, tokenizer)
115
+ tokenize_and_save_fim_examples(config, config.test_dataset_path, test_fim_examples_iter, tokenizer)
116
+
117
+ logger.info("Saved train, eval, test datasets to disk")
118
+
119
+
120
+ def main() -> None:
121
+ user_args = _parse_args()
122
+ _setup_logger(user_args.log_level)
123
+ try:
124
+ preprocess_config = Config.load_from_yaml(user_args.config)
125
+ run(preprocess_config)
126
+ except Exception:
127
+ logger.exception(f"Preprocessing failed")
128
+ sys.exit(1)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()
133
+
@@ -0,0 +1,189 @@
1
+ {
2
+ "c": {
3
+ "block_types": [
4
+ "function_definition",
5
+ "struct_specifier",
6
+ "union_specifier",
7
+ "enum_specifier"
8
+ ],
9
+ "subblock_types": [
10
+ "compound_statement",
11
+ "parameter_list",
12
+ "declaration",
13
+ "expression_statement",
14
+ "if_statement",
15
+ "while_statement",
16
+ "for_statement",
17
+ "switch_statement",
18
+ "case_statement",
19
+ "return_statement",
20
+ "field_declaration_list",
21
+ "field_declaration",
22
+ "enumerator_list",
23
+ "enumerator"
24
+ ]
25
+ },
26
+ "python": {
27
+ "block_types": [
28
+ "function_definition",
29
+ "class_definition",
30
+ "decorated_definition",
31
+ "async_function"
32
+ ],
33
+ "subblock_types": [
34
+ "block",
35
+ "parameters",
36
+ "assignment",
37
+ "expression_statement",
38
+ "if_statement",
39
+ "while_statement",
40
+ "for_statement",
41
+ "match_statement",
42
+ "case_clause",
43
+ "return_statement",
44
+ "decorated_definition",
45
+ "try_statement",
46
+ "except_clause",
47
+ "finally_clause",
48
+ "with_statement",
49
+ "lambda",
50
+ "yield"
51
+ ]
52
+ },
53
+ "cpp": {
54
+ "block_types": [
55
+ "function_definition",
56
+ "class_specifier",
57
+ "struct_specifier",
58
+ "union_specifier",
59
+ "enum_specifier",
60
+ "template_declaration",
61
+ "namespace_definition"
62
+ ],
63
+ "subblock_types": [
64
+ "compound_statement",
65
+ "parameter_list",
66
+ "declaration",
67
+ "expression_statement",
68
+ "if_statement",
69
+ "while_statement",
70
+ "for_statement",
71
+ "switch_statement",
72
+ "case_statement",
73
+ "return_statement",
74
+ "field_declaration_list",
75
+ "field_declaration",
76
+ "enumerator_list",
77
+ "enumerator",
78
+ "try_statement",
79
+ "catch_statement",
80
+ "finally_clause"
81
+ ]
82
+ },
83
+ "java": {
84
+ "block_types": [
85
+ "method_declaration",
86
+ "class_declaration",
87
+ "interface_declaration",
88
+ "enum_declaration",
89
+ "record_declaration",
90
+ "constructor_declaration"
91
+ ],
92
+ "subblock_types": [
93
+ "block",
94
+ "formal_parameters",
95
+ "local_variable_declaration",
96
+ "expression_statement",
97
+ "if_statement",
98
+ "while_statement",
99
+ "for_statement",
100
+ "enhanced_for_statement",
101
+ "switch_expression",
102
+ "switch_rule",
103
+ "case_label",
104
+ "return_statement",
105
+ "throw_statement",
106
+ "catch_clause",
107
+ "finally_clause",
108
+ "yield_statement",
109
+ "try_statement",
110
+ "try_with_resources_statement",
111
+ "synchronized_statement",
112
+ "static_initializer"
113
+ ]
114
+ },
115
+ "vhdl": {
116
+ "block_types": [
117
+ "entity_declaration",
118
+ "architecture_body",
119
+ "process_statement",
120
+ "package_declaration",
121
+ "configuration_declaration",
122
+ "generate_statement"
123
+ ],
124
+ "subblock_types": [
125
+ "signal_assignment",
126
+ "variable_assignment",
127
+ "if_statement",
128
+ "case_statement",
129
+ "loop_statement",
130
+ "wait_statement",
131
+ "assert_statement",
132
+ "component_instantiation",
133
+ "concurrent_statement",
134
+ "sequential_statement"
135
+ ]
136
+ },
137
+ "bash": {
138
+ "block_types": [
139
+ "function_definition",
140
+ "compound_statement",
141
+ "subshell",
142
+ "pipeline",
143
+ "list"
144
+ ],
145
+ "subblock_types": [
146
+ "command",
147
+ "variable_assignment",
148
+ "redirected_statement",
149
+ "expression",
150
+ "assignment",
151
+ "test_command",
152
+ "negated_command",
153
+ "break_statement",
154
+ "continue_statement",
155
+ "return_statement",
156
+ "command_substitution",
157
+ "process_substitution",
158
+ "arithmetic_expansion",
159
+ "if_statement",
160
+ "while_statement",
161
+ "for_statement",
162
+ "c_style_for_statement",
163
+ "case_statement"
164
+ ]
165
+ },
166
+ "mojo": {
167
+ "block_types": [
168
+ "function_definition",
169
+ "struct_definition",
170
+ "trait_definition",
171
+ "class_definition",
172
+ "enum_definition"
173
+ ],
174
+ "subblock_types": [
175
+ "compound_statement",
176
+ "parameter_list",
177
+ "declaration",
178
+ "expression_statement",
179
+ "return_statement",
180
+ "assignment",
181
+ "augmented_assignment",
182
+ "try_statement",
183
+ "catch_clause",
184
+ "finally_clause",
185
+ "decorated_definition"
186
+ ]
187
+ }
188
+ }
189
+