@@ -1395,8 +1395,13 @@ static void parse_spec_imports(CBMExtractCtx *ctx) {
13951395// that the main parser uses. Adding another host language is a one-line
13961396// declaration in lang_specs.c.
13971397
1398+ typedef struct {
1399+ TSNode script ;
1400+ TSNode content ;
1401+ } CBMEmbeddedBlock ;
1402+
13981403static void embedded_collect_content_nodes (TSNode root , const CBMEmbeddedLangSpec * spec ,
1399- TSNode * out , int * out_count , int max_out ) {
1404+ CBMEmbeddedBlock * out , int * out_count , int max_out ) {
14001405 /* Iterative DFS so deeply-nested script blocks are still found. Cap the
14011406 * stack to a sane bound (host grammars do not have million-deep markup
14021407 * trees) — no need to introduce TSNodeStack here. */
@@ -1412,7 +1417,8 @@ static void embedded_collect_content_nodes(TSNode root, const CBMEmbeddedLangSpe
14121417 for (uint32_t k = 0 ; k < cc ; k ++ ) {
14131418 TSNode c = ts_node_child (node , k );
14141419 if (strcmp (ts_node_type (c ), spec -> content_node_type ) == 0 ) {
1415- out [(* out_count )++ ] = c ;
1420+ out [* out_count ] = (CBMEmbeddedBlock ){.script = node , .content = c };
1421+ (* out_count )++ ;
14161422 if (* out_count >= max_out ) {
14171423 return ;
14181424 }
@@ -1429,50 +1435,166 @@ static void embedded_collect_content_nodes(TSNode root, const CBMEmbeddedLangSpe
14291435 }
14301436}
14311437
1438+ static bool ascii_trimmed_equals (const char * value , const char * expected ) {
1439+ if (!value || !expected ) {
1440+ return false;
1441+ }
1442+ while (* value && isspace ((unsigned char )* value )) {
1443+ value ++ ;
1444+ }
1445+ size_t len = strlen (value );
1446+ while (len > 0 && isspace ((unsigned char )value [len - 1 ])) {
1447+ len -- ;
1448+ }
1449+ size_t expected_len = strlen (expected );
1450+ if (len != expected_len ) {
1451+ return false;
1452+ }
1453+ for (size_t i = 0 ; i < len ; i ++ ) {
1454+ if (tolower ((unsigned char )value [i ]) != tolower ((unsigned char )expected [i ])) {
1455+ return false;
1456+ }
1457+ }
1458+ return true;
1459+ }
1460+
1461+ /* Vue is the only host that opts into structural embedded extraction. Restrict
1462+ * its inline programs to the language forms whose parsers we can select
1463+ * exactly. A src attribute always denotes an external program and therefore
1464+ * suppresses any inline extraction, even for malformed mixed markup. */
1465+ static bool vue_embedded_language (CBMExtractCtx * ctx , TSNode script , CBMLanguage * language ) {
1466+ enum { VUE_ATTR_STACK_CAP = 128 };
1467+ TSNode stack [VUE_ATTR_STACK_CAP ];
1468+ int top = 0 ;
1469+ bool has_lang = false;
1470+ bool lang_supported = true;
1471+ CBMLanguage selected = CBM_LANG_JAVASCRIPT ;
1472+ stack [top ++ ] = script ;
1473+ while (top > 0 ) {
1474+ TSNode node = stack [-- top ];
1475+ if (strcmp (ts_node_type (node ), "attribute" ) == 0 ) {
1476+ uint32_t named_count = ts_node_named_child_count (node );
1477+ if (named_count == 0 ) {
1478+ return false;
1479+ }
1480+ TSNode name_node = ts_node_named_child (node , 0 );
1481+ char * name = cbm_node_text (ctx -> arena , name_node , ctx -> source );
1482+ if (ascii_trimmed_equals (name , "src" )) {
1483+ return false;
1484+ }
1485+ if (ascii_trimmed_equals (name , "lang" )) {
1486+ TSNode value_node = node ;
1487+ if (!find_first_descendant_of (node , "attribute_value" , & value_node )) {
1488+ return false;
1489+ }
1490+ char * value = cbm_node_text (ctx -> arena , value_node , ctx -> source );
1491+ has_lang = true;
1492+ if (ascii_trimmed_equals (value , "js" ) ||
1493+ ascii_trimmed_equals (value , "javascript" )) {
1494+ selected = CBM_LANG_JAVASCRIPT ;
1495+ } else if (ascii_trimmed_equals (value , "ts" ) ||
1496+ ascii_trimmed_equals (value , "typescript" )) {
1497+ selected = CBM_LANG_TYPESCRIPT ;
1498+ } else {
1499+ lang_supported = false;
1500+ }
1501+ }
1502+ continue ;
1503+ }
1504+ uint32_t count = ts_node_named_child_count (node );
1505+ if ((int )count > VUE_ATTR_STACK_CAP - top ) {
1506+ return false;
1507+ }
1508+ for (int i = (int )count - 1 ; i >= 0 ; i -- ) {
1509+ stack [top ++ ] = ts_node_named_child (node , (uint32_t )i );
1510+ }
1511+ }
1512+ if (has_lang && !lang_supported ) {
1513+ return false;
1514+ }
1515+ * language = selected ;
1516+ return true;
1517+ }
1518+
1519+ static void parse_one_embedded_block (CBMExtractCtx * ctx , const CBMEmbeddedBlock * block ,
1520+ CBMLanguage embedded_language , bool extract_structure ) {
1521+ const TSLanguage * language = cbm_ts_language (embedded_language );
1522+ if (!language || ctx -> source_len < 0 ) {
1523+ return ;
1524+ }
1525+ TSRange range = {
1526+ .start_point = ts_node_start_point (block -> content ),
1527+ .end_point = ts_node_end_point (block -> content ),
1528+ .start_byte = ts_node_start_byte (block -> content ),
1529+ .end_byte = ts_node_end_byte (block -> content ),
1530+ };
1531+ if (range .end_byte <= range .start_byte || range .end_byte > (uint32_t )ctx -> source_len ) {
1532+ return ;
1533+ }
1534+
1535+ /* One fresh parser/tree per block prevents parser state or error recovery
1536+ * from one script from changing the next script's result. Parsing the full
1537+ * original buffer through the absolute included range preserves byte and
1538+ * point coordinates across CRLF and UTF-8 prefixes. */
1539+ TSParser * parser = ts_parser_new ();
1540+ if (!parser ) {
1541+ return ;
1542+ }
1543+ if (!ts_parser_set_language (parser , language ) ||
1544+ !ts_parser_set_included_ranges (parser , & range , 1 )) {
1545+ ts_parser_delete (parser );
1546+ return ;
1547+ }
1548+ TSTree * tree = ts_parser_parse_string (parser , NULL , ctx -> source , (uint32_t )ctx -> source_len );
1549+ if (!tree ) {
1550+ ts_parser_delete (parser );
1551+ return ;
1552+ }
1553+
1554+ CBMExtractCtx sub_ctx = {
1555+ .arena = ctx -> arena ,
1556+ .result = ctx -> result ,
1557+ .source = ctx -> source ,
1558+ .source_len = ctx -> source_len ,
1559+ .language = embedded_language ,
1560+ .project = ctx -> project ,
1561+ .rel_path = ctx -> rel_path ,
1562+ .module_qn = ctx -> module_qn ,
1563+ .root = ts_tree_root_node (tree ),
1564+ .macro_table = ctx -> macro_table ,
1565+ .return_type_table = ctx -> return_type_table ,
1566+ };
1567+ if (extract_structure ) {
1568+ cbm_extract_definitions_without_module (& sub_ctx );
1569+ }
1570+ walk_es_imports (& sub_ctx , sub_ctx .root );
1571+ if (extract_structure ) {
1572+ cbm_extract_unified (& sub_ctx );
1573+ }
1574+
1575+ ts_tree_delete (tree );
1576+ ts_parser_delete (parser );
1577+ }
1578+
14321579static void parse_embedded_imports (CBMExtractCtx * ctx ) {
14331580 const CBMLangSpec * spec = cbm_lang_spec (ctx -> language );
14341581 if (!spec || !spec -> embedded_imports ) {
14351582 return ;
14361583 }
14371584 for (const CBMEmbeddedLangSpec * e = spec -> embedded_imports ; e -> script_node_type != NULL ; e ++ ) {
1438- const TSLanguage * embedded_lang = cbm_ts_language (e -> embedded_language );
1439- if (!embedded_lang ) {
1440- continue ; /* embedded grammar not linked in — silently skip */
1441- }
14421585 enum { MAX_EMBEDDED_BLOCKS = 16 };
1443- TSNode hits [MAX_EMBEDDED_BLOCKS ];
1586+ CBMEmbeddedBlock hits [MAX_EMBEDDED_BLOCKS ];
14441587 int hit_count = 0 ;
14451588 embedded_collect_content_nodes (ctx -> root , e , hits , & hit_count , MAX_EMBEDDED_BLOCKS );
1446- if (hit_count == 0 ) {
1447- continue ;
1448- }
1449- TSParser * parser = ts_parser_new ();
1450- if (!parser ) {
1451- continue ;
1452- }
1453- if (!ts_parser_set_language (parser , embedded_lang )) {
1454- ts_parser_delete (parser );
1455- continue ;
1456- }
14571589 for (int i = 0 ; i < hit_count ; i ++ ) {
1458- uint32_t s = ts_node_start_byte (hits [i ]);
1459- uint32_t end = ts_node_end_byte (hits [i ]);
1460- if (end <= s ) {
1590+ CBMLanguage embedded_language = e -> embedded_language ;
1591+ bool extract_structure = ctx -> language == CBM_LANG_VUE ;
1592+ if (extract_structure &&
1593+ !vue_embedded_language (ctx , hits [i ].script , & embedded_language )) {
14611594 continue ;
14621595 }
1463- const char * sub_src = ctx -> source + s ;
1464- uint32_t sub_len = end - s ;
1465- TSTree * sub_tree = ts_parser_parse_string (parser , NULL , sub_src , sub_len );
1466- if (!sub_tree ) {
1467- continue ;
1468- }
1469- CBMExtractCtx sub_ctx = * ctx ;
1470- sub_ctx .source = sub_src ;
1471- sub_ctx .root = ts_tree_root_node (sub_tree );
1472- walk_es_imports (& sub_ctx , sub_ctx .root );
1473- ts_tree_delete (sub_tree );
1596+ parse_one_embedded_block (ctx , & hits [i ], embedded_language , extract_structure );
14741597 }
1475- ts_parser_delete (parser );
14761598 }
14771599}
14781600
0 commit comments