mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-28 17:20: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