Table of Contents
- src/n_markdown.c
- Structures
- Types
- Functions
- Defines
- Detailed Description
- Types Documentation
- Functions Documentation
- function url_end
- function try_strike
- function try_link
- function try_emphasis
- function try_code
- function try_autolink
- function tbuf_add
- function table_add_row
- function table_add_cell
- function strip_quote
- function starts_table
- function starts_fence
- function rule_align
- function row_cells
- function parse_inline
- function n_markdown_parse_ex
- function n_markdown_parse
- function n_markdown_has_markup_ex
- function n_markdown_has_markup
- function n_markdown_free
- function md_word_char
- function md_us_opens
- function md_us_closes
- function match_ul
- function match_quote
- function match_ol
- function match_heading
- function list_add_wrapped
- function line_starts_block
- function line_indented
- function line_blank
- function join_lines
- function is_ws
- function is_table_rule
- function is_md_punct
- function is_http_url
- function is_digit
- function inl_push
- function has_pipe
- function free_inline
- function flush_text
- function dup_range
- function doc_push
- Macros Documentation
- define MD_MAX_SPANS
- define MD_MAX_ITEMS
- define MD_MAX_HEADING
- define MD_MAX_DEPTH
- define MD_MAX_DEPTH
- define MD_MAX_COLS
- define MD_MAX_BLOCKS
- Source code
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
src/n_markdown.c
Safe-subset Markdown parser. More...
Structures
| Name | |
|---|---|
| struct | tbuf |
| struct | mline |
| struct | md_cells |
Types
| Name | |
|---|---|
| typedef size_t(*)(const char *s, size_t n) | strip_fn |
Functions
| Name | |
|---|---|
| size_t | url_end(const char * s, size_t n, size_t i) |
| int | try_strike(n_markdown_inline * a, tbuf * t, const char * s, size_t n, size_t i, unsigned inherited, int depth, unsigned flags, size_t * ni) |
| int | try_link(n_markdown_inline * a, tbuf * t, const char * s, size_t n, size_t i, unsigned inherited, size_t * ni) |
| int | try_emphasis(n_markdown_inline * a, tbuf * t, const char * s, size_t n, size_t i, unsigned inherited, int depth, unsigned flags, size_t * ni) |
| int | try_code(n_markdown_inline * a, tbuf * t, const char * s, size_t n, size_t i, unsigned inherited, size_t * ni) |
| int | try_autolink(n_markdown_inline * a, tbuf * t, const char * s, size_t n, size_t i, unsigned inherited, size_t * ni) |
| int | tbuf_add(tbuf * t, char c) |
| void | table_add_row(n_markdown_block * blk, const char * s, const md_cells * c, unsigned flags) |
| int | table_add_cell(n_markdown_block * blk, const char * s, size_t a, size_t b, unsigned flags) |
| size_t | strip_quote(const char * s, size_t n) |
| int | starts_table(const mline * lines, size_t li, size_t nlines) |
| int | starts_fence(const char * s, size_t n) |
| unsigned char | rule_align(const char * s, size_t a, size_t b) |
| void | row_cells(const char * s, size_t n, md_cells * out) |
| void | parse_inline(n_markdown_inline * a, const char * s, size_t n, unsigned inherited, int depth, unsigned flags) |
| void | n_markdown_parse_ex(const char * text, size_t len, unsigned flags, n_markdown_doc * out) parse text into out, choosing which optional block kinds to recognise |
| void | n_markdown_parse(const char * text, size_t len, n_markdown_doc * out) parse text (len bytes, plain UTF-8) into out |
| int | n_markdown_has_markup_ex(const char * text, size_t len, unsigned flags) 1 if text contains any markup the given flags would render, else 0 |
| int | n_markdown_has_markup(const char * text, size_t len) 1 if text contains any renderable markup, else 0 |
| void | n_markdown_free(n_markdown_doc * doc) free everything a n_markdown_doc owns and zero it |
| int | md_word_char(unsigned char c) |
| int | md_us_opens(const char * s, size_t i) |
| int | md_us_closes(const char * s, size_t n, size_t j, size_t len) |
| int | match_ul(const char * s, size_t n, size_t * off) |
| int | match_quote(const char * s, size_t n, size_t * off) |
| int | match_ol(const char * s, size_t n, size_t * off) |
| int | match_heading(const char * s, size_t n, int * level, size_t * off) |
| int | list_add_wrapped(n_markdown_block * blk, const mline * lines, size_t a, size_t b, size_t off, unsigned flags) |
| int | line_starts_block(const mline * lines, size_t li, size_t nlines, unsigned flags) |
| int | line_indented(const char * s, size_t n) |
| int | line_blank(const char * s, size_t n) |
| char * | join_lines(const mline * lines, size_t a, size_t b, strip_fn strip, size_t * out_len) |
| int | is_ws(char c) |
| int | is_table_rule(const char * s, size_t n) |
| int | is_md_punct(char c) |
| int | is_http_url(const char * s, size_t n) |
| int | is_digit(char c) |
| int | inl_push(n_markdown_inline * a, char * text, char * url, unsigned style) |
| int | has_pipe(const char * s, size_t n) |
| void | free_inline(n_markdown_inline * inl) |
| void | flush_text(n_markdown_inline * a, tbuf * t, unsigned style) |
| char * | dup_range(const char * s, size_t n) |
| n_markdown_block * | doc_push(n_markdown_doc * d, int kind) |
Defines
| Name | |
|---|---|
| MD_MAX_SPANS | |
| MD_MAX_ITEMS | |
| MD_MAX_HEADING | |
| MD_MAX_DEPTH Most list levels N_MARKDOWN_NESTED tracks; deeper ones count as the last. |
|
| MD_MAX_DEPTH Most list levels N_MARKDOWN_NESTED tracks; deeper ones count as the last. |
|
| MD_MAX_COLS | |
| MD_MAX_BLOCKS |
Detailed Description
Safe-subset Markdown parser.
Author: Castagnier Mickael
Version: 1.0
See n_markdown.h. Bold/italic nesting is flattened into per-span style flags. Bounded against hostile input – caps on blocks/spans/list-items and on bold/italic recursion depth; a cap overflow or an allocation failure truncates safely.
Types Documentation
typedef strip_fn
typedef size_t(* strip_fn) (const char *s, size_t n);
Functions Documentation
function url_end
static size_t url_end(
const char * s,
size_t n,
size_t i
)
function try_strike
static int try_strike(
n_markdown_inline * a,
tbuf * t,
const char * s,
size_t n,
size_t i,
unsigned inherited,
int depth,
unsigned flags,
size_t * ni
)
function try_link
static int try_link(
n_markdown_inline * a,
tbuf * t,
const char * s,
size_t n,
size_t i,
unsigned inherited,
size_t * ni
)
function try_emphasis
static int try_emphasis(
n_markdown_inline * a,
tbuf * t,
const char * s,
size_t n,
size_t i,
unsigned inherited,
int depth,
unsigned flags,
size_t * ni
)
function try_code
static int try_code(
n_markdown_inline * a,
tbuf * t,
const char * s,
size_t n,
size_t i,
unsigned inherited,
size_t * ni
)
function try_autolink
static int try_autolink(
n_markdown_inline * a,
tbuf * t,
const char * s,
size_t n,
size_t i,
unsigned inherited,
size_t * ni
)
function tbuf_add
static int tbuf_add(
tbuf * t,
char c
)
function table_add_row
static void table_add_row(
n_markdown_block * blk,
const char * s,
const md_cells * c,
unsigned flags
)
function table_add_cell
static int table_add_cell(
n_markdown_block * blk,
const char * s,
size_t a,
size_t b,
unsigned flags
)
function strip_quote
static size_t strip_quote(
const char * s,
size_t n
)
function starts_table
static int starts_table(
const mline * lines,
size_t li,
size_t nlines
)
function starts_fence
static int starts_fence(
const char * s,
size_t n
)
function rule_align
static unsigned char rule_align(
const char * s,
size_t a,
size_t b
)
function row_cells
static void row_cells(
const char * s,
size_t n,
md_cells * out
)
function parse_inline
static void parse_inline(
n_markdown_inline * a,
const char * s,
size_t n,
unsigned inherited,
int depth,
unsigned flags
)
function n_markdown_parse_ex
void n_markdown_parse_ex(
const char * text,
size_t len,
unsigned flags,
n_markdown_doc * out
)
parse text into out, choosing which optional block kinds to recognise
Parameters:
- text the UTF-8 source (may be NULL)
- len number of bytes of text
- flags any of N_MARKDOWN_HEADINGS, N_MARKDOWN_TABLES, N_MARKDOWN_AUTOLINKS, N_MARKDOWN_STRIKETHROUGH, N_MARKDOWN_ESCAPES, N_MARKDOWN_ALL, N_MARKDOWN_INLINE_ALL; unknown bits are ignored
- out the document to fill (zeroed first)
n_markdown_parse() is this with flags of zero, and that is the whole difference: the default subset is unchanged, byte for byte, so a caller that never passes a flag can never be handed a block kind it does not know.
Same guarantees as n_markdown_parse(): out is zeroed first, an allocation failure or a bounds cap truncates safely rather than failing, and the result is always released with n_markdown_free().
function n_markdown_parse
void n_markdown_parse(
const char * text,
size_t len,
n_markdown_doc * out
)
parse text (len bytes, plain UTF-8) into out
Parameters:
- text the UTF-8 source (may be NULL)
- len number of bytes of text
- out the document to fill (zeroed first)
Never fails destructively: on allocation failure or a bounds cap it keeps what it has (safe truncation). out is zeroed first. Always pair with n_markdown_free.
function n_markdown_has_markup_ex
int n_markdown_has_markup_ex(
const char * text,
size_t len,
unsigned flags
)
1 if text contains any markup the given flags would render, else 0
Parameters:
- text the UTF-8 source
- len number of bytes of text
- flags as n_markdown_parse_ex()
Return: 1 if there is renderable markup, 0 otherwise
n_markdown_has_markup() is this with flags of zero. A document that is nothing but a heading has no markup in the default subset and has markup with N_MARKDOWN_HEADINGS, which is the answer each caller wants.
function n_markdown_has_markup
int n_markdown_has_markup(
const char * text,
size_t len
)
1 if text contains any renderable markup, else 0
Parameters:
- text the UTF-8 source
- len number of bytes of text
Return: 1 if there is renderable markup, 0 otherwise
Lets a caller skip the styled path for a plain message.
function n_markdown_free
void n_markdown_free(
n_markdown_doc * doc
)
free everything a n_markdown_doc owns and zero it
Parameters:
- doc the document (safe on a zeroed doc and on NULL)
function md_word_char
static int md_word_char(
unsigned char c
)
function md_us_opens
static int md_us_opens(
const char * s,
size_t i
)
function md_us_closes
static int md_us_closes(
const char * s,
size_t n,
size_t j,
size_t len
)
function match_ul
static int match_ul(
const char * s,
size_t n,
size_t * off
)
function match_quote
static int match_quote(
const char * s,
size_t n,
size_t * off
)
function match_ol
static int match_ol(
const char * s,
size_t n,
size_t * off
)
function match_heading
static int match_heading(
const char * s,
size_t n,
int * level,
size_t * off
)
function list_add_wrapped
static int list_add_wrapped(
n_markdown_block * blk,
const mline * lines,
size_t a,
size_t b,
size_t off,
unsigned flags
)
function line_starts_block
static int line_starts_block(
const mline * lines,
size_t li,
size_t nlines,
unsigned flags
)
function line_indented
static int line_indented(
const char * s,
size_t n
)
function line_blank
static int line_blank(
const char * s,
size_t n
)
function join_lines
static char * join_lines(
const mline * lines,
size_t a,
size_t b,
strip_fn strip,
size_t * out_len
)
function is_ws
static int is_ws(
char c
)
function is_table_rule
static int is_table_rule(
const char * s,
size_t n
)
function is_md_punct
static int is_md_punct(
char c
)
function is_http_url
static int is_http_url(
const char * s,
size_t n
)
function is_digit
static int is_digit(
char c
)
function inl_push
static int inl_push(
n_markdown_inline * a,
char * text,
char * url,
unsigned style
)
function has_pipe
static int has_pipe(
const char * s,
size_t n
)
function free_inline
static void free_inline(
n_markdown_inline * inl
)
function flush_text
static void flush_text(
n_markdown_inline * a,
tbuf * t,
unsigned style
)
function dup_range
static char * dup_range(
const char * s,
size_t n
)
function doc_push
static n_markdown_block * doc_push(
n_markdown_doc * d,
int kind
)
Macros Documentation
define MD_MAX_SPANS
#define MD_MAX_SPANS 2048u /* spans per inline flow */
define MD_MAX_ITEMS
#define MD_MAX_ITEMS 1024u /* rows per list, cells per table */
define MD_MAX_HEADING
#define MD_MAX_HEADING 6 /* deepest "######" heading */
define MD_MAX_DEPTH
#define MD_MAX_DEPTH 16
Most list levels N_MARKDOWN_NESTED tracks; deeper ones count as the last.
define MD_MAX_DEPTH
#define MD_MAX_DEPTH 16 /* bold/italic nesting */
Most list levels N_MARKDOWN_NESTED tracks; deeper ones count as the last.
define MD_MAX_COLS
#define MD_MAX_COLS 32u /* columns per table */
define MD_MAX_BLOCKS
#define MD_MAX_BLOCKS 1024u /* blocks per document */
Source code
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_markdown.h"
#include <stdlib.h>
#include <string.h>
#define MD_MAX_BLOCKS 1024u /* blocks per document */
#define MD_MAX_SPANS 2048u /* spans per inline flow */
#define MD_MAX_ITEMS 1024u /* rows per list, cells per table */
#define MD_MAX_DEPTH 16 /* bold/italic nesting */
#define MD_MAX_COLS 32u /* columns per table */
#define MD_MAX_HEADING 6 /* deepest "######" heading */
/* ---- small helpers ------------------------------------------------------ */
static int is_ws(char c) {
return c == ' ' || c == '\t' || c == '\r' || c == '\f' || c == '\v';
}
static int is_digit(char c) {
return c >= '0' && c <= '9';
}
/* Own a NUL-terminated copy of s[0..n); NULL on OOM (treated as truncation). */
static char* dup_range(const char* s, size_t n) {
char* out = malloc(n + 1);
if (!out)
return NULL;
if (n)
memcpy(out, s, n);
out[n] = '\0';
return out;
}
/* A growable byte buffer for the pending plain-text run. */
typedef struct {
char* b;
size_t len, cap;
} tbuf;
static int tbuf_add(tbuf* t, char c) {
if (t->len + 1 > t->cap) {
size_t nc = t->cap ? t->cap * 2 : 32;
char* nb = realloc(t->b, nc);
if (!nb)
return 0;
t->b = nb;
t->cap = nc;
}
t->b[t->len++] = c;
return 1;
}
/* ---- inline parsing ----------------------------------------------------- */
/* Append an owned span (takes ownership of text/url). Drops it (freeing) on a
* cap overflow or a NULL text. Returns 1 if stored. */
static int inl_push(n_markdown_inline* a, char* text, char* url, unsigned style) {
n_markdown_span* ns;
if (!text) {
free(url);
return 0;
}
if (a->n_spans >= MD_MAX_SPANS) {
free(text);
free(url);
return 0;
}
ns = realloc(a->spans, (a->n_spans + 1) * sizeof(*ns));
if (!ns) {
free(text);
free(url);
return 0;
}
a->spans = ns;
ns[a->n_spans].text = text;
ns[a->n_spans].url = url;
ns[a->n_spans].style = style;
a->n_spans++;
return 1;
}
/* Flush the pending plain text (if any) as one span with \p style. */
static void flush_text(n_markdown_inline* a, tbuf* t, unsigned style) {
if (t->len == 0)
return;
(void)inl_push(a, dup_range(t->b, t->len), NULL, style);
t->len = 0;
}
/* Case-insensitive "http://" / "https://" prefix test on s[0..n). */
static int is_http_url(const char* s, size_t n) {
static const char* h = "http";
size_t i = 0;
for (; i < 4; i++)
if (i >= n || (s[i] | 0x20) != h[i])
return 0;
if (i < n && (s[i] | 0x20) == 's')
i++;
return i + 3 <= n && s[i] == ':' && s[i + 1] == '/' && s[i + 2] == '/';
}
static void parse_inline(n_markdown_inline* a, const char* s, size_t n, unsigned inherited, int depth, unsigned flags);
/* Try to match `code` at s[i]; on success emit the span and set *ni past it. */
static int try_code(n_markdown_inline* a, tbuf* t, const char* s, size_t n, size_t i, unsigned inherited, size_t* ni) {
size_t j;
if (s[i] != '`')
return 0;
for (j = i + 1; j < n && s[j] != '`'; j++);
if (j >= n || j == i + 1) /* no closing backtick, or "``": literal */
return 0;
flush_text(a, t, inherited);
(void)inl_push(a, dup_range(s + i + 1, j - (i + 1)), NULL,
inherited | N_MARKDOWN_CODE);
*ni = j + 1;
return 1;
}
/* Try to match [text](url) at s[i]. */
static int try_link(n_markdown_inline* a, tbuf* t, const char* s, size_t n, size_t i, unsigned inherited, size_t* ni) {
size_t close, url0, k;
if (s[i] != '[')
return 0;
for (close = i + 1; close < n && s[close] != ']'; close++);
if (close >= n || close + 1 >= n || s[close + 1] != '(')
return 0;
url0 = close + 2;
for (k = url0; k < n && s[k] != ')' && !is_ws(s[k]); k++);
if (k >= n || s[k] != ')' || k == url0) /* empty url or ws/EOF: no match */
return 0;
flush_text(a, t, inherited);
if (is_http_url(s + url0, k - url0)) {
(void)inl_push(a, dup_range(s + i + 1, close - (i + 1)),
dup_range(s + url0, k - url0),
inherited | N_MARKDOWN_LINK);
} else { /* non-http scheme: render the whole [..](..) literally */
(void)inl_push(a, dup_range(s + i, (k + 1) - i), NULL, inherited);
}
*ni = k + 1;
return 1;
}
/* A character that makes an underscore intraword: an ASCII letter or digit,
* or any byte of a multi-byte UTF-8 sequence (so "café_au_lait" counts). */
static int md_word_char(unsigned char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') || c >= 0x80;
}
/* Underscore emphasis does not open or close INSIDE a word (CommonMark):
* "snake_case_name" and ":throwing_kiss: :wrapped_present:" stay literal
* instead of italicising what lies between two of them. An opener at s[i]
* needs no word character before its run of underscores, a closer of length
* len at s[j] none after its run (so "a__b__c" is literal too). Asterisks
* are unaffected. */
static int md_us_opens(const char* s, size_t i) {
while (i > 0 && s[i - 1] == '_')
i--;
return i == 0 || !md_word_char((unsigned char)s[i - 1]);
}
static int md_us_closes(const char* s, size_t n, size_t j, size_t len) {
size_t k = j + len;
while (k < n && s[k] == '_')
k++;
return k >= n || !md_word_char((unsigned char)s[k]);
}
/* Try bold or italic emphasis at s[i]; recurses on the inner span. */
static int try_emphasis(n_markdown_inline* a, tbuf* t, const char* s, size_t n, size_t i, unsigned inherited, int depth, unsigned flags, size_t* ni) {
char c = s[i];
size_t j;
if (c != '*' && c != '_')
return 0;
if (depth >= MD_MAX_DEPTH) /* too deep: the marker is literal text */
return 0;
if (c == '_' && !md_us_opens(s, i)) /* intraword: literal */
return 0;
if (i + 1 < n && s[i + 1] == c) { /* bold: find the next "cc" */
for (j = i + 2; j + 1 < n; j++)
if (s[j] == c && s[j + 1] == c) {
if (c == '_' && !md_us_closes(s, n, j, 2))
continue;
if (j == i + 2) /* "****" is not an empty bold */
break;
flush_text(a, t, inherited);
parse_inline(a, s + i + 2, j - (i + 2),
inherited | N_MARKDOWN_BOLD, depth + 1, flags);
*ni = j + 2;
return 1;
}
}
/* italic: find the next single c. An empty one ("**" left over from a
* bold that never closed) is not italic: it used to swallow both
* markers, so "an **unclosed run" lost its asterisks. */
for (j = i + 1; j < n; j++)
if (s[j] == c) {
if (c == '_' && j > i + 1 && !md_us_closes(s, n, j, 1))
continue;
if (j == i + 1)
return 0;
flush_text(a, t, inherited);
parse_inline(a, s + i + 1, j - (i + 1),
inherited | N_MARKDOWN_ITALIC, depth + 1, flags);
*ni = j + 1;
return 1;
}
return 0;
}
/* ~~strike~~ (N_MARKDOWN_STRIKETHROUGH): two tildes, then the next "~~".
* Recurses like bold, so ~~**both**~~ carries both styles. */
static int try_strike(n_markdown_inline* a, tbuf* t, const char* s, size_t n, size_t i, unsigned inherited, int depth, unsigned flags, size_t* ni) {
size_t j;
if (!(flags & N_MARKDOWN_STRIKETHROUGH) || s[i] != '~' || i + 1 >= n ||
s[i + 1] != '~' || depth >= MD_MAX_DEPTH)
return 0;
for (j = i + 2; j + 1 < n; j++)
if (s[j] == '~' && s[j + 1] == '~') {
if (j == i + 2) /* "~~~~" is not an empty strike */
return 0;
flush_text(a, t, inherited);
parse_inline(a, s + i + 2, j - (i + 2),
inherited | N_MARKDOWN_STRIKE, depth + 1, flags);
*ni = j + 2;
return 1;
}
return 0;
}
/* Where a bare URL starting at s[i] ends: at white space or a character a URL
* in running text does not carry, then less the trailing punctuation that
* belongs to the sentence ("see https://x.org." ends at the g) and a closing
* parenthesis with no opening one inside the URL ("(https://x.org)"). */
static size_t url_end(const char* s, size_t n, size_t i) {
size_t e = i, k;
long depth = 0;
while (e < n && !is_ws(s[e]) && s[e] != '\n' && s[e] != '<' &&
s[e] != '>' && s[e] != '"' && s[e] != '`')
e++;
for (;;) {
char c;
if (e <= i)
return e;
c = s[e - 1];
if (c == '.' || c == ',' || c == ';' || c == ':' || c == '!' ||
c == '?' || c == '\'' || c == '"' || c == '*' || c == '_' ||
c == '~') {
e--;
continue;
}
if (c == ')') {
depth = 0;
for (k = i; k < e; k++)
depth += s[k] == '(' ? 1 : s[k] == ')' ? -1
: 0;
if (depth < 0) {
e--;
continue;
}
}
return e;
}
}
/* Bare http(s) URLs and <http(s)://...> (N_MARKDOWN_AUTOLINKS): the URL is
* both the text and the link. A bare one starts only at a word boundary, so
* "xhttp://" is text, and needs something after "://". */
static int try_autolink(n_markdown_inline* a, tbuf* t, const char* s, size_t n, size_t i, unsigned inherited, size_t* ni) {
size_t e;
if (s[i] == '<') {
size_t k, u0;
if (!is_http_url(s + i + 1, n - (i + 1)))
return 0;
for (k = i + 1; k < n && s[k] != '>' && !is_ws(s[k]) && s[k] != '\n'; k++);
if (k >= n || s[k] != '>')
return 0;
u0 = i + 1;
e = k;
if (e - u0 <= (((s[u0 + 4] | 0x20) == 's') ? 8u : 7u))
return 0; /* nothing after the "://" */
flush_text(a, t, inherited);
(void)inl_push(a, dup_range(s + u0, e - u0), dup_range(s + u0, e - u0),
inherited | N_MARKDOWN_LINK);
*ni = k + 1;
return 1;
}
if ((s[i] | 0x20) != 'h' || !is_http_url(s + i, n - i))
return 0;
if (i > 0 && (is_digit(s[i - 1]) || ((s[i - 1] | 0x20) >= 'a' && (s[i - 1] | 0x20) <= 'z') ||
s[i - 1] == '/' || (unsigned char)s[i - 1] >= 0x80))
return 0;
e = url_end(s, n, i);
{
size_t scheme = ((s[i + 4] | 0x20) == 's') ? 8 : 7; /* https:// */
if (e <= i + scheme)
return 0; /* nothing after the "://" */
}
flush_text(a, t, inherited);
(void)inl_push(a, dup_range(s + i, e - i), dup_range(s + i, e - i),
inherited | N_MARKDOWN_LINK);
*ni = e;
return 1;
}
/* A backslash before ASCII punctuation (N_MARKDOWN_ESCAPES) is that character,
* literally: "\\*not italic\\*". A backslash before anything else is a
* backslash. */
static int is_md_punct(char c) {
return (c >= '!' && c <= '/') || (c >= ':' && c <= '@') ||
(c >= '[' && c <= '`') || (c >= '{' && c <= '~');
}
/* Parse s[0..n) into inline spans appended to \p a, each carrying \p inherited
* plus whatever emphasis/code/link applies. \p flags are the parse_ex flags;
* only the inline ones matter here. */
static void parse_inline(n_markdown_inline* a, const char* s, size_t n, unsigned inherited, int depth, unsigned flags) {
tbuf t = {0};
size_t i = 0, ni;
while (i < n) {
if ((flags & N_MARKDOWN_ESCAPES) && s[i] == '\\' && i + 1 < n &&
is_md_punct(s[i + 1])) {
if (!tbuf_add(&t, s[i + 1]))
break;
i += 2;
continue;
}
if (try_code(a, &t, s, n, i, inherited, &ni) ||
try_link(a, &t, s, n, i, inherited, &ni) ||
((flags & N_MARKDOWN_AUTOLINKS) &&
try_autolink(a, &t, s, n, i, inherited, &ni)) ||
try_strike(a, &t, s, n, i, inherited, depth, flags, &ni) ||
try_emphasis(a, &t, s, n, i, inherited, depth, flags, &ni)) {
i = ni;
continue;
}
if (!tbuf_add(&t, s[i])) /* OOM: stop, keep what we have */
break;
i++;
}
flush_text(a, &t, inherited);
free(t.b);
}
/* ---- block parsing ------------------------------------------------------ */
/* One line = {ptr, len} into the normalized buffer. */
typedef struct {
const char* p;
size_t n;
} mline;
static int line_blank(const char* s, size_t n) {
size_t i;
for (i = 0; i < n; i++)
if (!is_ws(s[i]) && s[i] != '\n')
return 0;
return 1;
}
static int starts_fence(const char* s, size_t n) {
return n >= 3 && s[0] == '`' && s[1] == '`' && s[2] == '`';
}
/* > blockquote: on match, set *off to the start of the content (past "> "). */
static int match_quote(const char* s, size_t n, size_t* off) {
if (n == 0 || s[0] != '>')
return 0;
*off = 1;
if (*off < n && is_ws(s[*off]))
(*off)++;
return 1;
}
/* "- " / "* " list row: set *off past the marker and its following space(s). */
static int match_ul(const char* s, size_t n, size_t* off) {
if (n < 2 || (s[0] != '-' && s[0] != '*') || !is_ws(s[1]))
return 0;
*off = 1;
while (*off < n && is_ws(s[*off]))
(*off)++;
return 1;
}
/* "1. " list row: set *off past the digits, dot and following space(s). */
static int match_ol(const char* s, size_t n, size_t* off) {
size_t i = 0;
while (i < n && is_digit(s[i]))
i++;
if (i == 0 || i >= n || s[i] != '.')
return 0;
i++;
if (i >= n || !is_ws(s[i]))
return 0;
while (i < n && is_ws(s[i]))
i++;
*off = i;
return 1;
}
/* "## " heading: one to six hashes, then a space, then the text. Without the
* space it is not a heading, which is what keeps a hash tag out. */
static int match_heading(const char* s, size_t n, int* level, size_t* off) {
size_t i = 0, h = 0;
while (i + h < n && s[i + h] == '#')
h++;
if (h == 0 || h > (size_t)MD_MAX_HEADING)
return 0;
i += h;
if (i >= n || s[i] != ' ')
return 0;
while (i < n && is_ws(s[i]))
i++;
*level = (int)h;
*off = i;
return 1;
}
/* The cells of one table row, as [a, b) bounds into the line. */
typedef struct {
size_t a[MD_MAX_COLS];
size_t b[MD_MAX_COLS];
size_t n;
} md_cells;
/* Split a table row on its pipes. A leading and a trailing pipe are both
* optional, which is what GitHub accepts and what the documentation in this
* tree is written with; dropping the trailing one here is what stops the blank
* after it counting as an extra column. There is no escape for a pipe inside a
* cell, because the subset has no backslash escapes at all and inventing one
* here alone would be a rule nothing else in the parser follows. */
static void row_cells(const char* s, size_t n, md_cells* out) {
size_t i = 0;
out->n = 0;
while (i < n && is_ws(s[i]))
i++;
if (i < n && s[i] == '|')
i++;
while (n > i && is_ws(s[n - 1]))
n--;
if (n > i && s[n - 1] == '|')
n--;
for (;;) {
size_t j = i;
while (j < n && s[j] != '|')
j++;
if (out->n >= MD_MAX_COLS)
return;
out->a[out->n] = i;
out->b[out->n] = j;
out->n++;
if (j >= n)
return;
i = j + 1;
}
}
/* Is this the "|---|:-:|" row under a table's header? Every cell has to be
* dashes, with an optional colon at either end and at least one dash. That is
* the whole test, and it is what tells a table apart from a paragraph that
* happens to contain pipes. Split by row_cells, so the rule row and the header
* row can never disagree about how many columns there are. */
static int is_table_rule(const char* s, size_t n) {
md_cells c;
size_t k;
row_cells(s, n, &c);
if (c.n == 0)
return 0;
for (k = 0; k < c.n; k++) {
size_t a = c.a[k], b = c.b[k];
while (a < b && is_ws(s[a]))
a++;
while (b > a && is_ws(s[b - 1]))
b--;
if (a < b && s[a] == ':')
a++;
if (b > a && s[b - 1] == ':')
b--;
if (a >= b)
return 0;
while (a < b) {
if (s[a] != '-')
return 0;
a++;
}
}
return 1;
}
/* Alignment asked for by one cell of the rule row. */
static unsigned char rule_align(const char* s, size_t a, size_t b) {
int left, right;
while (a < b && is_ws(s[a]))
a++;
while (b > a && is_ws(s[b - 1]))
b--;
left = (a < b && s[a] == ':');
right = (b > a && s[b - 1] == ':');
if (left && right)
return N_MARKDOWN_ALIGN_CENTER;
if (right)
return N_MARKDOWN_ALIGN_RIGHT;
return N_MARKDOWN_ALIGN_LEFT;
}
/* Does a line hold a cell separator at all: the cheap half of "is this a
* table", asked of the header row before the rule row below it is looked at. */
static int has_pipe(const char* s, size_t n) {
size_t i;
for (i = 0; i < n; i++)
if (s[i] == '|')
return 1;
return 0;
}
/* Does a table start at line @p li: a row with a pipe in it, and the rule row
* underneath. Both halves are needed. A paragraph may well contain a pipe, and
* a line that has one decides nothing on its own. */
static int starts_table(const mline* lines, size_t li, size_t nlines) {
return has_pipe(lines[li].p, lines[li].n) && li + 1 < nlines &&
is_table_rule(lines[li + 1].p, lines[li + 1].n);
}
/* Would line @p li start a block of its own rather than continue a paragraph?
*
* Takes the whole array because a table cannot be recognised from one line,
* and @p flags because a heading or a table is only a block when it was asked
* for: without the flag those lines are ordinary text and have to keep being
* swallowed by the paragraph around them, exactly as they were before the
* flags existed. */
static int line_starts_block(const mline* lines, size_t li, size_t nlines, unsigned flags) {
const char* s = lines[li].p;
size_t n = lines[li].n, off;
int level;
if ((flags & N_MARKDOWN_HEADINGS) && match_heading(s, n, &level, &off))
return 1;
if ((flags & N_MARKDOWN_TABLES) && starts_table(lines, li, nlines))
return 1;
return starts_fence(s, n) || match_quote(s, n, &off) ||
match_ul(s, n, &off) || match_ol(s, n, &off);
}
/* Append a block; returns a pointer to it, or NULL on a cap/OOM. */
static n_markdown_block* doc_push(n_markdown_doc* d, int kind) {
n_markdown_block* nb;
if (d->n_blocks >= MD_MAX_BLOCKS)
return NULL;
nb = realloc(d->blocks, (d->n_blocks + 1) * sizeof(*nb));
if (!nb)
return NULL;
d->blocks = nb;
memset(&nb[d->n_blocks], 0, sizeof(nb[d->n_blocks]));
nb[d->n_blocks].kind = kind;
return &d->blocks[d->n_blocks++];
}
/* Join lines [a..b) with '\n' into an owned buffer, applying \p strip to each
* line to skip a prefix (quote/list marker). \p strip may be NULL. */
typedef size_t (*strip_fn)(const char* s, size_t n);
static char* join_lines(const mline* lines, size_t a, size_t b, strip_fn strip, size_t* out_len) {
size_t total = 0, i, w = 0;
char* buf;
for (i = a; i < b; i++) {
size_t off = strip ? strip(lines[i].p, lines[i].n) : 0;
total += lines[i].n - off + (i + 1 < b ? 1 : 0);
}
buf = malloc(total + 1);
if (!buf) {
*out_len = 0;
return NULL;
}
for (i = a; i < b; i++) {
size_t off = strip ? strip(lines[i].p, lines[i].n) : 0;
size_t seg = lines[i].n - off;
memcpy(buf + w, lines[i].p + off, seg);
w += seg;
if (i + 1 < b)
buf[w++] = '\n';
}
buf[w] = '\0';
*out_len = w;
return buf;
}
static size_t strip_quote(const char* s, size_t n) {
size_t off;
return match_quote(s, n, &off) ? off : 0;
}
/* Is this line indented under the marker above it? A wrapped list item is
* written that way, and GitHub calls taking it as part of the item above a
* lazy continuation. Without it a list item long enough to wrap ends its own
* list and the rest of the sentence becomes a paragraph. */
static int line_indented(const char* s, size_t n) {
return n > 0 && is_ws(s[0]) && !line_blank(s, n);
}
/* Add one table cell (parse its inline spans). Cells are appended row by row,
* so the array stays row-major with blk->n_cols to a row. */
static int table_add_cell(n_markdown_block* blk, const char* s, size_t a, size_t b, unsigned flags) {
n_markdown_inline* ni;
if (blk->n_items >= MD_MAX_ITEMS)
return 0;
while (a < b && is_ws(s[a]))
a++;
while (b > a && is_ws(s[b - 1]))
b--;
ni = realloc(blk->items, (blk->n_items + 1) * sizeof(*ni));
if (!ni)
return 0;
blk->items = ni;
memset(&ni[blk->n_items], 0, sizeof(ni[blk->n_items]));
parse_inline(&blk->items[blk->n_items], s + a, b - a, 0, 0, flags);
blk->n_items++;
return 1;
}
/* One row of cells, padded or truncated to blk->n_cols so that every row is
* the same length and cell (r, c) is always items[r * n_cols + c]. A row of a
* hand-written table is short or long often enough that a renderer should not
* have to ask. */
static void table_add_row(n_markdown_block* blk, const char* s, const md_cells* c, unsigned flags) {
size_t k;
for (k = 0; k < blk->n_cols; k++) {
if (k < c->n) {
if (!table_add_cell(blk, s, c->a[k], c->b[k], flags))
return;
} else if (!table_add_cell(blk, s, 0, 0, flags)) {
return;
}
}
}
/* Add one list item that may run over several lines: the marker is stripped
* from the first and the indent from the rest, and the lines are joined with a
* space rather than a newline, because they are one sentence that the renderer
* will wrap to whatever width it has. */
static int list_add_wrapped(n_markdown_block* blk, const mline* lines, size_t a, size_t b, size_t off, unsigned flags) {
n_markdown_inline* ni;
char* buf;
size_t total = 0, i, w = 0;
if (blk->n_items >= MD_MAX_ITEMS)
return 0;
for (i = a; i < b; i++) {
size_t skip = (i == a) ? off : 0;
while (i != a && skip < lines[i].n && is_ws(lines[i].p[skip]))
skip++;
total += lines[i].n - skip + 1;
}
buf = malloc(total + 1);
if (!buf)
return 0;
for (i = a; i < b; i++) {
size_t skip = (i == a) ? off : 0;
while (i != a && skip < lines[i].n && is_ws(lines[i].p[skip]))
skip++;
if (w && lines[i].n > skip)
buf[w++] = ' ';
memcpy(buf + w, lines[i].p + skip, lines[i].n - skip);
w += lines[i].n - skip;
}
buf[w] = '\0';
ni = realloc(blk->items, (blk->n_items + 1) * sizeof(*ni));
if (!ni) {
free(buf);
return 0;
}
blk->items = ni;
memset(&ni[blk->n_items], 0, sizeof(ni[blk->n_items]));
parse_inline(&blk->items[blk->n_items], buf, w, 0, 0, flags);
blk->n_items++;
free(buf);
return 1;
}
void n_markdown_parse(const char* text, size_t len, n_markdown_doc* out) {
n_markdown_parse_ex(text, len, 0, out);
}
#define MD_MAX_DEPTH 16
void n_markdown_parse_ex(const char* text, size_t len, unsigned flags, n_markdown_doc* out) {
char* norm;
mline* lines = NULL;
size_t nlines = 0, i, li, start;
/* N_MARKDOWN_NESTED: each line's indentation, stripped from its view
(columns, a tab counting 4, and bytes), and the open list levels */
size_t* lead_col = NULL;
size_t* lead_bytes = NULL;
size_t marker[MD_MAX_DEPTH];
int sp = 0;
const int nested = (flags & N_MARKDOWN_NESTED) != 0;
memset(out, 0, sizeof(*out));
if (!text)
return;
/* Normalize CRLF / lone CR to LF. */
norm = malloc(len + 1);
if (!norm)
return;
{
size_t w = 0;
for (i = 0; i < len; i++) {
if (text[i] == '\r') {
norm[w++] = '\n';
if (i + 1 < len && text[i + 1] == '\n')
i++;
} else {
norm[w++] = text[i];
}
}
norm[w] = '\0';
len = w;
}
/* Split into lines. */
lines = malloc((len + 1) * sizeof(*lines));
if (!lines) {
free(norm);
return;
}
start = 0;
for (i = 0; i <= len; i++) {
if (i == len || norm[i] == '\n') {
lines[nlines].p = norm + start;
lines[nlines].n = i - start;
nlines++;
start = i + 1;
}
}
if (nested) {
lead_col = malloc(nlines * sizeof(*lead_col));
lead_bytes = malloc(nlines * sizeof(*lead_bytes));
if (!lead_col || !lead_bytes) {
free(lead_col);
free(lead_bytes);
free(lines);
free(norm);
return;
}
for (i = 0; i < nlines; i++) {
size_t k = 0, col = 0;
while (k < lines[i].n && is_ws(lines[i].p[k])) {
col += lines[i].p[k] == '\t' ? 4 - col % 4 : 1;
k++;
}
lead_col[i] = col;
lead_bytes[i] = k;
lines[i].p += k;
lines[i].n -= k;
}
}
li = 0;
while (li < nlines) {
const char* s = lines[li].p;
size_t n = lines[li].n, off, nblocks = out->n_blocks;
int depth = 0;
n_markdown_block* blk;
if (line_blank(s, n)) {
li++;
continue;
}
if (nested) {
/* the list levels this line is not indented under end here */
while (sp > 0 && lead_col[li] <= marker[sp - 1])
sp--;
depth = sp;
}
if (flags & N_MARKDOWN_HEADINGS) { /* "## " heading */
int level = 0;
if (match_heading(s, n, &level, &off)) {
blk = doc_push(out, N_MARKDOWN_HEADING);
if (blk) {
blk->level = level;
parse_inline(&blk->inl, s + off, n - off, 0, 0, flags);
}
li++;
goto placed;
}
}
if ((flags & N_MARKDOWN_TABLES) && starts_table(lines, li, nlines)) { /* pipe table */
md_cells head, rule;
row_cells(s, n, &head);
row_cells(lines[li + 1].p, lines[li + 1].n, &rule);
blk = doc_push(out, N_MARKDOWN_TABLE);
if (!blk) {
/* out of blocks: skip the whole table rather than leave its
rows to be read as paragraphs */
li += 2;
while (li < nlines && !line_blank(lines[li].p, lines[li].n) &&
has_pipe(lines[li].p, lines[li].n) &&
!line_starts_block(lines, li, nlines, flags))
li++;
goto placed;
}
/* The header decides the width. A rule row of a different length
is a malformed table, and taking the header's count keeps the
cells and the alignments in step whichever way it differs. */
blk->n_cols = head.n ? head.n : 1;
blk->align = malloc(blk->n_cols);
if (blk->align) {
size_t k;
for (k = 0; k < blk->n_cols; k++)
blk->align[k] = k < rule.n
? rule_align(lines[li + 1].p, rule.a[k], rule.b[k])
: (unsigned char)N_MARKDOWN_ALIGN_LEFT;
}
table_add_row(blk, s, &head, flags);
li += 2;
/* a row is a non-blank line with a pipe that does not start a
block of its own: "# x | y" or "- a|b" after a table used to be
taken as rows */
while (li < nlines && !line_blank(lines[li].p, lines[li].n) &&
has_pipe(lines[li].p, lines[li].n) &&
!line_starts_block(lines, li, nlines, flags)) {
md_cells body;
row_cells(lines[li].p, lines[li].n, &body);
table_add_row(blk, lines[li].p, &body, flags);
li++;
}
goto placed;
}
if (starts_fence(s, n)) { /* ``` fenced code block */
size_t body_a, body_b, fence_lead = nested ? lead_bytes[li] : 0;
char* code;
size_t clen;
li++;
body_a = li;
while (li < nlines && !starts_fence(lines[li].p, lines[li].n))
li++;
body_b = li;
if (li < nlines)
li++; /* closing fence */
if (nested) {
/* the code keeps the indentation it has beyond the fence's */
size_t k;
for (k = body_a; k < body_b; k++) {
size_t keep = lead_bytes[k] > fence_lead ? lead_bytes[k] - fence_lead : 0;
lines[k].p -= keep;
lines[k].n += keep;
}
}
code = join_lines(lines, body_a, body_b, NULL, &clen);
blk = doc_push(out, N_MARKDOWN_CODE_BLOCK);
if (blk)
blk->code = code ? code : dup_range("", 0);
else
free(code);
goto placed;
}
if (match_quote(s, n, &off)) { /* > blockquote */
size_t a = li, joined_len;
char* joined;
while (li < nlines && match_quote(lines[li].p, lines[li].n, &off))
li++;
joined = join_lines(lines, a, li, strip_quote, &joined_len);
blk = doc_push(out, N_MARKDOWN_QUOTE);
if (blk && joined)
parse_inline(&blk->inl, joined, joined_len, 0, 0, flags);
free(joined);
goto placed;
}
if (match_ul(s, n, &off)) { /* unordered list */
const size_t item_lead = nested ? lead_col[li] : 0;
blk = doc_push(out, N_MARKDOWN_UL);
while (li < nlines && match_ul(lines[li].p, lines[li].n, &off) && (!nested || lead_col[li] == item_lead)) {
size_t item_a = li;
/* off is the marker width of THIS item, set by the match in
the condition above; nothing below writes to it, so the
continuation scan can run before it is used. */
li++;
while (li < nlines &&
(nested ? lead_col[li] > item_lead && !line_blank(lines[li].p, lines[li].n)
: line_indented(lines[li].p, lines[li].n)) &&
!line_starts_block(lines, li, nlines, flags))
li++;
if (blk)
(void)list_add_wrapped(blk, lines, item_a, li, off, flags);
}
if (nested && sp < MD_MAX_DEPTH)
marker[sp++] = item_lead;
goto placed;
}
if (match_ol(s, n, &off)) { /* ordered list */
const size_t item_lead = nested ? lead_col[li] : 0;
blk = doc_push(out, N_MARKDOWN_OL);
if (blk && nested)
blk->start = strtol(s, NULL, 10);
while (li < nlines && match_ol(lines[li].p, lines[li].n, &off) && (!nested || lead_col[li] == item_lead)) {
size_t item_a = li;
/* off is the marker width of THIS item, set by the match in
the condition above; nothing below writes to it, so the
continuation scan can run before it is used. */
li++;
while (li < nlines &&
(nested ? lead_col[li] > item_lead && !line_blank(lines[li].p, lines[li].n)
: line_indented(lines[li].p, lines[li].n)) &&
!line_starts_block(lines, li, nlines, flags))
li++;
if (blk)
(void)list_add_wrapped(blk, lines, item_a, li, off, flags);
}
if (nested && sp < MD_MAX_DEPTH)
marker[sp++] = item_lead;
goto placed;
}
{ /* paragraph: consecutive non-blank, non-special lines */
size_t a = li, joined_len;
char* joined;
/* The first line is taken whatever it is. Every block start is
tested above, so a line reaching here is paragraph text by
elimination -- and consuming it unconditionally is what
guarantees this loop advances, whatever a future matcher
decides. A predicate that says "block starts here" about a line
the branches above declined would otherwise spin. */
li++;
while (li < nlines && !line_blank(lines[li].p, lines[li].n) &&
!line_starts_block(lines, li, nlines, flags))
li++;
joined = join_lines(lines, a, li, NULL, &joined_len);
blk = doc_push(out, N_MARKDOWN_PARA);
if (blk && joined)
parse_inline(&blk->inl, joined, joined_len, 0, 0, flags);
free(joined);
}
placed:
/* the blocks this line started sit at its depth */
for (; nblocks < out->n_blocks; nblocks++)
out->blocks[nblocks].depth = depth;
}
free(lead_col);
free(lead_bytes);
free(lines);
free(norm);
}
static void free_inline(n_markdown_inline* inl) {
size_t i;
for (i = 0; i < inl->n_spans; i++) {
free(inl->spans[i].text);
free(inl->spans[i].url);
}
free(inl->spans);
inl->spans = NULL;
inl->n_spans = 0;
}
void n_markdown_free(n_markdown_doc* doc) {
size_t i, j;
if (!doc || !doc->blocks)
return;
for (i = 0; i < doc->n_blocks; i++) {
n_markdown_block* b = &doc->blocks[i];
free_inline(&b->inl);
free(b->code);
for (j = 0; j < b->n_items; j++)
free_inline(&b->items[j]);
free(b->items);
free(b->align);
}
free(doc->blocks);
doc->blocks = NULL;
doc->n_blocks = 0;
}
int n_markdown_has_markup(const char* text, size_t len) {
return n_markdown_has_markup_ex(text, len, 0);
}
int n_markdown_has_markup_ex(const char* text, size_t len, unsigned flags) {
n_markdown_doc d;
int markup = 0;
n_markdown_parse_ex(text, len, flags, &d);
if (d.n_blocks > 1) {
markup = 1;
} else if (d.n_blocks == 1) {
const n_markdown_block* b = &d.blocks[0];
if (b->kind != N_MARKDOWN_PARA) {
markup = 1;
} else {
size_t i;
for (i = 0; i < b->inl.n_spans; i++)
if (b->inl.spans[i].style != 0) {
markup = 1;
break;
}
}
}
n_markdown_free(&d);
if (!markup && (flags & N_MARKDOWN_ESCAPES) && text) {
size_t i;
for (i = 0; i + 1 < len; i++)
if (text[i] == '\\' && is_md_punct(text[i + 1]))
return 1; /* the plain path would show the backslash */
}
return markup;
}
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB