├── test ├── samples │ └── hello.zig └── valid.py ├── .gitignore ├── binding.gyp ├── package.json ├── Cargo.toml ├── README.md ├── LICENSE ├── .github └── workflows │ └── ci.yml └── grammar.js /test/samples/hello.zig: -------------------------------------------------------------------------------- 1 | test { 2 | // a = 1; 3 | // const abc = &.{ 1, 2, 3, 4 }; 4 | // _ = abc; 5 | 6 | if (1 == 1) { 7 | @compileLog("bruh"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache 2 | zig-out 3 | node_modules 4 | 5 | build 6 | test/invalid_outputs 7 | 8 | parser.exp 9 | parser.lib 10 | parser.obj 11 | 12 | # Tree-sitter generated stuff we don't want in here 13 | src 14 | bindings 15 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "tree_sitter_zig_binding", 5 | "include_dirs": [ 6 | " repeat(choice( 22 | seq($.test_decl), 23 | seq($.comptime_decl), 24 | seq(optional($.doc_comment), optional(field("pub", $.pub)), $.decl) 25 | )); 26 | 27 | const container_members = $ => seq(container_declarations($), repeat(seq($.container_field, ",")), choice($.container_field, container_declarations($))); 28 | 29 | // Lists 30 | 31 | const identifier_list = $ => seq(repeat(seq(optional($.doc_comment), $.identifier, ",")), optional(seq(optional($.doc_comment), $.identifier))); 32 | 33 | const switch_prong_list = $ => seq(repeat(seq($.switch_prong, ",")), optional($.switch_prong)); 34 | 35 | const asm_output_list = $ => seq(repeat(seq($.asm_output_item, ",")), optional($.asm_output_item)); 36 | 37 | const asm_input_list = $ => seq(repeat(seq($.asm_input_item, ",")), optional($.asm_input_item)); 38 | 39 | const string_list = $ => seq(repeat(seq($.string_literal, ",")), optional($.string_literal)); 40 | 41 | const param_decl_list = $ => seq(repeat(seq($.param_decl, ",")), optional($.param_decl)); 42 | 43 | const expr_list = $ => seq(repeat(seq($.expr, ",")), optional($.expr)); 44 | 45 | module.exports = grammar({ 46 | name: "zig", 47 | 48 | externals: (_) => [], 49 | inline: (_) => [], 50 | extras: ($) => [/\s/, $.line_comment], 51 | // TODO: Investigate these - can we fix them? 52 | conflicts: ($) => [[$.root], [$.container_decl_auto]], 53 | 54 | rules: { 55 | root: $ => seq(optional($.container_doc_comment), container_members($)), 56 | 57 | // *** Keywords *** 58 | pub: _ => "pub", 59 | anyframe: _ => "anyframe", 60 | unreachable: _ => "unreachable", 61 | 62 | // *** Top level *** 63 | test_decl: $ => seq("test", optional(field("name", choice($.string_literal_single, $.identifier))), $.block), 64 | 65 | comptime_decl: $ => seq("comptime", $.block), 66 | 67 | decl: $ => choice( 68 | seq(optional(choice("export", seq("extern", optional($.string_literal_single)), choice("inline", "noinline"))), $.fn_proto, choice(";", $.block)), 69 | seq(optional(choice("export", seq("extern", optional($.string_literal_single)))), optional("threadlocal"), $.var_decl), 70 | seq("usingnamespace", $.expr, ";") 71 | ), 72 | 73 | fn_proto: $ => seq("fn", optional($.identifier), "(", param_decl_list($), ")", optional($.byte_align), optional($.addr_space), optional($.link_section), optional($.call_conv), optional("!"), $._type_expr), 74 | var_decl: $ => seq(choice("const", "var"), field("name", $.identifier), optional(seq(":", field("type", $._type_expr))), optional($.byte_align), optional($.addr_space), optional($.link_section), optional(seq("=", field("init", $.expr))), ";"), 75 | container_field: $ => choice( 76 | seq(optional($.doc_comment), optional("comptime"), $.identifier, optional(seq(":", $._type_expr)), optional($.byte_align), optional(seq("=", $.expr))), 77 | seq(optional($.doc_comment), optional("comptime"), optional(seq($.identifier, ":")), $._type_expr, optional($.byte_align), optional(seq("=", $.expr))), 78 | ), 79 | 80 | // *** Block Level *** 81 | statement: $ => prec(precedence.curly, choice( 82 | seq(optional("comptime"), $.var_decl), 83 | seq("comptime", $.block_expr_statement), 84 | seq("nosuspend", $.block_expr_statement), 85 | seq("suspend", $.block_expr_statement), 86 | seq("defer", $.block_expr_statement), 87 | seq("errdefer", optional($.payload), $.block_expr_statement), 88 | seq($.if_statement), 89 | seq($.labeled_statement), 90 | seq($.switch_expr), 91 | seq(choice($.assign_expr, $.expr), ";"), 92 | )), 93 | 94 | if_statement: $ => choice( 95 | seq($.if_prefix, $.block_expr, optional(seq("else", optional($.payload), $.statement))), 96 | seq($.if_prefix, choice($.assign_expr, $.expr), choice(";", seq("else", optional($.payload), $.statement))) 97 | ), 98 | 99 | labeled_statement: $ => prec(precedence.curly, seq(optional($.block_label), choice($.block, $.loop_statement))), 100 | loop_statement: $ => seq(optional("inline"), choice($.for_statement, $.while_statement)), 101 | 102 | for_statement: $ => choice( 103 | seq($.for_prefix, $.block_expr, optional(seq("else", $.statement))), 104 | seq($.for_prefix, choice($.assign_expr, $.expr), seq(choice(";", seq("else", $.statement)))), 105 | ), 106 | 107 | while_statement: $ => choice( 108 | seq($.while_prefix, $.block_expr, optional(seq("else", optional($.payload), $.statement))), 109 | seq($.while_prefix, choice($.assign_expr, $.expr), seq(choice(";", seq("else", optional($.payload), $.statement)))), 110 | ), 111 | 112 | block_expr_statement: $ => choice( 113 | $.block_expr, 114 | seq(choice($.assign_expr, $.expr), ";"), 115 | ), 116 | 117 | block_expr: $ => prec(precedence.curly, seq(optional($.block_label), $.block)), 118 | 119 | // *** Expression Level *** 120 | 121 | assign_expr: $ => prec(precedence.assign, seq($.expr, $.assign_op, $.expr)), 122 | 123 | expr: $ => choice($.binary_expr, $._prefix_expr, $._primary_expr), 124 | 125 | binary_expr: $ => { 126 | const table = [ 127 | [precedence.or, "or"], 128 | [precedence.and, "and"], 129 | [precedence.comparative, $.compare_op], 130 | [precedence.bitwise, $.bitwise_op], 131 | [precedence.bitshift, $.bit_shift_op], 132 | [precedence.addition, $.addition_op], 133 | [precedence.multiply, $.multiply_op], 134 | ]; 135 | 136 | return choice( 137 | ...table.map(([precedence, operator]) => 138 | prec.left( 139 | precedence, 140 | seq( 141 | $.expr, 142 | operator, 143 | $.expr 144 | ) 145 | ) 146 | ) 147 | ); 148 | }, 149 | 150 | _prefix_expr: $ => prec.left(precedence.prefix, seq(repeat($.prefix_op), $._primary_expr)), 151 | 152 | _primary_expr: $ => prec.right(precedence.primary, choice( 153 | $.asm_expr, 154 | $.if_expr, 155 | seq("break", optional($.break_label), optional($.expr)), 156 | seq("comptime", $.expr), 157 | seq("nosuspend", $.expr), 158 | seq("continue", optional($.break_label)), 159 | seq("resume", $.expr), 160 | seq("return", optional($.expr)), 161 | seq(optional($.block_label), $.loop_expr), 162 | $.block, 163 | $._curly_suffix_expr, 164 | )), 165 | 166 | if_expr: $ => prec.right(seq($.if_prefix, $.expr, optional(seq("else", optional($.payload), $.expr)))), 167 | 168 | block: $ => seq("{", repeat($.statement), "}"), 169 | 170 | loop_expr: $ => choice( 171 | prec(2, seq("inline", choice($.for_expr, $.while_expr))), 172 | seq(optional("inline"), choice($.for_expr, $.while_expr)), 173 | ), 174 | 175 | for_expr: $ => prec.right(seq($.for_prefix, $.expr, optional(seq("else", $.expr)))), 176 | 177 | while_expr: $ => prec.right(seq($.while_prefix, $.expr, optional(seq("else", optional($.payload), $.expr)))), 178 | 179 | _curly_suffix_expr: $ => prec.right(precedence.curly, seq($._type_expr, optional($.init_list))), 180 | 181 | init_list: $ => choice( 182 | seq("{", $.field_init, repeat(seq(",", $.field_init)), optional(","), "}"), 183 | seq("{", $.expr, repeat(seq(",", $.expr)), optional(","), "}"), 184 | seq("{", "}"), 185 | ), 186 | 187 | _type_expr: $ => seq(repeat($.prefix_type_op), choice($.error_union_expr, $._suffix_expr)), 188 | 189 | error_union_expr: $ => prec.right(2, seq($._suffix_expr, seq("!", $._type_expr))), 190 | 191 | _suffix_expr: $ => prec.right(choice( 192 | seq("async", $._primary_type_expr, repeat($._suffix_op), $.fn_call_arguments), 193 | seq($._primary_type_expr, repeat(choice($._suffix_op, $.fn_call_arguments))), 194 | )), 195 | 196 | _primary_type_expr: $ => prec(2, choice( 197 | seq($.builtin_identifier, $.fn_call_arguments), 198 | $.char_literal, 199 | $.container_decl, 200 | seq(".", $.identifier), 201 | seq(".", $.init_list), 202 | $.error_set_decl, 203 | $.float, 204 | $.fn_proto, 205 | $.grouped_expr, 206 | $.labeled_type_expr, 207 | $.identifier, 208 | $.if_type_expr, 209 | $.integer, 210 | seq("comptime", $._type_expr), 211 | seq("error", ".", $.identifier), 212 | $.anyframe, 213 | $.unreachable, 214 | $.string_literal, 215 | $.switch_expr, 216 | )), 217 | 218 | container_decl: $ => seq(optional(choice("extern", "packed")), $.container_decl_auto), 219 | 220 | error_set_decl: $ => seq("error", "{", identifier_list($), "}"), 221 | 222 | grouped_expr: $ => seq("(", $.expr, ")"), 223 | 224 | if_type_expr: $ => prec.right(seq($.if_prefix, $._type_expr, optional(seq("else", optional($.payload), $._type_expr)))), 225 | 226 | labeled_type_expr: $ => choice( 227 | seq($.block_label, $.block), 228 | seq(optional($.block_label), $.loop_type_expr), 229 | ), 230 | 231 | loop_type_expr: $ => choice( 232 | prec(2, seq("inline", choice($.for_type_expr, $.while_type_expr))), 233 | seq(optional("inline"), choice($.for_type_expr, $.while_type_expr)), 234 | ), 235 | 236 | for_type_expr: $ => prec.right(seq($.for_prefix, $._type_expr, optional(seq("else", $._type_expr)))), 237 | 238 | while_type_expr: $ => prec.right(seq($.while_prefix, $._type_expr, optional(seq("else", optional($.payload), $._type_expr)))), 239 | 240 | switch_expr: $ => seq("switch", "(", $.expr, ")", "{", switch_prong_list($), "}"), 241 | 242 | // *** Assembly *** 243 | asm_expr: $ => seq("asm", optional("volatile"), "(", $.expr, optional($.asm_output), ")"), 244 | 245 | asm_output: $ => seq(":", asm_output_list($), optional($.asm_input)), 246 | 247 | asm_output_item: $ => seq("[", $.identifier, "]", $.string_literal, "(", choice(seq("->", $._type_expr), $.identifier), ")"), 248 | 249 | asm_input: $ => seq(":", asm_input_list($), optional($.asm_clobbers)), 250 | 251 | asm_input_item: $ => seq("[", $.identifier, "]", $.string_literal, "(", $.expr, ")"), 252 | 253 | asm_clobbers: $ => seq(":", string_list($)), 254 | 255 | // *** Helper grammar *** 256 | break_label: $ => seq(":", $.identifier), 257 | 258 | block_label: $ => prec.left(seq($.identifier, ":")), 259 | 260 | field_init: $ => seq(".", $.identifier, "=", $.expr), 261 | 262 | while_continue_expr: $ => seq(":", "(", choice($.assign_expr, $.expr), ")"), 263 | 264 | link_section: $ => seq("linksection", "(", $.expr, ")"), 265 | 266 | addr_space: $ => seq("addrspace", "(", $.expr, ")"), 267 | 268 | // Fn specific 269 | call_conv: $ => seq("callconv", "(", $.expr, ")"), 270 | 271 | param_decl: $ => choice( 272 | seq(optional($.doc_comment), optional(choice("noalias", "comptime")), optional(seq($.identifier, ":")), $.param_type), 273 | "...", 274 | ), 275 | 276 | param_type: $ => choice( 277 | "anytype", 278 | $._type_expr, 279 | ), 280 | 281 | // Control flow prefixes 282 | if_prefix: $ => seq( 283 | "if", 284 | "(", 285 | $.expr, 286 | ")", 287 | optional($.ptr_payload) 288 | ), 289 | 290 | while_prefix: $ => seq( 291 | "while", 292 | "(", 293 | $.expr, 294 | ")", 295 | optional($.ptr_payload), 296 | optional($.while_continue_expr) 297 | ), 298 | 299 | for_prefix: $ => seq( 300 | "for", 301 | "(", 302 | $.for_arguments_list, 303 | ")", 304 | $.ptr_list_payload 305 | ), 306 | 307 | // Payloads 308 | payload: $ => seq("|", $.identifier, "|"), 309 | 310 | ptr_payload: $ => seq("|", optional("*"), $.identifier, "|"), 311 | 312 | ptr_index_payload: $ => seq("|", optional("*"), $.identifier, optional(seq(",", $.identifier)), "|"), 313 | 314 | ptr_list_payload: $ => seq("|", optional("*"), $.identifier, repeat(seq(",", optional("*"), $.identifier)), optional(","), "|"), 315 | 316 | // Switch specific 317 | switch_prong: $ => seq( 318 | optional("inline"), 319 | $.switch_case, 320 | "=>", 321 | optional($.ptr_index_payload), 322 | choice($.assign_expr, $.expr), 323 | ), 324 | 325 | switch_case: $ => choice( 326 | seq($.switch_item, repeat(seq(",", $.switch_item)), optional(",")), 327 | "else" 328 | ), 329 | 330 | switch_item: $ => seq($.expr, optional(seq("...", $.expr))), 331 | 332 | // For specific 333 | for_arguments_list: $ => seq( 334 | $.for_item, 335 | repeat(seq(",", $.for_item)), 336 | optional(",") 337 | ), 338 | 339 | for_item: $ => seq( 340 | $.expr, 341 | optional(seq("..", optional($.expr))) 342 | ), 343 | 344 | // Operators 345 | assign_op: $ => choice( 346 | "*=", 347 | "*|=", 348 | "/=", 349 | "%=", 350 | "+=", 351 | "+|=", 352 | "-=", 353 | "-|=", 354 | "<<=", 355 | "<<|=", 356 | ">>=", 357 | "&=", 358 | "^=", 359 | "|=", 360 | "*%=", 361 | "+%=", 362 | "-%=", 363 | "=", 364 | ), 365 | 366 | compare_op: $ => choice( 367 | "==", 368 | "!=", 369 | ">", 370 | "<", 371 | ">=", 372 | "<=", 373 | ), 374 | 375 | bitwise_op: $ => choice( 376 | "&", 377 | "^", 378 | "|", 379 | "orelse", 380 | seq("catch", optional($.payload)), 381 | ), 382 | 383 | bit_shift_op: $ => choice( 384 | "<<", 385 | ">>", 386 | "<<|" 387 | ), 388 | 389 | addition_op: $ => choice( 390 | "+", 391 | "-", 392 | "++", 393 | "+%", 394 | "-%", 395 | "+|", 396 | "-|", 397 | ), 398 | 399 | multiply_op: $ => choice( 400 | "||", 401 | "*", 402 | "/", 403 | "%", 404 | "**", 405 | "*%", 406 | "*|", 407 | ), 408 | 409 | prefix_op: $ => choice( 410 | "!", 411 | "-", 412 | "~", 413 | "-%", 414 | "&", 415 | "try", 416 | "await", 417 | ), 418 | 419 | prefix_type_op: $ => choice( 420 | "?", 421 | seq("anyframe", "->"), 422 | seq($.slice_type_start, repeat(choice($.byte_align, $.addr_space, "const", "volatile", "allowzero"))), 423 | seq($.ptr_type_start, repeat(choice($.addr_space, seq("align", "(", $.expr, optional(seq(":", $.expr, ":", $.expr)), ")"), "const", "volatile", "allowzero"))), 424 | $.array_type_start, 425 | ), 426 | 427 | array_access: $ => seq("[", $.expr, "]"), 428 | slice: $ => seq("[", field("start", $.expr), "..", optional(seq(optional(field("end", $.expr)), optional(seq(":", field("sentinel", $.expr))))), "]"), 429 | 430 | deref: _ => ".*", 431 | unwrap: _ => ".?", 432 | 433 | field_access: $ => seq(".", $.identifier), 434 | 435 | _suffix_op: $ => choice( 436 | $.slice, 437 | $.array_access, 438 | $.field_access, 439 | $.deref, 440 | $.unwrap, 441 | ), 442 | 443 | fn_call_arguments: $ => seq("(", expr_list($), ")"), 444 | 445 | // Ptr specific 446 | slice_type_start: $ => seq("[", optional(seq(":", $.expr)), "]"), 447 | 448 | ptr_type_start: $ => choice( 449 | "*", 450 | "**", 451 | seq("[", "*", optional(choice("c", seq(":", $.expr))), "]"), 452 | ), 453 | 454 | array_type_start: $ => seq("[", $.expr, optional(seq(":", $.expr)), "]"), 455 | 456 | // ContainerDecl specific 457 | container_decl_auto: $ => seq($.container_decl_type, "{", optional($.container_doc_comment), container_members($), "}"), 458 | 459 | container_decl_type: $ => choice( 460 | seq("struct", optional(seq("(", $.expr, ")"))), 461 | "opaque", 462 | seq("enum", optional(seq("(", $.expr, ")"))), 463 | seq("union", optional(seq("(", choice(seq("enum", optional(seq("(", $.expr, ")"))), $.expr), ")"))), 464 | ), 465 | 466 | // Alignment 467 | byte_align: $ => seq("align", "(", $.expr, ")"), 468 | 469 | // Comments 470 | container_doc_comment: (_) => 471 | token(repeat1(seq("//!", /[^\n]*/, /[ \n]*/))), 472 | doc_comment: (_) => token(repeat1(/\/\/\/(([^/\n][^\n]*[ \n]*)|)/)), 473 | line_comment: (_) => token(seq("//", /.*/)), 474 | 475 | // Strings 476 | identifier: ($) => choice(/[A-Za-z_][A-Za-z0-9_]*/, seq("@", $.string_literal)), 477 | string_escape: ($) => choice( 478 | "\\n", 479 | "\\r", 480 | "\\t", 481 | "\\\\", 482 | "\\'", 483 | "\\\"", 484 | /\\x[0-9a-fA-F]{2}/, 485 | /\\u\{[0-9a-fA-F]{1,6}\}/ 486 | ), 487 | builtin_identifier: $ => /@[A-Za-z_][A-Za-z0-9_]*/, 488 | 489 | char_fragment: ($) => token.immediate(prec(1, /[^'\\]/)), 490 | string_fragment: ($) => token.immediate(prec(1, /[^"\\]+/)), 491 | 492 | char_literal: ($) => seq("'", choice($.char_fragment, $.string_escape), "'"), 493 | string_literal_single: ($) => seq("\"", repeat(choice($.string_fragment, $.string_escape)), "\""), 494 | multi_string_literal: ($) => prec(1, repeat1(seq("\\\\", /[^\n]*/, "\n"))), 495 | string_literal: $ => prec(1, choice($.string_literal_single, $.multi_string_literal)), 496 | 497 | integer: $ => choice( 498 | token(seq("0b", numericWithSeparator(/[01]/))), 499 | token(seq("0o", numericWithSeparator(/[0-7]/))), 500 | token(seq("0x", numericWithSeparator(/[0-9a-fA-F]/), optional(seq(/[pP]\+?/, numericWithSeparator(/[0-9]/))))), 501 | token(seq(numericWithSeparator(/[0-9]/), optional(seq(/[eE]\+?/, numericWithSeparator(/[0-9]/))))), 502 | ), 503 | 504 | float: $ => prec(10, choice( 505 | token(seq("0x", numericWithSeparator(/[0-9a-fA-F]/), ".", numericWithSeparator(/[0-9a-fA-F]/), optional(seq(/[pP][+\-]?/, numericWithSeparator(/[0-9]/))))), 506 | token(seq(numericWithSeparator(/[0-9]/), ".", numericWithSeparator(/[0-9]/), optional(seq(/[eE][+\-]?/, numericWithSeparator(/[0-9]/))))), 507 | token(seq("0x", numericWithSeparator(/[0-9a-fA-F]/), /[pP]-/, numericWithSeparator(/[0-9]/))), 508 | token(seq(numericWithSeparator(/[0-9]/), /[eE]-/, numericWithSeparator(/[0-9]/))), 509 | )), 510 | } 511 | }); 512 | 513 | function numericWithSeparator(regex) { 514 | return seq(regex, repeat(seq(optional("_"), regex))); 515 | } 516 | 517 | --------------------------------------------------------------------------------