tree-sitter-cobol.patch 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. diff --git a/grammar.js b/grammar.js
  2. index 6d48b47..e700a18 100644
  3. --- a/grammar.js
  4. +++ b/grammar.js
  5. @@ -19,6 +19,7 @@ module.exports = grammar({
  6. $._LINE_COMMENT,
  7. $.comment_entry,
  8. $._multiline_string,
  9. + $._exec_block,
  10. ],
  11. extras: $ => [
  12. @@ -29,20 +30,30 @@ module.exports = grammar({
  13. $._LINE_COMMENT_ALIAS,
  14. $.copy_statement,
  15. $.comment,
  16. + $.directive,
  17. ],
  18. rules: {
  19. - start: $ => repeat(
  20. - choice(
  21. - $.program_definition,
  22. - //optional($.function_definition) //todo
  23. - )
  24. + start: $ => optional(choice(
  25. + repeat1($.program_definition),
  26. + $.copybook_fragment,
  27. + //optional($.function_definition) //todo
  28. + )),
  29. +
  30. + // A standalone copybook (.cpy): data descriptions or procedure code
  31. + // without a program header.
  32. + copybook_fragment: $ => choice(
  33. + $.record_description_list,
  34. + $._procedure_division_contenet
  35. ),
  36. _LINE_COMMENT_ALIAS: $ => alias($._LINE_COMMENT, $.comment),
  37. comment: $ => /\*>[^\n]*/,
  38. + // GnuCOBOL / COBOL-2002 compiler directives: >>SOURCE, >>IF, >>DEFINE, ...
  39. + directive: $ => />>[^\n]*/,
  40. +
  41. program_definition: $ => prec.right(seq(
  42. $.identification_division,
  43. optional($.environment_division),
  44. @@ -60,7 +71,8 @@ module.exports = grammar({
  45. optional(choice(
  46. $.as_literal,
  47. $.is_initial,
  48. - $.is_common)),
  49. + $.is_common,
  50. + $.is_recursive)),
  51. '.')),
  52. repeat(choice(
  53. $.author_section,
  54. @@ -90,6 +102,11 @@ module.exports = grammar({
  55. $._COMMON
  56. ),
  57. + is_recursive: $ => seq(
  58. + optional($._IS),
  59. + $._RECURSIVE
  60. + ),
  61. +
  62. author_section: $ => seq(
  63. $._AUTHOR, '.',
  64. field('comment', repeat1($.comment_entry)),
  65. @@ -102,17 +119,17 @@ module.exports = grammar({
  66. date_written_section: $ => seq(
  67. $._DATE_WRITTEN, '.',
  68. - field('comment', repeat1($.comment_entry)),
  69. + field('comment', repeat($.comment_entry)),
  70. ),
  71. date_compiled_section: $ => seq(
  72. $._DATE_COMPILED, '.',
  73. - field('comment', repeat1($.comment_entry)),
  74. + field('comment', repeat($.comment_entry)),
  75. ),
  76. security_section: $ => seq(
  77. $._SECURITY, '.',
  78. - field('comment', repeat1($.comment_entry)),
  79. + field('comment', repeat($.comment_entry)),
  80. ),
  81. function_definition: $ => seq(
  82. @@ -734,7 +751,7 @@ module.exports = grammar({
  83. file_description: $ => seq(
  84. $.file_type,
  85. $.file_description_entry,
  86. - $.record_description_list
  87. + optional($.record_description_list)
  88. ),
  89. file_type: $ => choice(
  90. @@ -870,12 +887,12 @@ module.exports = grammar({
  91. ),
  92. record_description_list: $ => seq(
  93. - repeat1(seq($.data_description, repeat1('.')))
  94. + repeat1(choice(seq($.data_description, repeat1('.')), prec(1, seq($.exec_statement, optional('.')))))
  95. ),
  96. working_storage_section: $ => seq(
  97. $._WORKING_STORAGE, $._SECTION, '.',
  98. - repeat(seq($.data_description, repeat1('.')))
  99. + repeat(choice(seq($.data_description, repeat1('.')), seq($.exec_statement, optional('.'))))
  100. ),
  101. data_description: $ => choice(
  102. @@ -1070,7 +1087,7 @@ module.exports = grammar({
  103. $.picture_edit,
  104. ),
  105. - picture_x: $ => /([xX](\([0-9]+\))?)+/,
  106. + picture_x: $ => /([xX](\(([0-9]+|[a-zA-Z][a-zA-Z0-9-]*)\))?)+/,
  107. picture_n: $ => /([nN](\([0-9]+\))?)+/,
  108. @@ -1119,6 +1136,11 @@ module.exports = grammar({
  109. seq($.BINARY_SHORT, $.SIGNED),
  110. seq($.BINARY_SHORT, $.UNSIGNED),
  111. $.BINARY_SHORT,
  112. + seq($.BINARY_LONG_LONG, $.SIGNED),
  113. + seq($.BINARY_LONG_LONG, $.UNSIGNED),
  114. + $.BINARY_LONG_LONG,
  115. + $.FLOAT_LONG,
  116. + $.FLOAT_SHORT,
  117. seq($.BINARY_LONG, $.SIGNED),
  118. seq($.BINARY_LONG, $.UNSIGNED),
  119. $.BINARY_LONG,
  120. @@ -1197,7 +1219,7 @@ module.exports = grammar({
  121. )),
  122. value_item: $ => seq(
  123. - $._literal,
  124. + choice($._literal, $.qualified_word),
  125. optional(seq(
  126. $.THRU,
  127. $._literal
  128. @@ -1227,7 +1249,7 @@ module.exports = grammar({
  129. linkage_section: $ => seq(
  130. $._LINKAGE, $._SECTION, '.',
  131. - $.record_description_list
  132. + optional($.record_description_list)
  133. ),
  134. report_section: $ => /report_section/,
  135. @@ -1357,6 +1379,19 @@ module.exports = grammar({
  136. '.'
  137. ),
  138. + exec_statement: $ => $._exec_block,
  139. +
  140. + free_statement: $ => prec.right(seq(
  141. + $._FREE,
  142. + repeat1($._target_x)
  143. + )),
  144. +
  145. + entry_statement: $ => prec.right(seq(
  146. + $._ENTRY,
  147. + field('name', $.string),
  148. + optional(seq($._USING, repeat1($._call_param)))
  149. + )),
  150. +
  151. end_program: $ => prec(1, seq(
  152. $._END_PROGRAM,
  153. $.program_name,
  154. @@ -1364,6 +1399,9 @@ module.exports = grammar({
  155. )),
  156. _statement: $ => choice(
  157. + $.exec_statement,
  158. + $.entry_statement,
  159. + $.free_statement,
  160. $.accept_statement,
  161. $.add_statement,
  162. $.allocate_statement,
  163. @@ -1465,12 +1503,19 @@ module.exports = grammar({
  164. ),
  165. replacing_clause: $ => seq(
  166. + $._REPLACING,
  167. + repeat1($.replacing_pair)
  168. + ),
  169. +
  170. + replacing_pair: $ => seq(
  171. field('leading_or_trailing', optional(choice($.LEADING, $.TRAILING))),
  172. - field('x', choice($.WORD, $.string)),
  173. - optional($._BY),
  174. - field('by', choice($.WORD, $.string)),
  175. + field('x', choice($.pseudo_text, $.WORD, $.string)),
  176. + $._BY,
  177. + field('by', choice($.pseudo_text, $.WORD, $.string)),
  178. ),
  179. + pseudo_text: $ => /==([^=\n]|=[^=\n])*==/,
  180. +
  181. start_statement: $ => seq(
  182. $._START,
  183. field('file_name', $.WORD),
  184. @@ -1670,9 +1715,9 @@ module.exports = grammar({
  185. repeat1($._call_param)
  186. ))),
  187. optional(choice(
  188. - field('returning', seq($._RETURNING, $._identifier),
  189. - field('giving', seq($._GIVING, $._identifier)),
  190. - )))
  191. + field('returning', seq($._RETURNING, $._identifier)),
  192. + field('giving', seq($._GIVING, $._identifier))
  193. + ))
  194. ),
  195. _call_param: $ => choice(
  196. @@ -1902,6 +1947,7 @@ module.exports = grammar({
  197. _evaluate_object: $ => choice(
  198. $.expr,
  199. + prec.right(seq(choice('=', '>', '<', '>=', '<=', $._NOT_EQUAL), $.expr)),
  200. $.ANY,
  201. $.TRUE,
  202. $.FALSE
  203. @@ -1951,6 +1997,10 @@ module.exports = grammar({
  204. expr: $ => prec.left(choice(
  205. seq($.NOT, $.expr),
  206. seq($.expr, choice($.AND, $.OR), $.expr),
  207. + // Abbreviated combined relation: `X < 0 OR > Y` — the subject of the
  208. + // second comparison is implied. (The AND_LT/OR_GT combined tokens the
  209. + // grammar carries for this never win against keyword lexing.)
  210. + seq($.expr, choice($.AND, $.OR), $._comparator, $._expr_calc),
  211. $._expr_bool,
  212. seq("(", $.expr, ")")
  213. )),
  214. @@ -1965,6 +2015,11 @@ module.exports = grammar({
  215. )),
  216. _expr_calc_binary: $ => choice(
  217. + prec.left(1, seq($._expr_calc, $.B_AND, $._expr_calc)),
  218. + prec.left(1, seq($._expr_calc, $.B_OR, $._expr_calc)),
  219. + prec.left(1, seq($._expr_calc, $.B_XOR, $._expr_calc)),
  220. + prec.left(1, seq($._expr_calc, $.B_SHIFT_L, $._expr_calc)),
  221. + prec.left(1, seq($._expr_calc, $.B_SHIFT_R, $._expr_calc)),
  222. prec.left(1, seq($._expr_calc, '+', $._expr_calc)),
  223. prec.left(1, seq($._expr_calc, '-', $._expr_calc)),
  224. prec.left(2, seq($._expr_calc, '**', $._expr_calc)),
  225. @@ -1974,6 +2029,7 @@ module.exports = grammar({
  226. ),
  227. _expr_calc_unary: $ => prec(4, choice(
  228. + seq($.B_NOT, $._expr_calc),
  229. seq('+', $._expr_calc),
  230. seq('-', $._expr_calc),
  231. seq('^', $._expr_calc),
  232. @@ -2753,7 +2809,7 @@ module.exports = grammar({
  233. seq($.TRIM_FUNCTION, '(', $._trim_args, ')', optional($.func_refmod)),
  234. seq($.NUMVALC_FUNC, '(', $._numvalc_args, ')'),
  235. seq($.LOCALE_DT_FUNC, '(', $._locale_dt_args, ')', optional($.func_refmod)),
  236. - seq($.WORD, optional($.func_args)),
  237. + seq($.WORD, optional($.func_args), optional($.func_refmod)),
  238. ))),
  239. func_refmod: $ => choice(
  240. @@ -2868,6 +2924,16 @@ module.exports = grammar({
  241. _BINARY_CHAR: $ => /[bB][iI][nN][aA][rR][yY]-[cC][hH][aA][rR]/,
  242. _BINARY_DOUBLE: $ => /[bB][iI][nN][aA][rR][yY]-[dD][oO][uU][bB][lL][eE]/,
  243. _BINARY_LONG: $ => /[bB][iI][nN][aA][rR][yY]-[lL][oO][nN][gG]/,
  244. + _BINARY_LONG_LONG: $ => /[bB][iI][nN][aA][rR][yY]-[lL][oO][nN][gG]-[lL][oO][nN][gG]/,
  245. + _FREE: $ => /[fF][rR][eE][eE]/,
  246. + B_AND: $ => /[bB]-[aA][nN][dD]/,
  247. + B_OR: $ => /[bB]-[oO][rR]/,
  248. + B_XOR: $ => /[bB]-[xX][oO][rR]/,
  249. + B_NOT: $ => /[bB]-[nN][oO][tT]/,
  250. + B_SHIFT_L: $ => /[bB]-[sS][hH][iI][fF][tT]-[lL][cC]?/,
  251. + B_SHIFT_R: $ => /[bB]-[sS][hH][iI][fF][tT]-[rR][cC]?/,
  252. + _FLOAT_LONG: $ => /[fF][lL][oO][aA][tT]-[lL][oO][nN][gG]/,
  253. + _FLOAT_SHORT: $ => /[fF][lL][oO][aA][tT]-[sS][hH][oO][rR][tT]/,
  254. _BINARY_SHORT: $ => /[bB][iI][nN][aA][rR][yY]-[sS][hH][oO][rR][tT]/,
  255. _BLANK: $ => /[bB][lL][aA][nN][kK]/,
  256. _BLANK_LINE: $ => /[bB][lL][aA][nN][kK]-[lL][iI][nN][eE]/,
  257. @@ -3335,6 +3401,9 @@ module.exports = grammar({
  258. BINARY_CHAR: $ => $._BINARY_CHAR,
  259. BINARY_DOUBLE: $ => $._BINARY_DOUBLE,
  260. BINARY_LONG: $ => $._BINARY_LONG,
  261. + BINARY_LONG_LONG: $ => $._BINARY_LONG_LONG,
  262. + FLOAT_LONG: $ => $._FLOAT_LONG,
  263. + FLOAT_SHORT: $ => $._FLOAT_SHORT,
  264. BINARY_SHORT: $ => $._BINARY_SHORT,
  265. //BLANK: $ => $._BLANK,
  266. BLANK_LINE: $ => $._BLANK_LINE,
  267. @@ -3754,7 +3823,7 @@ module.exports = grammar({
  268. COMPUTATIONAL: $ => $._COMPUTATIONAL,
  269. _COMPUTATIONAL: $ => /[cC][oO][mM][pP][uU][tT][aA][tT][iI][oO][nN][aA][lL]/,
  270. - _NOT_EQUAL: $ => /(!=)|([nN][oO][tT][ \t]+(([eE][qQ][uU][aA][lL])|=))/,
  271. + _NOT_EQUAL: $ => /(!=)|([nN][oO][tT]([ \t]+[eE][qQ][uU][aA][lL]|[ \t]*=))/,
  272. _NOT_LESS: $ => /([nN][oO][tT][ \t]+(<|[lL][eE][sS][sS]))/,
  273. _NOT_GREATER: $ => /([nN][oO][tT][ \t]+(>|[gG][rR][eE][aA][tT][eE][rR]))/,
  274. diff --git a/src/scanner.c b/src/scanner.c
  275. index 6c1ae90..b69cf53 100644
  276. --- a/src/scanner.c
  277. +++ b/src/scanner.c
  278. @@ -1,4 +1,5 @@
  279. #include <tree_sitter/parser.h>
  280. +#include <stdlib.h>
  281. #include <wctype.h>
  282. enum TokenType {
  283. @@ -8,10 +9,21 @@ enum TokenType {
  284. LINE_COMMENT,
  285. COMMENT_ENTRY,
  286. multiline_string,
  287. + EXEC_BLOCK,
  288. };
  289. +// Wide mode: a preprocessor that converts free-format COBOL to fixed format
  290. +// by left-padding every line 7 columns writes the sentinel "CGWIDE" into the
  291. +// sequence area (columns 1-6) of the FIRST line. Free-format lines routinely
  292. +// run past column 72, so when the sentinel is seen the fixed-format right
  293. +// margin (column 73+ = ignored identification area) is pushed out of reach.
  294. +// The one-byte flag is scanner state, carried through serialize/deserialize.
  295. +#define CG_FIXED_WIDTH 72
  296. +#define CG_WIDE_WIDTH 4096
  297. +static const char CG_WIDE_SENTINEL[6] = {'C', 'G', 'W', 'I', 'D', 'E'};
  298. +
  299. void *tree_sitter_COBOL_external_scanner_create() {
  300. - return NULL;
  301. + return calloc(1, 1);
  302. }
  303. static bool is_white_space(int c) {
  304. @@ -31,7 +43,7 @@ char* any_content_keyword[] = {
  305. "procedure division",
  306. };
  307. -static bool start_with_word( TSLexer *lexer, char *words[], int number_of_words) {
  308. +static bool start_with_word( TSLexer *lexer, char *words[], int number_of_words, int width) {
  309. while(lexer->lookahead == ' ' || lexer->lookahead == '\t') {
  310. lexer->advance(lexer, true);
  311. }
  312. @@ -45,7 +57,7 @@ static bool start_with_word( TSLexer *lexer, char *words[], int number_of_words)
  313. while(true) {
  314. // At the end of the line
  315. - if(lexer->get_column(lexer) > 71 || lexer->lookahead == '\n' || lexer->lookahead == 0) {
  316. + if(lexer->get_column(lexer) > width - 1 || lexer->lookahead == '\n' || lexer->lookahead == 0) {
  317. return false;
  318. }
  319. @@ -58,7 +70,7 @@ static bool start_with_word( TSLexer *lexer, char *words[], int number_of_words)
  320. }
  321. if(all_match_failed) {
  322. - for(; lexer->get_column(lexer) < 71 && lexer->lookahead != '\n' && lexer->lookahead != 0;
  323. + for(; lexer->get_column(lexer) < width - 1 && lexer->lookahead != '\n' && lexer->lookahead != 0;
  324. lexer->advance(lexer, true)) {
  325. }
  326. return false;
  327. @@ -94,6 +106,9 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
  328. return false;
  329. }
  330. + char *wide = (char *)payload;
  331. + const int width = (wide && *wide) ? CG_WIDE_WIDTH : CG_FIXED_WIDTH;
  332. +
  333. if(valid_symbols[WHITE_SPACES]) {
  334. if(is_white_space(lexer->lookahead)) {
  335. while(is_white_space(lexer->lookahead)) {
  336. @@ -106,9 +121,20 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
  337. }
  338. if(valid_symbols[LINE_PREFIX_COMMENT] && lexer->get_column(lexer) <= 5) {
  339. + // The sequence area is ignored content — but the free-format
  340. + // preprocessor plants the CGWIDE sentinel here on the first line.
  341. + int match = 0;
  342. while(lexer->get_column(lexer) <= 5) {
  343. + if(match >= 0 && match < 6 && lexer->lookahead == CG_WIDE_SENTINEL[match]) {
  344. + match++;
  345. + } else {
  346. + match = -1;
  347. + }
  348. lexer->advance(lexer, true);
  349. }
  350. + if(match == 6 && wide) {
  351. + *wide = 1;
  352. + }
  353. lexer->result_symbol = LINE_PREFIX_COMMENT;
  354. lexer->mark_end(lexer);
  355. return true;
  356. @@ -132,7 +158,7 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
  357. }
  358. if(valid_symbols[LINE_SUFFIX_COMMENT]) {
  359. - if(lexer->get_column(lexer) >= 72) {
  360. + if(lexer->get_column(lexer) >= width) {
  361. while(lexer->lookahead != '\n' && lexer->lookahead != 0) {
  362. lexer->advance(lexer, true);
  363. }
  364. @@ -143,7 +169,7 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
  365. }
  366. if(valid_symbols[COMMENT_ENTRY]) {
  367. - if(!start_with_word(lexer, any_content_keyword, number_of_comment_entry_keywords)) {
  368. + if(!start_with_word(lexer, any_content_keyword, number_of_comment_entry_keywords, width)) {
  369. lexer->mark_end(lexer);
  370. lexer->result_symbol = COMMENT_ENTRY;
  371. return true;
  372. @@ -152,18 +178,66 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
  373. }
  374. }
  375. + if(valid_symbols[EXEC_BLOCK]) {
  376. + // EXEC (CICS|SQL|DLI|...) ... END-EXEC embedded block. Match the word
  377. + // EXEC (case-insensitive) followed by whitespace, then consume through
  378. + // the next END-EXEC. On any mismatch return false so the internal
  379. + // lexer re-reads the same characters as an ordinary WORD.
  380. + if(lexer->lookahead == 'e' || lexer->lookahead == 'E') {
  381. + const char *kw = "exec";
  382. + int ki = 0;
  383. + while(ki < 4 && (lexer->lookahead == towupper(kw[ki]) || lexer->lookahead == towlower(kw[ki]))) {
  384. + lexer->advance(lexer, false);
  385. + ki++;
  386. + }
  387. + if(ki == 4 && (lexer->lookahead == ' ' || lexer->lookahead == '\t' ||
  388. + lexer->lookahead == '\n' || lexer->lookahead == '\r')) {
  389. + char ring[8] = {0,0,0,0,0,0,0,0};
  390. + while(lexer->lookahead != 0) {
  391. + for(int i = 0; i < 7; ++i) ring[i] = ring[i+1];
  392. + ring[7] = (char)towlower(lexer->lookahead);
  393. + lexer->advance(lexer, false);
  394. + if(ring[0]=='e' && ring[1]=='n' && ring[2]=='d' && ring[3]=='-' &&
  395. + ring[4]=='e' && ring[5]=='x' && ring[6]=='e' && ring[7]=='c') {
  396. + lexer->result_symbol = EXEC_BLOCK;
  397. + lexer->mark_end(lexer);
  398. + return true;
  399. + }
  400. + }
  401. + }
  402. + return false;
  403. + }
  404. + }
  405. +
  406. if(valid_symbols[multiline_string]) {
  407. + int quote = lexer->lookahead;
  408. + if(quote != '"' && quote != '\'') {
  409. + return false;
  410. + }
  411. while(true) {
  412. - if(lexer->lookahead != '"') {
  413. + if(lexer->lookahead != quote) {
  414. return false;
  415. }
  416. lexer->advance(lexer, false);
  417. - while(lexer->lookahead != '"' && lexer->lookahead != 0 && lexer->get_column(lexer) < 72) {
  418. + bool closed = false;
  419. + while(true) {
  420. + while(lexer->lookahead != quote && lexer->lookahead != 0 && lexer->get_column(lexer) < width) {
  421. + lexer->advance(lexer, false);
  422. + }
  423. + if(lexer->lookahead != quote) {
  424. + break;
  425. + }
  426. lexer->advance(lexer, false);
  427. + if(lexer->lookahead == quote) {
  428. + // doubled quote = escaped quote inside the literal
  429. + lexer->advance(lexer, false);
  430. + continue;
  431. + }
  432. + closed = true;
  433. + break;
  434. }
  435. - if(lexer->lookahead == '"') {
  436. + if(closed) {
  437. lexer->result_symbol = multiline_string;
  438. - lexer->advance(lexer, false);
  439. lexer->mark_end(lexer);
  440. return true;
  441. }
  442. @@ -187,7 +261,7 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
  443. }
  444. lexer->advance(lexer, true);
  445. - while(lexer->lookahead == ' ' && lexer->get_column(lexer) < 72) {
  446. + while(lexer->lookahead == ' ' && lexer->get_column(lexer) < width) {
  447. lexer->advance(lexer, true);
  448. }
  449. }
  450. @@ -197,11 +271,19 @@ bool tree_sitter_COBOL_external_scanner_scan(void *payload, TSLexer *lexer,
  451. }
  452. unsigned tree_sitter_COBOL_external_scanner_serialize(void *payload, char *buffer) {
  453. + if(payload && buffer) {
  454. + buffer[0] = *(char *)payload;
  455. + return 1;
  456. + }
  457. return 0;
  458. }
  459. void tree_sitter_COBOL_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
  460. + if(payload) {
  461. + *(char *)payload = (buffer && length >= 1) ? buffer[0] : 0;
  462. + }
  463. }
  464. void tree_sitter_COBOL_external_scanner_destroy(void *payload) {
  465. + free(payload);
  466. }