@@ -415,6 +415,96 @@ fn test_query_no_match() {
415415 assert ! ( !matched) ;
416416}
417417
418+ #[ test]
419+ fn test_query_choice_checks_presence ( ) {
420+ for input in [ "x = 1" , "x = y" ] {
421+ let ast = parse_input ( input) ;
422+ let mut cursor = AstCursor :: new ( & ast) ;
423+ cursor. goto_first_child ( ) ;
424+ let assignment_id = cursor. node_id ( ) ;
425+
426+ let query = yeast:: query!(
427+ ( assignment
428+ right: [
429+ ( integer)
430+ ( identifier)
431+ ]
432+ )
433+ ) ;
434+ let mut captures = yeast:: captures:: Captures :: new ( ) ;
435+ assert ! (
436+ query. do_match( & ast, assignment_id, & mut captures) . unwrap( ) ,
437+ "query choice should match {input}"
438+ ) ;
439+ }
440+ }
441+
442+ #[ test]
443+ fn test_repeated_query_choice_accumulates_present_captures ( ) {
444+ let ast = parse_input ( "[x, 1, y]" ) ;
445+
446+ let mut cursor = AstCursor :: new ( & ast) ;
447+ cursor. goto_first_child ( ) ;
448+ let array_id = cursor. node_id ( ) ;
449+
450+ let query = yeast:: query!(
451+ ( array
452+ [
453+ ( identifier) @names
454+ ( integer)
455+ ] *
456+ )
457+ ) ;
458+ let mut captures = yeast:: captures:: Captures :: new ( ) ;
459+ assert ! ( query. do_match( & ast, array_id, & mut captures) . unwrap( ) ) ;
460+ assert_eq ! ( captures. get_all( "names" ) . len( ) , 2 ) ;
461+ }
462+
463+ #[ test]
464+ fn test_query_choice_accepts_quantified_alternative ( ) {
465+ let ast = parse_input ( "[x, y]" ) ;
466+
467+ let mut cursor = AstCursor :: new ( & ast) ;
468+ cursor. goto_first_child ( ) ;
469+ let array_id = cursor. node_id ( ) ;
470+
471+ let query = yeast:: query!(
472+ ( array
473+ [
474+ ( identifier) + @names
475+ ( integer)
476+ ]
477+ )
478+ ) ;
479+ let mut captures = yeast:: captures:: Captures :: new ( ) ;
480+ assert ! ( query. do_match( & ast, array_id, & mut captures) . unwrap( ) ) ;
481+ assert_eq ! ( captures. get_all( "names" ) . len( ) , 2 ) ;
482+ }
483+
484+ #[ test]
485+ fn test_query_sequence_can_start_with_choice ( ) {
486+ let ast = parse_input ( "[x, 1]" ) ;
487+
488+ let mut cursor = AstCursor :: new ( & ast) ;
489+ cursor. goto_first_child ( ) ;
490+ let array_id = cursor. node_id ( ) ;
491+
492+ let query = yeast:: query!(
493+ ( array
494+ (
495+ [
496+ ( identifier) @value
497+ ( integer) @value
498+ ]
499+ ","
500+ )
501+ )
502+ ) ;
503+ let mut captures = yeast:: captures:: Captures :: new ( ) ;
504+ assert ! ( query. do_match( & ast, array_id, & mut captures) . unwrap( ) ) ;
505+ assert_eq ! ( captures. get_all( "value" ) . len( ) , 1 ) ;
506+ }
507+
418508#[ test]
419509fn test_query_skips_extras_in_positional_match ( ) {
420510 // Regression test: positional wildcards `(_)` must not bind to
@@ -1673,6 +1763,137 @@ fn test_raw_capture_marker_explicit_translate() {
16731763 ) ;
16741764}
16751765
1766+ #[ test]
1767+ fn test_query_choice_makes_branch_specific_captures_optional ( ) {
1768+ fn rules ( ) -> Vec < Rule > {
1769+ vec ! [
1770+ yeast:: rule!(
1771+ ( program ( _) * @stmts)
1772+ =>
1773+ ( program stmt: { stmts} )
1774+ ) ,
1775+ yeast:: rule!(
1776+ ( assignment
1777+ left: ( _) @left
1778+ right: [
1779+ ( integer) @number
1780+ ( identifier) @@name
1781+ ] )
1782+ =>
1783+ assignment {
1784+ let right = match ( number, name) {
1785+ ( Some ( number) , None ) => number,
1786+ ( None , Some ( name) ) => {
1787+ let spelling = ctx. source_text( name) ;
1788+ tree!( ( identifier #{ spelling} ) )
1789+ }
1790+ _ => return Err ( "exactly one alternative should match" . to_string( ) ) ,
1791+ } ;
1792+ tree!( ( assignment left: { left} right: { right} ) )
1793+ }
1794+ ) ,
1795+ yeast:: rule!( ( identifier) => ( identifier "ID" ) ) ,
1796+ yeast:: rule!( ( integer) => ( integer "INT" ) ) ,
1797+ ]
1798+ }
1799+
1800+ let lang: tree_sitter:: Language = tree_sitter_ruby:: LANGUAGE . into ( ) ;
1801+ let schema =
1802+ yeast:: node_types_yaml:: schema_from_yaml_with_language ( OUTPUT_SCHEMA_YAML , & lang) . unwrap ( ) ;
1803+ let phases = vec ! [ Phase :: new( "translate" , rules( ) ) ] ;
1804+ let runner: Runner = Runner :: with_schema ( lang, & schema, & phases) ;
1805+
1806+ for ( input, expected_right) in [ ( "x = 1" , "integer \" INT\" " ) , ( "x = y" , "identifier \" y\" " ) ] {
1807+ let ast = runner. run ( input) . unwrap ( ) ;
1808+ let dump = dump_ast ( & ast, ast. get_root ( ) , input) ;
1809+ assert ! (
1810+ dump. contains( expected_right) ,
1811+ "unexpected dump for {input}: {dump}"
1812+ ) ;
1813+ }
1814+ }
1815+
1816+ #[ test]
1817+ fn test_query_choice_keeps_shared_capture_required ( ) {
1818+ fn rules ( ) -> Vec < Rule > {
1819+ vec ! [
1820+ yeast:: rule!(
1821+ ( program ( _) * @stmts)
1822+ =>
1823+ ( program stmt: { stmts} )
1824+ ) ,
1825+ yeast:: rule!(
1826+ ( assignment
1827+ left: ( _) @left
1828+ right: [
1829+ ( integer) @value
1830+ ( identifier) @value
1831+ ] )
1832+ =>
1833+ assignment {
1834+ let value: yeast:: OutputId = value;
1835+ tree!( ( assignment left: { left} right: { value} ) )
1836+ }
1837+ ) ,
1838+ yeast:: rule!( ( identifier) => ( identifier "ID" ) ) ,
1839+ yeast:: rule!( ( integer) => ( integer "INT" ) ) ,
1840+ ]
1841+ }
1842+
1843+ let lang: tree_sitter:: Language = tree_sitter_ruby:: LANGUAGE . into ( ) ;
1844+ let schema =
1845+ yeast:: node_types_yaml:: schema_from_yaml_with_language ( OUTPUT_SCHEMA_YAML , & lang) . unwrap ( ) ;
1846+ let phases = vec ! [ Phase :: new( "translate" , rules( ) ) ] ;
1847+ let runner: Runner = Runner :: with_schema ( lang, & schema, & phases) ;
1848+
1849+ for input in [ "x = 1" , "x = y" ] {
1850+ runner. run ( input) . unwrap ( ) ;
1851+ }
1852+ }
1853+
1854+ #[ test]
1855+ fn test_query_choice_promotes_mixed_multiplicity_to_repeated ( ) {
1856+ let lang: tree_sitter:: Language = tree_sitter_ruby:: LANGUAGE . into ( ) ;
1857+ let schema =
1858+ yeast:: node_types_yaml:: schema_from_yaml_with_language ( OUTPUT_SCHEMA_YAML , & lang) . unwrap ( ) ;
1859+ let rules: Vec < Rule > = vec ! [
1860+ yeast:: rule!(
1861+ ( program ( _) * @stmts)
1862+ =>
1863+ ( program stmt: { stmts} )
1864+ ) ,
1865+ yeast:: rule!(
1866+ ( assignment
1867+ left: [
1868+ ( identifier) @values
1869+ ( left_assignment_list ( identifier) * @values)
1870+ ]
1871+ right: ( _) @right)
1872+ =>
1873+ assignment {
1874+ let values: Vec <yeast:: OutputId > = values;
1875+ tree!( ( assignment
1876+ left: ( left_assignment_list item: { values} )
1877+ right: { right} ) )
1878+ }
1879+ ) ,
1880+ yeast:: rule!( ( identifier) => ( identifier "ID" ) ) ,
1881+ yeast:: rule!( ( integer) => ( integer "INT" ) ) ,
1882+ ] ;
1883+ let phases = vec ! [ Phase :: new( "translate" , rules) ] ;
1884+ let runner: Runner = Runner :: with_schema ( lang, & schema, & phases) ;
1885+
1886+ for ( input, expected_count) in [ ( "x = 1" , 1 ) , ( "x, y = 1" , 2 ) ] {
1887+ let ast = runner. run ( input) . unwrap ( ) ;
1888+ let dump = dump_ast ( & ast, ast. get_root ( ) , input) ;
1889+ assert_eq ! (
1890+ dump. matches( "identifier \" ID\" " ) . count( ) ,
1891+ expected_count,
1892+ "unexpected dump for {input}: {dump}"
1893+ ) ;
1894+ }
1895+ }
1896+
16761897// ---- Cursor tests ----
16771898
16781899#[ test]
0 commit comments