mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	Add sample XS file
This commit is contained in:
		
							
								
								
									
										466
									
								
								samples/XS/CommonMark.xs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										466
									
								
								samples/XS/CommonMark.xs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,466 @@ | ||||
| /* | ||||
|  * This software is copyright (C) by Nick Wellnhofer <wellnhofer@aevum.de>. | ||||
|  * | ||||
|  * This is free software; you can redistribute it and/or modify it under | ||||
|  * the same terms as the Perl 5 programming language system itself. | ||||
|  * | ||||
|  * Terms of the Perl programming language system itself | ||||
|  * | ||||
|  * a) the GNU General Public License as published by the Free | ||||
|  *    Software Foundation; either version 1, or (at your option) any | ||||
|  *    later version, or | ||||
|  * b) the "Artistic License" | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * Notes on memory management | ||||
|  * | ||||
|  * - A pointer to the Perl SV representing a node is stored in the | ||||
|  *   user data slot of `struct cmark_node`, so there's a 1:1 mapping | ||||
|  *   between Perl and C objects. | ||||
|  * - Every node SV keeps a reference to the parent SV. This is done | ||||
|  *   indirectly by looking up the parent SV and increasing its refcount. | ||||
|  * - This makes sure that a document isn't freed if the last reference | ||||
|  *   from Perl to the root node is dropped, as references to child nodes | ||||
|  *   might still exist. | ||||
|  * - As a consequence, as long as a node is referenced from Perl, all its | ||||
|  *   ancestor nodes will also be associated with a Perl object. | ||||
|  */ | ||||
|  | ||||
| #define PERL_NO_GET_CONTEXT | ||||
|  | ||||
| #include "EXTERN.h" | ||||
| #include "perl.h" | ||||
| #include "XSUB.h" | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <cmark.h> | ||||
|  | ||||
| #if CMARK_VERSION < 0x001000 | ||||
|     #error libcmark 0.16.0 is required. | ||||
| #endif | ||||
|  | ||||
| /* Fix prefixes of render functions. */ | ||||
| #define cmark_node_render_html cmark_render_html | ||||
| #define cmark_node_render_xml  cmark_render_xml | ||||
| #define cmark_node_render_man  cmark_render_man | ||||
|  | ||||
| static SV* | ||||
| S_create_or_incref_node_sv(pTHX_ cmark_node *node) { | ||||
|     SV *new_obj = NULL; | ||||
|  | ||||
|     while (node) { | ||||
|         SV *obj; | ||||
|         HV *stash; | ||||
|  | ||||
|         /* Look for existing object. */ | ||||
|         obj = (SV*)cmark_node_get_user_data(node); | ||||
|  | ||||
|         if (obj) { | ||||
|             /* Incref if found. */ | ||||
|             SvREFCNT_inc_simple_void_NN(obj); | ||||
|             if (!new_obj) { | ||||
|                 new_obj = obj; | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         /* Create a new SV. */ | ||||
|         obj = newSViv(PTR2IV(node)); | ||||
|         cmark_node_set_user_data(node, obj); | ||||
|         if (!new_obj) { | ||||
|             new_obj = obj; | ||||
|         } | ||||
|  | ||||
|         /* | ||||
|          * Unfortunately, Perl doesn't offer an API function to bless an SV | ||||
|          * without a reference. The following code is mostly copied from | ||||
|          * sv_bless. | ||||
|          */ | ||||
|         SvOBJECT_on(obj); | ||||
| #if (PERL_VERSION <= 16) | ||||
|         PL_sv_objcount++; | ||||
| #endif | ||||
|         SvUPGRADE(obj, SVt_PVMG); | ||||
|         stash = gv_stashpvn("CommonMark::Node", 16, GV_ADD); | ||||
|         SvSTASH_set(obj, (HV*)SvREFCNT_inc(stash)); | ||||
|  | ||||
|         /* Recurse into parent. */ | ||||
|         node = cmark_node_parent(node); | ||||
|     } | ||||
|  | ||||
|     return new_obj; | ||||
| } | ||||
|  | ||||
| static void | ||||
| S_decref_node_sv(pTHX_ cmark_node *node) { | ||||
|     SV *obj; | ||||
|  | ||||
|     if (!node) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     obj = (SV*)cmark_node_get_user_data(node); | ||||
|     if (!obj) { | ||||
|         /* Should never happen. */ | ||||
|         croak("Internal error: node SV not found"); | ||||
|     } | ||||
|  | ||||
|     SvREFCNT_dec_NN(obj); | ||||
| } | ||||
|  | ||||
| /* Find or create an SV for a cmark_node. */ | ||||
| static SV* | ||||
| S_node2sv(pTHX_ cmark_node *node) { | ||||
|     SV *obj; | ||||
|  | ||||
|     if (!node) { | ||||
|         return &PL_sv_undef; | ||||
|     } | ||||
|  | ||||
|     obj = S_create_or_incref_node_sv(aTHX_ node); | ||||
|  | ||||
|     return newRV_noinc(obj); | ||||
| } | ||||
|  | ||||
| /* Transfer refcount from a node to another. */ | ||||
| static void | ||||
| S_transfer_refcount(pTHX_ cmark_node *from, cmark_node *to) { | ||||
|     if (from != to) { | ||||
|         S_create_or_incref_node_sv(aTHX_ to); | ||||
|         S_decref_node_sv(aTHX_ from); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Get C struct pointer from an SV argument. */ | ||||
| static void* | ||||
| S_sv2c(pTHX_ SV *sv, const char *class_name, STRLEN len, CV *cv, | ||||
|        const char *var_name) { | ||||
|     if (!SvROK(sv) || !sv_derived_from_pvn(sv, class_name, len, 0)) { | ||||
|         const char *sub_name = GvNAME(CvGV(cv)); | ||||
|         croak("%s: %s is not of type %s", sub_name, var_name, class_name); | ||||
|     } | ||||
|     return INT2PTR(void*, SvIV(SvRV(sv))); | ||||
| } | ||||
|  | ||||
|  | ||||
| MODULE = CommonMark  PACKAGE = CommonMark  PREFIX = cmark_ | ||||
|  | ||||
| PROTOTYPES: DISABLE | ||||
|  | ||||
| BOOT: | ||||
|     if (cmark_version != CMARK_VERSION) { | ||||
|         warn("Compiled against libcmark %s, but runtime version is %s", | ||||
|              CMARK_VERSION_STRING, cmark_version_string); | ||||
|     } | ||||
|  | ||||
| char* | ||||
| cmark_markdown_to_html(package, string) | ||||
|     SV *package = NO_INIT | ||||
|     SV *string | ||||
| PREINIT: | ||||
|     STRLEN len; | ||||
|     const char *buffer; | ||||
| CODE: | ||||
|     (void)package; | ||||
|     buffer = SvPVutf8(string, len); | ||||
|     RETVAL = cmark_markdown_to_html(buffer, len); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| cmark_node* | ||||
| cmark_parse_document(package, string) | ||||
|     SV *package = NO_INIT | ||||
|     SV *string | ||||
| PREINIT: | ||||
|     STRLEN len; | ||||
|     const char *buffer; | ||||
| CODE: | ||||
|     (void)package; | ||||
|     buffer = SvPVutf8(string, len); | ||||
|     RETVAL = cmark_parse_document(buffer, len); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| cmark_node* | ||||
| cmark_parse_file(package, file) | ||||
|     SV *package = NO_INIT | ||||
|     SV *file | ||||
| PREINIT: | ||||
|     PerlIO *perl_io; | ||||
|     FILE *stream = NULL; | ||||
| CODE: | ||||
|     (void)package; | ||||
|     perl_io = IoIFP(sv_2io(file)); | ||||
|     if (perl_io) { | ||||
|         stream = PerlIO_findFILE(perl_io); | ||||
|     } | ||||
|     if (!stream) { | ||||
|         croak("parse_file: file is not a file handle"); | ||||
|     } | ||||
|     RETVAL = cmark_parse_file(stream); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| int | ||||
| cmark_version(package) | ||||
|     SV *package = NO_INIT | ||||
| CODE: | ||||
|     (void)package; | ||||
|     RETVAL = cmark_version; | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| const char* | ||||
| cmark_version_string(package) | ||||
|     SV *package = NO_INIT | ||||
| CODE: | ||||
|     (void)package; | ||||
|     RETVAL = cmark_version_string; | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| int | ||||
| cmark_compile_time_version(package) | ||||
|     SV *package = NO_INIT | ||||
| CODE: | ||||
|     (void)package; | ||||
|     RETVAL = CMARK_VERSION; | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| const char* | ||||
| cmark_compile_time_version_string(package) | ||||
|     SV *package = NO_INIT | ||||
| CODE: | ||||
|     (void)package; | ||||
|     RETVAL = CMARK_VERSION_STRING; | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
|  | ||||
| MODULE = CommonMark  PACKAGE = CommonMark::Node  PREFIX = cmark_node_ | ||||
|  | ||||
| cmark_node* | ||||
| new(package, type) | ||||
|     SV *package = NO_INIT | ||||
|     cmark_node_type type | ||||
| CODE: | ||||
|     (void)package; | ||||
|     RETVAL = cmark_node_new(type); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| void | ||||
| DESTROY(cmark_node *node) | ||||
| CODE: | ||||
|     cmark_node *parent = cmark_node_parent(node); | ||||
|     if (parent) { | ||||
|         cmark_node_set_user_data(node, NULL); | ||||
|         S_decref_node_sv(aTHX_ parent); | ||||
|     } | ||||
|     else { | ||||
|         cmark_node_free(node); | ||||
|     } | ||||
|  | ||||
| cmark_iter* | ||||
| iterator(cmark_node *node) | ||||
| CODE: | ||||
|     S_create_or_incref_node_sv(aTHX_ node); | ||||
|     RETVAL = cmark_iter_new(node); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| cmark_node* | ||||
| interface_get_node(cmark_node *node) | ||||
| INTERFACE: | ||||
|     cmark_node_next | ||||
|     cmark_node_previous | ||||
|     cmark_node_parent | ||||
|     cmark_node_first_child | ||||
|     cmark_node_last_child | ||||
|  | ||||
| int | ||||
| interface_get_int(cmark_node *node) | ||||
| INTERFACE: | ||||
|     cmark_node_get_type | ||||
|     cmark_node_get_header_level | ||||
|     cmark_node_get_list_type | ||||
|     cmark_node_get_list_delim | ||||
|     cmark_node_get_list_start | ||||
|     cmark_node_get_list_tight | ||||
|     cmark_node_get_start_line | ||||
|     cmark_node_get_start_column | ||||
|     cmark_node_get_end_line | ||||
|     cmark_node_get_end_column | ||||
|  | ||||
| NO_OUTPUT int | ||||
| interface_set_int(cmark_node *node, int value) | ||||
| INTERFACE: | ||||
|     cmark_node_set_header_level | ||||
|     cmark_node_set_list_type | ||||
|     cmark_node_set_list_delim | ||||
|     cmark_node_set_list_start | ||||
|     cmark_node_set_list_tight | ||||
| POSTCALL: | ||||
|     if (!RETVAL) { | ||||
|         croak("%s: invalid operation", GvNAME(CvGV(cv))); | ||||
|     } | ||||
|  | ||||
| const char* | ||||
| interface_get_utf8(cmark_node *node) | ||||
| INTERFACE: | ||||
|     cmark_node_get_type_string | ||||
|     cmark_node_get_literal | ||||
|     cmark_node_get_title | ||||
|     cmark_node_get_url | ||||
|     cmark_node_get_fence_info | ||||
|  | ||||
| NO_OUTPUT int | ||||
| interface_set_utf8(cmark_node *node, const char *value) | ||||
| INTERFACE: | ||||
|     cmark_node_set_literal | ||||
|     cmark_node_set_title | ||||
|     cmark_node_set_url | ||||
|     cmark_node_set_fence_info | ||||
| POSTCALL: | ||||
|     if (!RETVAL) { | ||||
|         croak("%s: invalid operation", GvNAME(CvGV(cv))); | ||||
|     } | ||||
|  | ||||
| void | ||||
| cmark_node_unlink(cmark_node *node) | ||||
| PREINIT: | ||||
|     cmark_node *old_parent; | ||||
| INIT: | ||||
|     old_parent = cmark_node_parent(node); | ||||
| POSTCALL: | ||||
|     S_decref_node_sv(aTHX_ old_parent); | ||||
|  | ||||
| NO_OUTPUT int | ||||
| interface_move_node(cmark_node *node, cmark_node *other) | ||||
| PREINIT: | ||||
|     cmark_node *old_parent; | ||||
|     cmark_node *new_parent; | ||||
| INIT: | ||||
|     old_parent = cmark_node_parent(other); | ||||
| INTERFACE: | ||||
|     cmark_node_insert_before | ||||
|     cmark_node_insert_after | ||||
|     cmark_node_prepend_child | ||||
|     cmark_node_append_child | ||||
| POSTCALL: | ||||
|     if (!RETVAL) { | ||||
|         croak("%s: invalid operation", GvNAME(CvGV(cv))); | ||||
|     } | ||||
|     new_parent = cmark_node_parent(other); | ||||
|     S_transfer_refcount(aTHX_ old_parent, new_parent); | ||||
|  | ||||
| char* | ||||
| interface_render(cmark_node *root, long options = 0) | ||||
| INTERFACE: | ||||
|     cmark_node_render_html | ||||
|     cmark_node_render_xml | ||||
|     cmark_node_render_man | ||||
|  | ||||
|  | ||||
| MODULE = CommonMark  PACKAGE = CommonMark::Iterator  PREFIX = cmark_iter_ | ||||
|  | ||||
| void | ||||
| DESTROY(cmark_iter *iter) | ||||
| CODE: | ||||
|     S_decref_node_sv(aTHX_ cmark_iter_get_node(iter)); | ||||
|     S_decref_node_sv(aTHX_ cmark_iter_get_root(iter)); | ||||
|     cmark_iter_free(iter); | ||||
|  | ||||
| void | ||||
| cmark_iter_next(cmark_iter *iter) | ||||
| PREINIT: | ||||
|     I32 gimme; | ||||
|     cmark_node *old_node; | ||||
|     cmark_event_type ev_type; | ||||
| PPCODE: | ||||
|     gimme    = GIMME_V; | ||||
|     old_node = cmark_iter_get_node(iter); | ||||
|     ev_type  = cmark_iter_next(iter); | ||||
|  | ||||
|     if (ev_type != CMARK_EVENT_DONE) { | ||||
|         cmark_node *node = cmark_iter_get_node(iter); | ||||
|  | ||||
|         ST(0) = sv_2mortal(newSViv((IV)ev_type)); | ||||
|  | ||||
|         if (gimme == G_ARRAY) { | ||||
|             SV *obj = S_create_or_incref_node_sv(aTHX_ node); | ||||
|  | ||||
|             /* A bit more efficient than S_transfer_refcount. */ | ||||
|             if (old_node != node) { | ||||
|                 S_decref_node_sv(aTHX_ old_node); | ||||
|                 SvREFCNT_inc_simple_void_NN(obj); | ||||
|             } | ||||
|  | ||||
|             ST(1) = sv_2mortal(newRV_noinc(obj)); | ||||
|             XSRETURN(2); | ||||
|         } | ||||
|         else { | ||||
|             S_transfer_refcount(aTHX_ old_node, node); | ||||
|             XSRETURN(1); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         S_decref_node_sv(aTHX_ old_node); | ||||
|  | ||||
|         if (gimme == G_ARRAY) { | ||||
|             XSRETURN_EMPTY; | ||||
|         } | ||||
|         else { | ||||
|             ST(0) = sv_2mortal(newSViv((IV)ev_type)); | ||||
|             XSRETURN(1); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| cmark_node* | ||||
| cmark_iter_get_node(cmark_iter *iter) | ||||
|  | ||||
| cmark_event_type | ||||
| cmark_iter_get_event_type(cmark_iter *iter) | ||||
|  | ||||
| void | ||||
| cmark_iter_reset(iter, node, event_type) | ||||
|     cmark_iter *iter | ||||
|     cmark_node *node | ||||
|     cmark_event_type event_type | ||||
| PREINIT: | ||||
|     cmark_node *old_node; | ||||
| INIT: | ||||
|     old_node = cmark_iter_get_node(iter); | ||||
|     S_transfer_refcount(aTHX_ old_node, node); | ||||
|  | ||||
|  | ||||
| MODULE = CommonMark  PACKAGE = CommonMark::Parser  PREFIX = cmark_parser_ | ||||
|  | ||||
| cmark_parser* | ||||
| cmark_parser_new(package) | ||||
|     SV *package = NO_INIT | ||||
| CODE: | ||||
|     (void)package; | ||||
|     RETVAL = cmark_parser_new(); | ||||
| OUTPUT: | ||||
|     RETVAL | ||||
|  | ||||
| void | ||||
| DESTROY(cmark_parser *parser) | ||||
| CODE: | ||||
|     cmark_parser_free(parser); | ||||
|  | ||||
| void | ||||
| cmark_parser_feed(cmark_parser *parser, SV *string) | ||||
| PREINIT: | ||||
|     STRLEN len; | ||||
|     const char *buffer; | ||||
| CODE: | ||||
|     buffer = SvPVutf8(string, len); | ||||
|     cmark_parser_feed(parser, buffer, len); | ||||
|  | ||||
| cmark_node* | ||||
| cmark_parser_finish(cmark_parser *parser) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user