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
_cstraccept 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.
| Operation | Call | Returns |
|---|---|---|
| Parse | naxp_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 error | naxp_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 naxp | naxp_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. |
| Free | naxp_free(postcode) | Nothing. NULL is allowed. |
| Pattern | naxp_pattern(postcode) | The pattern the naxp was parsed from, NUL-terminated. |
| Largest encoded value | naxp_max_encoded_value(postcode) | A uint64_t, which is also how many encoded values there are. |
| Longest text | naxp_max_length(postcode) | The length of the longest text a value decodes to, so a buffer of this length plus one always suffices. |
| Accepts | naxp_accepts(postcode, text, text_length), naxp_accepts_cstr(postcode, text) | Whether the text is valid for the naxp. |
| Encode | naxp_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. |
| Decode | naxp_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 form | naxp_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 code | naxp_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. |
| Compare | naxp_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 value | naxp_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:
naxpinstances 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.
| Operation | Call | Returns |
|---|---|---|
| Parse | naxp::parse(pattern) | A naxp, or throws naxp_format_error, whose error() holds the error. |
| Try to parse | naxp::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 naxp | naxp::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 naxps | naxp::all_standard_naxps() | A standard_naxp_variant for each variant, with its id, name, variant, pattern and notes. |
| Pattern | pattern() | The pattern the naxp was parsed from. |
| Largest encoded value | max_encoded_value() | A std::uint64_t, which is also how many encoded values there are. |
| Longest text | max_length() | The length of the longest text a value decodes to, so a buffer of this length always suffices. |
| Accepts | accepts(text) | Whether the text is valid for the naxp. |
| Encode | encode(text) | The encoded value, from 1 to max_encoded_value(), or 0 where the text is invalid. |
| Decode | decode(value) | The canonical text, or throws std::out_of_range where the value is out of range. |
| Try to decode | try_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 form | canonical_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 code | emit(language, prefix, value_type) | Source code for this naxp in another language. See code-gen. |
| Compare | naxp::compare(a, b), naxp::try_compare(a, b, comparison), each with an optional budget | How two naxps relate. Rarely needed outside migrating. |
| First divergent value | naxp::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
textarguments arestring,ReadOnlySpan<char>orReadOnlySpan<byte>(ASCII). -
API function
destinationarguments areSpan<char>orSpan<byte>, with the number of characters written in the associatedwrittenoutput parameter. - More detail (including the source generator) is available in the source repo README.
| Operation | Call | Returns |
|---|---|---|
| Parse | Naxp.Parse(pattern) | A Naxp, or throws FormatException. |
| Try to parse | Naxp.TryParse(pattern, out naxp, out errorMessage) | true where the pattern parsed. A longer overload adds out errorOffset, out errorLength and out errorCode. |
| Standard naxp | Naxp.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 naxps | Naxp.AllStandardNaxps | A StandardNaxpVariant for each variant, with its Id, Name, Variant, Pattern and Notes. |
| Pattern | Pattern | The pattern the naxp was parsed from. |
| Largest encoded value | MaxEncodedValue | A ulong, which is also how many encoded values there are. |
| Longest text | MaxLength | The length of the longest text a value decodes to, so a buffer of this length always suffices. |
| Accepts | Accepts(text) | Whether the text is valid for the naxp. |
| Encode | Encode(text), TryEncode(text, out encoded) | The encoded value, from 1 to MaxEncodedValue, or 0 (or false) where the text is invalid. |
| Decode | Decode(value) | The canonical text, or throws ArgumentOutOfRangeException where the value is out of range. |
| Try to decode | TryDecode(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 bytes | DecodeToBytes(value) | The canonical text as a byte[] of ASCII. |
| Canonical form | GetCanonicalForm(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 code | Emit(language, prefix, valueType) | Source code for this naxp in another language. See code-gen. |
| Compare | Naxp.Compare(a, b), Naxp.TryCompare(a, b, out comparison), each with an optional budget | How two naxps relate. Rarely needed outside migrating. |
| First divergent value | Naxp.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:
-
textarguments are a string or aUint8Arrayof ASCII. -
valuearguments can be abigintor a safe integer. - More detail is available in the source repo README.
| Operation | Call | Returns |
|---|---|---|
| Parse | Naxp.parse(pattern) | A Naxp, or throws NaxpFormatError. |
| Try to parse | Naxp.tryParse(pattern) | { naxp, errorMessage, errorOffset, errorLength, errorCode }, with naxp null where the pattern is invalid. |
| Standard naxp | Naxp.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 naxps | Naxp.allStandardNaxps() | A frozen object for each variant, with its id, name, variant, pattern and notes. |
| Pattern | pattern | The pattern the naxp was parsed from. |
| Largest encoded value | maxEncodedValue | A bigint, which is also how many encoded values there are. |
| Longest text | maxLength | The length of the longest text a value decodes to. |
| Accepts | accepts(text) | Whether the text is valid for the naxp. |
| Encode | encode(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.)
|
| Decode | decode(value) | The canonical text, or throws RangeError where the value is out of range. |
| Try to decode | tryDecode(value) | The canonical text, or null where the value is out of range. |
| Decode to bytes | decodeToBytes(value) | The canonical text as a Uint8Array of ASCII. |
| Canonical form | getCanonicalForm(text) | The canonical form of the text, or null where the text is invalid. |
| Generate code | emit(language, prefix, valueType) | Source code for this naxp in another language. See code-gen. |
| Compare | Naxp.compare(a, b, budget?), Naxp.tryCompare(a, b, budget?) | How two naxps relate. Rarely needed outside migrating. |
| First divergent value | Naxp.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}'." |
| Offset | 3 |
| Length | 1 |
All error codes are listed here.
Create a new implementation
If you want to write an implementation in a language not already covered then:
- Please open an issue on GitHub so that work is not duplicated.
- Take a look at the existing implementations above to understand how they work.
-
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.