Corpus Transforms
A Mr.Docs extension is a Lua or JavaScript script that runs as part of a build and shapes the documentation in a way templates alone cannot. There are two kinds, and a single script may declare either or both:
-
A corpus transform, covered on this page, rewrites metadata across many symbols at once: backfill briefs from a naming convention, tag symbols by group, mark generated code as "see below" in the output. Transforms run between extraction and rendering, so every generator sees the change.
-
A script generator owns the whole emit: instead of rendering one page per symbol, it traverses the corpus and writes whatever files it wants.
A script declares a transform with mrdocs.register_transform(id, fn) and a generator with mrdocs.register_generator(id, fn). Either way the registered function receives a single context object, ctx.
Languages and locations
Extensions are scripts dropped under extensions/ inside a Mr.Docs addon directory, the same roots that hold the Handlebars templates, with the .js or .lua extension. Mr.Docs aggregates scripts across every addon root and runs them in alphabetical order by full path, with the two languages interleaved.
|
Both scripting languages reach the same
|
Writing a transform
A transform is a function registered with mrdocs.register_transform(id, fn); the id keys its transform-options.<id> parameters, delivered as ctx.params. Mr.Docs invokes each registered function once, in registration order, with the ctx object. A flat view of the corpus reaches the script through ctx.corpus.
-
JavaScript
-
Lua
addons/extensions/noop.jsmrdocs.register_transform("noop", function(ctx) {
// walk ctx.corpus.symbols, assign to the fields you want to change
});
addons/extensions/noop.luamrdocs.register_transform("noop", function(ctx)
-- walk ctx.corpus.symbols, assign to the fields you want to change
end)
The context object
Every registered function receives one argument, ctx:
-
ctx.corpusis the corpus: an object whosesymbolsfield is a flat array of every extracted symbol, plusget(id)andlookup(name)functions that resolve a single symbol (see Iterating symbols). -
ctx.configis the resolved configuration: the same object the templates receive, holding every value from the config file and the command line. See the configuration reference for the available keys. -
ctx.paramsis the extension’s own parameter block, keyed by theidit registered under:transform-options.<id>for a transform,generator-options.<id>for a generator. It is an empty object when the configuration sets none.
A generator’s ctx carries one more field, ctx.output, covered on the Script Generators page.
A script can register any number of transforms and generators, or none at all. If it registers nothing, Mr.Docs warns that the script had no effect and moves on.
Accessing the corpus
Iterating symbols
The ctx.corpus object provides functions that expose the symbol graph. The ctx.corpus.symbols field is a flat array containing every extracted symbol. Scripts that need queries like "all members of `X`" simply walk the array and filter.
For instance, the following scripts count the symbols of each kind and report the totals at the end of the run:
-
JavaScript
-
Lua
addons/extensions/count_by_kind.jsmrdocs.register_transform("count-by-kind", function(ctx) {
var counts = {};
for (var i = 0; i < ctx.corpus.symbols.length; ++i) {
var k = ctx.corpus.symbols[i].kind;
counts[k] = (counts[k] || 0) + 1;
}
for (var k in counts) {
console.log(k + ": " + counts[k]);
}
});
addons/extensions/count_by_kind.luamrdocs.register_transform("count-by-kind", function(ctx)
local counts = {}
for _, sym in ipairs(ctx.corpus.symbols) do
counts[sym.kind] = (counts[sym.kind] or 0) + 1
end
for k, v in pairs(counts) do
print(k .. ": " .. v)
end
end)
Each entry in ctx.corpus.symbols is a proxy for a live Mr.Docs symbol. The fields of each object are at the DOM reference.
Looking up a symbol
When a script knows a symbol’s id and needs to act on that one symbol:
-
ctx.corpus.get(id)returns the proxy for it ornullif the id is unknown -
ctx.corpus.lookup(name)does a global-namespace name lookup and returns the proxy (ornull)
subclass-tree.cpp/// The root of the shape hierarchy.
struct Shape {};
/// A shape with straight sides.
struct Polygon : Shape {};
/// A three-sided polygon.
struct Triangle : Polygon {};
/// A four-sided polygon.
struct Quadrilateral : Polygon {};
/// A quadrilateral with equal sides.
struct Square : Quadrilateral {};
/// A round shape.
struct Circle : Shape {};
-
JavaScript
-
Lua
addons/extensions/subclass_tree.jsmrdocs.register_transform("subclass-tree", function(ctx) {
const base = ctx.corpus.lookup("Shape");
if (base) {
console.log(base.name);
listSubclasses(ctx.corpus, base, " ");
}
});
/**
* Print the inheritance subtree below a record, one class per line, indented by
* depth. Each record's `derived` list holds the ids of its direct subclasses;
* `corpus.get` turns each id back into a symbol, and the recursion follows them
* down the graph.
* @param {object} corpus - `ctx.corpus`.
* @param {object} sym - The record whose subclasses to print.
* @param {string} indent - The leading whitespace for this level.
*/
function listSubclasses(corpus, sym, indent) {
for (let i = 0; i < sym.derived.length; ++i) {
const child = corpus.get(sym.derived[i]);
if (child) {
console.log(indent + child.name);
listSubclasses(corpus, child, indent + " ");
}
}
}
addons/extensions/subclass_tree.lualocal listSubclasses
mrdocs.register_transform("subclass-tree", function(ctx)
local base = ctx.corpus.lookup("Shape")
if base then
print(base.name)
listSubclasses(ctx.corpus, base, " ")
end
end)
--- Print the inheritance subtree below a record, one class per line, indented
--- by depth. Each record's `derived` list holds the ids of its direct
--- subclasses; `corpus.get` turns each id back into a symbol, and the recursion
--- follows them down the graph.
--- @param corpus table `ctx.corpus`.
--- @param sym table The record whose subclasses to print.
--- @param indent string The leading whitespace for this level.
function listSubclasses(corpus, sym, indent)
for _, id in ipairs(sym.derived) do
local child = corpus.get(id)
if child then
print(indent .. child.name)
listSubclasses(corpus, child, indent .. " ")
end
end
end
Running either script against the fixture above prints:
Shape
Circle
Polygon
Quadrilateral
Square
Triangle
Emitting warnings
A read-only transform can do more than compute totals: it can report problems it finds back to Mr.Docs. mrdocs.report.warn(…) and mrdocs.report.error(…) route through the same diagnostic system as the rest of the tool, so their messages are formatted, counted in the run’s totals, and escalated by warn-as-error just like a built-in warning. They take the same free-form arguments as console.log.
A natural use is checking documentation against your project’s conventions. The example below checks for a "low-quality brief" that reads like a stray comment or is too short. A complete, runnable example lives at examples/extensions/project-conventions/.
project-conventions.cpp/// Reset the parser to its initial state.
void reset();
/// x
void flush();
/// >>>>>>>>>>>>
void sync();
-
JavaScript
-
Lua
addons/extensions/project_conventions.js// Check each symbol's documentation against the project's conventions. Every
// convention is a small predicate; this transform warns about each symbol that
// breaks one, routing through mrdocs.report so the messages count as real
// diagnostics. The one convention checked here flags a "low-quality brief": a
// brief that reads like a stray comment rather than a description.
mrdocs.register_transform("project-conventions", function(ctx) {
for (let i = 0; i < ctx.corpus.symbols.length; ++i) {
const sym = ctx.corpus.symbols[i];
const brief = symbolBrief(sym);
if (isLowQualityBrief(brief)) {
mrdocs.report.warn(sym.name + ': low-quality brief: "' + brief + '"');
}
}
});
/**
* The text of a documentation node, gathered by walking its children.
* @param {object} node - A documentation node with `literal` and `children`.
* @returns {string} The concatenated text of the node and its descendants.
*/
function nodeText(node) {
let text = node.literal || "";
if (node.children) {
for (let i = 0; i < node.children.length; ++i) {
text += nodeText(node.children[i]);
}
}
return text;
}
/**
* A symbol's brief as plain text, trimmed, or "" when it has none.
* @param {object} sym - The symbol to read.
* @returns {string} The brief text, or an empty string.
*/
function symbolBrief(sym) {
if (!sym.doc || !sym.doc.brief) {
return "";
}
return nodeText(sym.doc.brief).replace(/^\s+|\s+$/g, "");
}
/**
* Whether a brief is low-quality: it reads like a stray comment rather than a
* description because it is too short or more punctuation than words.
* @param {string} brief - The brief text, already trimmed.
* @returns {boolean} True when the brief breaks the convention.
*/
function isLowQualityBrief(brief) {
if (brief.length === 0) {
return false;
}
let special = 0, total = 0;
for (let i = 0; i < brief.length; ++i) {
if (brief[i] === " ") {
continue;
}
total += 1;
if (!/[A-Za-z0-9]/.test(brief[i])) {
special += 1;
}
}
return brief.length < 4 || special > total / 2;
}
addons/extensions/project_conventions.lualocal node_text, symbol_brief, is_low_quality_brief
-- Check each symbol's documentation against the project's conventions. Every
-- convention is a small predicate; this transform warns about each symbol that
-- breaks one, routing through mrdocs.report so the messages count as real
-- diagnostics. The one convention checked here flags a "low-quality brief": a
-- brief that reads like a stray comment rather than a description.
mrdocs.register_transform("project-conventions", function(ctx)
for _, sym in ipairs(ctx.corpus.symbols) do
local brief = symbol_brief(sym)
if is_low_quality_brief(brief) then
mrdocs.report.warn(sym.name .. ': low-quality brief: "' .. brief .. '"')
end
end
end)
--- The text of a documentation node, gathered by walking its children.
--- @param node table A documentation node with `literal` and `children`.
--- @return string The concatenated text of the node and its descendants.
function node_text(node)
local text = node.literal or ""
if node.children then
for _, child in ipairs(node.children) do
text = text .. node_text(child)
end
end
return text
end
--- A symbol's brief as plain text, trimmed, or "" when it has none.
--- @param sym table The symbol to read.
--- @return string The brief text, or an empty string.
function symbol_brief(sym)
if not (sym.doc and sym.doc.brief) then
return ""
end
return node_text(sym.doc.brief):match("^%s*(.-)%s*$")
end
--- Whether a brief is low-quality: it reads like a stray comment rather than a
--- description because it is too short or more punctuation than words.
--- @param brief string The brief text, already trimmed.
--- @return boolean True when the brief breaks the convention.
function is_low_quality_brief(brief)
if #brief == 0 then
return false
end
local special, total = 0, 0
for c in brief:gmatch(".") do
if c ~= " " then
total = total + 1
if not c:match("%w") then special = special + 1 end
end
end
return #brief < 4 or special > total / 2
end
Running either script against the fixture above warns:
flush: low-quality brief: "x"
sync: low-quality brief: ">>>>>>>>>>>>"
Modifying the corpus
Scripts modify the corpus by assigning to fields on a symbol proxy. Each assignment lands directly in the underlying Mr.Docs symbol. The runtime validates each assignment and raises an exception on an invalid value. An uncaught error in an extension aborts the build and includes the script’s path and the error message.
Most extensions read some attribute of a symbol and write back a string, an enumerator, or a small structured value. For instance, a codebase that follows "any function whose name starts with is_<name> is a predicate returning true if its <name> holds" encodes information that the brief could repeat verbatim:
brief-from-name.cppbool is_prime(int n);
bool is_palindrome(char const* s);
bool is_empty(char const* s);
-
JavaScript
-
Lua
addons/extensions/brief_from_name.jsmrdocs.register_transform("brief-from-name", function(ctx) {
for (let i = 0; i < ctx.corpus.symbols.length; ++i) {
fillPredicateDoc(ctx.corpus.symbols[i]);
}
});
/**
* Fill in the brief and parameter doc of an `is_*` predicate from its name.
* The subject is the name after `is_` with underscores turned to spaces, so
* `is_prime` becomes "Returns true if prime." Does nothing for a symbol that is
* not an `is_*` function, and never overwrites a field an author already wrote.
* @param {object} sym - The symbol to document, mutated in place.
*/
function fillPredicateDoc(sym) {
if (sym.kind !== "function" || sym.name.indexOf("is_") !== 0) {
return;
}
if (!sym.doc) { sym.doc = {}; }
const subject = sym.name.slice(3).replace(/_/g, " ");
if (!sym.doc.brief) {
sym.doc.brief = "Returns true if " + subject + ".";
}
if (sym.params.length === 1
&& (!sym.doc.params || sym.doc.params.length === 0)) {
sym.doc.params = [{
name: sym.params[0].name,
children: "The input examined for the " + subject + " property."
}];
}
}
addons/extensions/brief_from_name.lualocal fillPredicateDoc
mrdocs.register_transform("brief-from-name", function(ctx)
for _, sym in ipairs(ctx.corpus.symbols) do
fillPredicateDoc(sym)
end
end)
--- Fill in the brief and parameter doc of an `is_*` predicate from its name.
--- The subject is the name after `is_` with underscores turned to spaces, so
--- `is_prime` becomes "Returns true if prime." Does nothing for a symbol that
--- is not an `is_*` function, and never overwrites a field an author already
--- wrote.
--- @param sym table The symbol to document, mutated in place.
function fillPredicateDoc(sym)
if sym.kind ~= "function" or sym.name:sub(1, 3) ~= "is_" then
return
end
if not sym.doc then sym.doc = {} end
local subject = sym.name:sub(4):gsub("_", " ")
if not sym.doc.brief then
sym.doc.brief = "Returns true if " .. subject .. "."
end
if #sym.params == 1
and (not sym.doc.params or #sym.doc.params == 0) then
sym.doc.params = {
{
name = sym.params[1].name,
children = "The input examined for the "
.. subject .. " property."
}
}
end
end
Every is_foo_bar function then ships with "Returns true if foo bar." Authors only have to write a brief when the synthesized one is not the right one.
is_empty
Returns true if empty.
Synopsis
Declared in <brief‐from‐name.cpp>
bool
is_empty(char const* s);
Parameters
Name |
Description |
s |
The input examined for the empty property. |
is_palindrome
Returns true if palindrome.
Synopsis
Declared in <brief‐from‐name.cpp>
bool
is_palindrome(char const* s);
Parameters
Name |
Description |
s |
The input examined for the palindrome property. |
is_prime
Returns true if prime.
Synopsis
Declared in <brief‐from‐name.cpp>
bool
is_prime(int n);
Parameters
Name |
Description |
n |
The input examined for the prime property. |
Cross-linking Symbols
When the value being written needs to reference another symbol, the second symbol’s id is what makes the link clickable in the rendered output rather than a plain string.
For instance, consider a project where the parse_X and format_X free functions are symmetric. A reader landing on one almost always wants to see the other. The extension builds a name → id index in one pass, then walks it again to look up each partner:
parse-format-relates.cpp/// An HTTP request as a structured value.
struct request;
/// Parse `text` into a request. Returns a valid request on success.
request parse_request(char const* text);
/// Format `r` as the wire-format text of an HTTP request.
char const* format_request(request const& r);
/// A user record.
struct user;
/// Parse `text` into a user.
user parse_user(char const* text);
/// Format `u` as the canonical wire-format text of a user.
char const* format_user(user const& u);
-
JavaScript
-
Lua
addons/extensions/parse_format_relates.jsmrdocs.register_transform("parse-format-relates", function(ctx) {
for (let i = 0; i < ctx.corpus.symbols.length; ++i) {
const s = ctx.corpus.symbols[i];
if (s.kind === "function") {
const pname = partnerName(s.name);
const partner = pname ? ctx.corpus.lookup(pname) : null;
if (partner) {
s.doc = {
sees: [{
children: [{
kind: "reference",
literal: pname,
id: partner.id
}]
}]
};
}
}
}
});
/**
* The name of a function's symmetric IO partner: `format_X` for a `parse_X`,
* and `parse_X` for a `format_X`. Returns null for any name that is neither.
* @param {string} name - The function's name.
* @returns {string|null} The partner's name, or null if `name` is not a
* `parse_`/`format_` helper.
*/
function partnerName(name) {
let partner = null;
if (name.indexOf("parse_") === 0) {
partner = "format_" + name.slice(6);
} else if (name.indexOf("format_") === 0) {
partner = "parse_" + name.slice(7);
}
return partner;
}
addons/extensions/parse_format_relates.lualocal partnerName
mrdocs.register_transform("parse-format-relates", function(ctx)
for _, s in ipairs(ctx.corpus.symbols) do
if s.kind == "function" then
local pname = partnerName(s.name)
if pname then
local partner = ctx.corpus.lookup(pname)
if partner then
s.doc = {
sees = {
{
children = {
{ kind = "reference",
literal = pname,
id = partner.id }
}
}
}
}
end
end
end
end
end)
--- The name of a function's symmetric IO partner: `format_X` for a `parse_X`,
--- and `parse_X` for a `format_X`. Returns nil for any name that is neither.
--- @param name string The function's name.
--- @return string|nil partner The partner's name, or nil if `name` is not a
--- `parse_`/`format_` helper.
function partnerName(name)
local partner = nil
if name:sub(1, 6) == "parse_" then
partner = "format_" .. name:sub(7)
elseif name:sub(1, 7) == "format_" then
partner = "parse_" .. name:sub(8)
end
return partner
end
request
An HTTP request as a structured value.
Synopsis
Declared in <parse‐format‐relates.cpp>
struct request;
Non-Member Functions
Name |
Description |
Format |
|
Parse |
user
A user record.
Synopsis
Declared in <parse‐format‐relates.cpp>
struct user;
Non-Member Functions
Name |
Description |
Format |
|
Parse |
format_request
Format r as the wire‐format text of an HTTP request.
Synopsis
Declared in <parse‐format‐relates.cpp>
char const*
format_request(request const& r);
Parameters
Name |
Description |
r |
An HTTP request as a structured value. |
See Also
format_user
Format u as the canonical wire‐format text of a user.
Synopsis
Declared in <parse‐format‐relates.cpp>
char const*
format_user(user const& u);
Parameters
Name |
Description |
u |
A user record. |
See Also
parse_request
Parse text into a request. Returns a valid request on success.
Synopsis
Declared in <parse‐format‐relates.cpp>
request
parse_request(char const* text);
Return Value
An HTTP request as a structured value.
See Also
parse_user
Parse text into a user.
Synopsis
Declared in <parse‐format‐relates.cpp>
user
parse_user(char const* text);
Return Value
A user record.
See Also
The two-pass shape (index, then look up) is the idiom whenever a write needs to refer to a symbol the script hasn’t yet seen during the walk.
|
Notice in this example that |
Reading files
After being presented with the arguments for generated reference documentation, a common objection is that the prose should be mostly written by technical writers, not developers. The rationale is not to clutter the headers, and that technical writers can focus on use cases the users are interested in and dedicate themselves to long tutorials.
A transform extension can bridge the two by also reading documentation from external sources. While developers write documentation in code that is verifiably correct and guaranteed never to drift, a writer keeps the extra documentation alongside the project. The ghostwrite transform extension fills in each symbol’s description from the corresponding file. A complete, runnable example lives at examples/extensions/ghostwriter/.
-
Transform
-
Input
-
Vec2 docs
-
Circle docs
-
distance docs
-
Config
addons/extensions/ghostwriter.lualocal read_file, doc_path, paragraphs, blocks, inlines
mrdocs.register_transform("ghostwriter", function(ctx)
local root = (ctx.params and ctx.params.source) or "external-docs"
for _, sym in ipairs(ctx.corpus.symbols) do
-- Fill the description from the file when the symbol has none of its own.
-- The brief written in the header is a separate field, so it is kept; the
-- writer's file supplies the detailed description the developer left out.
if sym.name ~= nil and (not sym.doc or #(sym.doc.document or {}) == 0) then
local text = read_file(doc_path(ctx, sym, root))
if text then
local new_blocks = blocks(paragraphs(text))
if #new_blocks > 0 then
if not sym.doc then
sym.doc = {}
end
sym.doc.document = new_blocks
end
end
end
end
end)
--- Read a whole file into a string.
--- @param path string Path to the file, relative to the run's working directory.
--- @return string|nil The file's contents, or nil when the file does not exist.
function read_file(path)
local f = io.open(path, "r")
if not f then
return nil
end
local content = f:read("*a")
f:close()
return content
end
--- The external file that documents a symbol, mirroring its scope.
--- For example app::Vec2 maps to `<root>/app/Vec2.md`.
--- @param ctx table The transform context, used to walk `parent` ids.
--- @param sym table The symbol to locate a file for.
--- @param root string The source directory holding the writer's files.
--- @return string The path to the symbol's Markdown file.
function doc_path(ctx, sym, root)
local parts = {}
local cur = sym
while cur and cur.name ~= nil do
table.insert(parts, 1, cur.name)
cur = cur.parent and ctx.corpus.get(cur.parent) or nil
end
return root .. "/" .. table.concat(parts, "/") .. ".md"
end
--- Split Markdown text into paragraphs.
--- Paragraphs are separated by blank lines; a soft line break inside a paragraph
--- is collapsed to a single space.
--- @param text string The raw Markdown.
--- @return string[] One entry per non-empty paragraph.
function paragraphs(text)
local out = {}
text = text:gsub("\r\n", "\n")
for para in (text .. "\n\n"):gmatch("(.-)\n\n") do
para = para:gsub("%s*\n%s*", " "):gsub("^%s+", ""):gsub("%s+$", "")
if #para > 0 then
out[#out + 1] = para
end
end
return out
end
--- Wrap each paragraph string in a documentation paragraph block.
--- @param paras string[] Paragraphs, as returned by `paragraphs`.
--- @return table[] Paragraph block nodes, suitable for `sym.doc.document`.
function blocks(paras)
local out = {}
for _, p in ipairs(paras) do
out[#out + 1] = { kind = "paragraph", children = inlines(p) }
end
return out
end
--- Parse one paragraph into documentation inline nodes.
--- Text wrapped in single backticks becomes an inline-code node; everything else
--- becomes plain text. Only backtick code spans are handled here; a richer
--- transform could parse more of Markdown.
--- @param text string A single paragraph, with no line breaks.
--- @return table[] Inline nodes, suitable as a block's `children`.
function inlines(text)
local nodes = {}
local is_code = false
for segment in (text .. "`"):gmatch("(.-)`") do
if #segment > 0 then
if is_code then
nodes[#nodes + 1] = { kind = "code", children = { { kind = "text", literal = segment } } }
else
nodes[#nodes + 1] = { kind = "text", literal = segment }
end
end
is_code = not is_code
end
return nodes
end
simple.cpp/// Application value types.
namespace app {
/// A point or displacement in the plane.
struct Vec2
{
/// The x component.
double x;
/// The y component.
double y;
/// The Euclidean length.
///
/// @return The straight-line length of the vector.
double length() const;
};
/// A circle in the plane.
struct Circle
{
/// The radius.
double radius;
/// The enclosed area.
///
/// @return The area enclosed by the circle.
double area() const;
};
/// The distance between two points.
///
/// @param a The first point.
/// @param b The second point.
/// @return The straight-line distance between `a` and `b`.
double distance(Vec2 const& a, Vec2 const& b);
}
external-docs/app/Vec2.mdA `Vec2` stores a pair of Cartesian coordinates and doubles as both a point
and a displacement. Arithmetic on it follows the usual rules for a 2-D vector
space, so adding two vectors adds their components and scaling multiplies them.
Prefer `Vec2` over a bare `std::pair<double, double>` when the values are
geometric: the named `x` and `y` members and the geometry helpers make intent
obvious at the call site, and keep unrelated pairs from being mixed in by
mistake.
external-docs/app/Circle.mdA `Circle` is defined entirely by its radius; its center is supplied by the
surrounding coordinate system rather than stored here. This keeps the type
small enough to pass by value.
The radius is assumed non-negative. Passing a negative radius is a precondition
violation, not a runtime error, so the geometry helpers do not check for it.
external-docs/app/distance.md`distance` returns the Euclidean distance between two points: the length of the
straight segment joining them, or equivalently the square root of the summed
squared differences of their `x` and `y` components.
The value is symmetric in its arguments and is zero exactly when the two points
coincide, so it works well as the basis for an equality-with-tolerance check.
mrdocs.ymladdons-supplemental:
- addons
generator: adoc
multipage: false
show-namespaces: false
warn-if-undocumented: false
source-root: .
input:
- .
transform-options:
ghostwriter:
source: external-docs
The example is a Lua transform that reads the corresponding Markdown files by the technical writer. The code includes a brief for each symbol and its metadata, while the writer provides a longer description. The mrdocs.yml configuration file loads the transform and points it at the source directory through transform-options.ghostwriter.source.
The transform reads the file whose path matches each symbol’s scope and sets its description from the file’s paragraphs. It only touches the description; every other field the developer wrote stays. The distance function shows the split: its brief, its @param entries, and its @return all come from the header, while the writer’s file supplies just the longer description. The rendered page carries both sources at once:
app::Circle
A circle in the plane.
Synopsis
Declared in <simple.cpp>
struct Circle;
Description
A Circle is defined entirely by its radius; its center is supplied by the surrounding coordinate system rather than stored here. This keeps the type small enough to pass by value.
The radius is assumed non‐negative. Passing a negative radius is a precondition violation, not a runtime error, so the geometry helpers do not check for it.
Member Functions
Name |
Description |
The enclosed area. |
Data Members
Name |
Description |
The radius. |
app::Circle::area
The enclosed area.
Synopsis
Declared in <simple.cpp>
double
area() const;
Return Value
The area enclosed by the circle.
app::Circle::radius
The radius.
Synopsis
Declared in <simple.cpp>
double radius;
app::Vec2
A point or displacement in the plane.
Synopsis
Declared in <simple.cpp>
struct Vec2;
Description
A Vec2 stores a pair of Cartesian coordinates and doubles as both a point and a displacement. Arithmetic on it follows the usual rules for a 2‐D vector space, so adding two vectors adds their components and scaling multiplies them.
Prefer Vec2 over a bare std::pair<double, double> when the values are geometric: the named x and y members and the geometry helpers make intent obvious at the call site, and keep unrelated pairs from being mixed in by mistake.
Member Functions
Name |
Description |
The Euclidean length. |
Data Members
Name |
Description |
The x component. |
|
The y component. |
Non-Member Functions
Name |
Description |
The distance between two points. |
app::Vec2::length
The Euclidean length.
Synopsis
Declared in <simple.cpp>
double
length() const;
Return Value
The straight‐line length of the vector.
app::Vec2::x
The x component.
Synopsis
Declared in <simple.cpp>
double x;
app::Vec2::y
The y component.
Synopsis
Declared in <simple.cpp>
double y;
app::distance
The distance between two points.
Synopsis
Declared in <simple.cpp>
Description
distance returns the Euclidean distance between two points: the length of the straight segment joining them, or equivalently the square root of the summed squared differences of their x and y components.
The value is symmetric in its arguments and is zero exactly when the two points coincide, so it works well as the basis for an equality‐with‐tolerance check.
Return Value
The straight‐line distance between a and b.
Parameters
Name |
Description |
a |
The first point. |
b |
The second point. |