naxp library APIs

naxp libraries are available in a number of languages with the following run-time functionality:

  • Parse. Parse a naxp pattern and, if successful, return an object representing the naxp.
  • Encode. Map text to its encoded value.
  • Decode. Map an encoded value back to (canonical) text.
  • Validate. Check whether text is valid for a naxp, among other checks.
  • Generate code for a specific naxp.

Tip If you want to implement a specific naxp without linking a library or paying a run-time cost, the code-gen page writes the functions for you.

C

A plain C interface over the C++17 library, for C programs and for any language that can call C.

Install

For builds not using CMake, copy the self-contained amalgamated source naxp.cpp plus naxp.h from the latest release, and link with the C++ driver (g++ or clang++), since the library behind the interface is C++. With CMake, fetch the library with FetchContent, declaring C++ as well as C so that CMake links the C++ runtime:

project(your_project LANGUAGES C CXX)

include(FetchContent)
FetchContent_Declare(naxp
	GIT_REPOSITORY https://github.com/naxp-org/naxp.git
	SOURCE_SUBDIR src/cpp)
FetchContent_MakeAvailable(naxp)

target_link_libraries(your_target PRIVATE naxp::naxp)

Example

#include <naxp/naxp.h>

const char *pattern = "\\A\\A?\\9\\X? \\s!! \\9\\A\\A | GIR \\s!! 0AA";
naxp_error *error = NULL;
naxp *postcode = naxp_parse(pattern, strlen(pattern), &error);
char text[16];

naxp_max_encoded_value(postcode);  /* 1755842401 */
naxp_max_length(postcode);  /* 8 */
naxp_encode_cstr(postcode, "EC4M 8AD");  /* 278794572 */
naxp_encode_cstr(postcode, "EC4M8AD");  /* 278794572 (same as above) */
naxp_decode_cstr(postcode, 278794572, text, sizeof text);  /* true, text is "EC4M 8AD" */
naxp_canonical_form_cstr(postcode, "EC4M8AD", text, sizeof text);  /* true, text is "EC4M 8AD" */
naxp_accepts_cstr(postcode, "invalid");  /* false */
naxp_encode_cstr(postcode, "invalid");  /* 0 */

naxp_free(postcode);

API

Notes:

  • API functions ending _cstr accept and write NUL-terminated strings.
  • Other functions take a string length, and write no NUL terminator.
  • More detail is available in the doc comments in naxp.h.
OperationCallReturns
Parsenaxp_parse(pattern, pattern_length, &error)A naxp *, or NULL where the pattern is invalid, with error set. Pass NULL for error where you only want to know whether it parsed.
Read an errornaxp_error_code(error), naxp_error_message(error), naxp_error_offset(error), naxp_error_length(error)The parts of the error. Free it with naxp_error_free(error).
Standard naxpnaxp_standard_naxp(id, variant)A naxp * for one of the standard naxps, or NULL where there is no such standard or variant. The id and variant match without regard to case, and NULL for variant means the default. Free it with naxp_free.
Freenaxp_free(postcode)Nothing. NULL is allowed.
Patternnaxp_pattern(postcode)The pattern the naxp was parsed from, NUL-terminated.
Largest encoded valuenaxp_max_encoded_value(postcode)A uint64_t, which is also how many encoded values there are.
Longest textnaxp_max_length(postcode)The length of the longest text a value decodes to, so a buffer of this length plus one always suffices.
Acceptsnaxp_accepts(postcode, text, text_length), naxp_accepts_cstr(postcode, text)Whether the text is valid for the naxp.
Encodenaxp_encode(postcode, text, text_length), naxp_encode_cstr(postcode, text)The encoded value, from 1 to the largest, or 0 where the text is invalid.
Decodenaxp_decode(postcode, value, destination, capacity, &length), naxp_decode_cstr(postcode, value, destination, capacity)true with the canonical text written, or false where the value is out of range or the destination is too short.
Canonical formnaxp_canonical_form(postcode, text, text_length, destination, capacity, &length), naxp_canonical_form_cstr(postcode, text, destination, capacity)true with the canonical form written, or false where the text is invalid or the destination is too short.
Generate codenaxp_emit(postcode, language, prefix, value_type, initial_indent, new_line, indent, &length)Source code for this naxp in another language, to free with naxp_string_free. See code-gen.
Comparenaxp_compare(a, b, &comparison), naxp_compare_within(a, b, &comparison, budget)How two naxps relate, or false where the budget runs out. Rarely needed outside migrating.
First divergent valuenaxp_first_divergent_value(a, b)The lowest value both naxps hold but decode differently, or 0.

C++

A C++17 library in the namespace logmu.

Install

For builds not using CMake, copy the self-contained amalgamated source naxp.cpp plus naxp.hpp from the latest release. With CMake, fetch the library with FetchContent:

include(FetchContent)
FetchContent_Declare(naxp
	GIT_REPOSITORY https://github.com/naxp-org/naxp.git
	SOURCE_SUBDIR src/cpp)
FetchContent_MakeAvailable(naxp)

target_link_libraries(your_target PRIVATE naxp::naxp)

Example

#include <naxp/naxp.hpp>

const auto postcode = logmu::naxp::parse(R"(\A\A?\9\X? \s!! \9\A\A | GIR \s!! 0AA)");

postcode.max_encoded_value();  // 1755842401
postcode.max_length();  // 8
postcode.encode("EC4M 8AD");  // 278794572
postcode.encode("EC4M8AD");  // 278794572 (same as above)
postcode.decode(278794572);  // "EC4M 8AD" (canonical form)
*postcode.canonical_form("EC4M8AD");  // "EC4M 8AD" (canonical form)
postcode.accepts("invalid");  // false
postcode.encode("invalid");  // 0

API

Notes:

  • naxp instances are cheap to copy and thread-safe.
  • Every API function text argument is a std::string_view.
  • More detail is available in the doc comments in naxp.hpp.
OperationCallReturns
Parsenaxp::parse(pattern)A naxp, or throws naxp_format_error, whose error() holds the error.
Try to parsenaxp::try_parse(pattern, &error)A std::optional<naxp>, empty where the pattern is invalid, with the naxp_error filled in. The error argument is optional.
Standard naxpnaxp::standard_naxp(id, variant), naxp::try_standard_naxp(id, variant)One of the standard naxps, such as naxp::standard_naxp("uk-postcode"). Throws std::invalid_argument, or returns an empty std::optional, where there is none. The id and variant match without regard to case, and variant is optional.
All standard naxpsnaxp::all_standard_naxps()A standard_naxp_variant for each variant, with its id, name, variant, pattern and notes.
Patternpattern()The pattern the naxp was parsed from.
Largest encoded valuemax_encoded_value()A std::uint64_t, which is also how many encoded values there are.
Longest textmax_length()The length of the longest text a value decodes to, so a buffer of this length always suffices.
Acceptsaccepts(text)Whether the text is valid for the naxp.
Encodeencode(text)The encoded value, from 1 to max_encoded_value(), or 0 where the text is invalid.
Decodedecode(value)The canonical text, or throws std::out_of_range where the value is out of range.
Try to decodetry_decode(value, text), try_decode(value, destination, capacity, length)true with the canonical text set or written, or false where the value is out of range or the destination is too short.
Canonical formcanonical_form(text), try_canonical_form(text, canonical_form), try_canonical_form(text, destination, capacity, length)The canonical form of the text, as a std::optional<std::string> or a bool, empty or false where the text is invalid.
Generate codeemit(language, prefix, value_type)Source code for this naxp in another language. See code-gen.
Comparenaxp::compare(a, b), naxp::try_compare(a, b, comparison), each with an optional budgetHow two naxps relate. Rarely needed outside migrating.
First divergent valuenaxp::first_divergent_value(a, b)The lowest value both naxps hold but decode differently, or 0.

C#

.NET library and source generator.

The source generator compiles a naxp at build time (just like C# does for regexes). The generated code needs C# 7.3 or later, and the source generator needs the .NET 8 SDK or Visual Studio 2022 17.8 or later.

Install

dotnet add package naxp

The NuGet package targets .NET 8 and .NET Standard 2.0, which means it runs on .NET Framework.

Example

using LogMu;

var postcode = Naxp.Parse(@"\A\A?\9\X? \s!! \9\A\A | GIR \s!! 0AA");

postcode.MaxEncodedValue;  // 1755842401
postcode.MaxLength;  // 8
postcode.Encode("EC4M 8AD");  // 278794572
postcode.Encode("EC4M8AD");  // 278794572 (same as above)
postcode.Decode(278794572);  // "EC4M 8AD" (canonical form)
postcode.GetCanonicalForm("EC4M8AD");  // "EC4M 8AD" (canonical form)
postcode.Accepts("invalid");  // false
postcode.Encode("invalid");  // 0

API

Notes:

  • API function text arguments are string, ReadOnlySpan<char> or ReadOnlySpan<byte> (ASCII).
  • API function destination arguments are Span<char> or Span<byte>, with the number of characters written in the associated written output parameter.
  • More detail (including the source generator) is available in the source repo README.
OperationCallReturns
ParseNaxp.Parse(pattern)A Naxp, or throws FormatException.
Try to parseNaxp.TryParse(pattern, out naxp, out errorMessage)true where the pattern parsed. A longer overload adds out errorOffset, out errorLength and out errorCode.
Standard naxpNaxp.StandardNaxp(id, variant), Naxp.TryStandardNaxp(id, out naxp)One of the standard naxps, such as Naxp.StandardNaxp("uk-postcode"). Throws ArgumentException, or returns false, where there is none. The id and variant match without regard to case, and variant is optional; an overload of TryStandardNaxp takes it.
All standard naxpsNaxp.AllStandardNaxpsA StandardNaxpVariant for each variant, with its Id, Name, Variant, Pattern and Notes.
PatternPatternThe pattern the naxp was parsed from.
Largest encoded valueMaxEncodedValueA ulong, which is also how many encoded values there are.
Longest textMaxLengthThe length of the longest text a value decodes to, so a buffer of this length always suffices.
AcceptsAccepts(text)Whether the text is valid for the naxp.
EncodeEncode(text), TryEncode(text, out encoded)The encoded value, from 1 to MaxEncodedValue, or 0 (or false) where the text is invalid.
DecodeDecode(value)The canonical text, or throws ArgumentOutOfRangeException where the value is out of range.
Try to decodeTryDecode(value, out text), TryDecode(value, destination, out written)true with the canonical text set or written, or false where the value is out of range or the destination is too short.
Decode to bytesDecodeToBytes(value)The canonical text as a byte[] of ASCII.
Canonical formGetCanonicalForm(text), TryGetCanonicalForm(text, out canonicalForm), TryGetCanonicalForm(text, destination, out written)The canonical form of the text, or null (or false) where the text is invalid.
Generate codeEmit(language, prefix, valueType)Source code for this naxp in another language. See code-gen.
CompareNaxp.Compare(a, b), Naxp.TryCompare(a, b, out comparison), each with an optional budgetHow two naxps relate. Rarely needed outside migrating.
First divergent valueNaxp.FirstDivergentValue(a, b)The lowest value both naxps hold but decode differently, or 0.

JavaScript

Plain JavaScript modules, with TypeScript declarations, for Node 18 or later and for the browser.

This is the library that runs the interactive pages on this website.

Install

npm install @naxp/naxp

The package on npm has zero dependencies and no build step.

Example

import { Naxp } from '@naxp/naxp';

const postcode = Naxp.parse('\\A\\A?\\9\\X? \\s!! \\9\\A\\A | GIR \\s!! 0AA');

postcode.maxEncodedValue;  // 1755842401n
postcode.maxLength;  // 8
postcode.encode('EC4M 8AD');  // 278794572n
postcode.encode('EC4M8AD');  // 278794572n (same as above)
postcode.decode(278794572n);  // 'EC4M 8AD' (canonical form)
postcode.getCanonicalForm('EC4M8AD');  // 'EC4M 8AD' (canonical form)
postcode.accepts('invalid');  // false
postcode.encode('invalid');  // 0n

API

Notes:

  • text arguments are a string or a Uint8Array of ASCII.
  • value arguments can be a bigint or a safe integer.
  • More detail is available in the source repo README.
OperationCallReturns
ParseNaxp.parse(pattern)A Naxp, or throws NaxpFormatError.
Try to parseNaxp.tryParse(pattern){ naxp, errorMessage, errorOffset, errorLength, errorCode }, with naxp null where the pattern is invalid.
Standard naxpNaxp.standardNaxp(id, variant?), Naxp.tryStandardNaxp(id, variant?)One of the standard naxps, such as Naxp.standardNaxp('uk-postcode'). Throws RangeError, or returns null, where there is none. The id and variant match without regard to case.
All standard naxpsNaxp.allStandardNaxps()A frozen object for each variant, with its id, name, variant, pattern and notes.
PatternpatternThe pattern the naxp was parsed from.
Largest encoded valuemaxEncodedValueA bigint, which is also how many encoded values there are.
Longest textmaxLengthThe length of the longest text a value decodes to.
Acceptsaccepts(text)Whether the text is valid for the naxp.
Encodeencode(text) The encoded value, from 1n to maxEncodedValue, or 0n where the text is invalid. (The returned value is a bigint because a naxp may hold up to 264 − 1 values.)
Decodedecode(value)The canonical text, or throws RangeError where the value is out of range.
Try to decodetryDecode(value)The canonical text, or null where the value is out of range.
Decode to bytesdecodeToBytes(value)The canonical text as a Uint8Array of ASCII.
Canonical formgetCanonicalForm(text)The canonical form of the text, or null where the text is invalid.
Generate codeemit(language, prefix, valueType)Source code for this naxp in another language. See code-gen.
CompareNaxp.compare(a, b, budget?), Naxp.tryCompare(a, b, budget?)How two naxps relate. Rarely needed outside migrating.
First divergent valueNaxp.firstDivergentValue(a, b)The lowest value both naxps hold but decode differently, or 0n.

Parsing errors

When parsing a naxp pattern fails, the error is reported as (a) an error code, (b) an informative error message, and (c) the zero-based offset and (d) the length of the part of the pattern in error.

For example, parsing A{2-5} reports:

Error code"NAXP1002"
Error message"The counts of an interval are separated by ',', not '-'. Write 'A{2,5}'."
Offset3
Length1

All error codes are listed here.

Create a new implementation

If you want to write an implementation in a language not already covered then:

  1. Please open an issue on GitHub so that work is not duplicated.
  2. Take a look at the existing implementations above to understand how they work.
  3. Test your implementation against the conformance data, a JSON file per specification version that is generated from the specification itself rather than from any implementation, so an implementation cannot define its own truth by passing it. It comprises
    • well-formed naxps, each with its value count and a set of texts with their encoded values and canonical forms (every accepted string, for the smaller cases),
    • invalid text for those naxps, and
    • invalid naxps, each tagged with the rule it breaks.

    Counts and encoded values are written as decimal strings, because they legitimately reach 264 − 1 and most JSON parsers read a number into a double.