From 3888e7fe644d8673f279a980b27a3e93b654df33 Mon Sep 17 00:00:00 2001 From: pilcrowOnPaper Date: Fri, 7 Mar 2025 19:13:29 +0900 Subject: [PATCH 1/2] add ascii api --- src/ascii.test.ts | 75 +++++++++++++++++++++++++++++++++++++++++++++++ src/ascii.ts | 35 ++++++++++++++++++++++ src/index.ts | 1 + 3 files changed, 111 insertions(+) create mode 100644 src/ascii.test.ts create mode 100644 src/ascii.ts diff --git a/src/ascii.test.ts b/src/ascii.test.ts new file mode 100644 index 0000000..21c428a --- /dev/null +++ b/src/ascii.test.ts @@ -0,0 +1,75 @@ +import * as vitest from "vitest"; + +import { decodeASCII, encodeASCII, isValidASCIIEncoding } from "./ascii.js"; + +vitest.describe("encodeASCII()", () => { + vitest.test("valid code points", () => { + for (let i = 0; i <= 0x7f; i++) { + const s = String.fromCodePoint(i); + const result = encodeASCII(s); + const expected = new TextEncoder().encode(s); + vitest.expect(result, `test code point ${i}`).toEqual(expected); + } + }); + + vitest.test("multiple valid code points", () => { + for (let i = 0; i <= 0x7f; i++) { + for (let j = 0; j <= 0x7f; j++) { + const s = String.fromCodePoint(i, j); + const result = encodeASCII(s); + const expected = new TextEncoder().encode(s); + vitest.expect(result, `test code points ${i}, ${j}`).toEqual(expected); + } + } + }); + + vitest.test(" invalid ascii", () => { + const s = String.fromCodePoint(0x8f); + vitest.expect(() => encodeASCII(s)).toThrow(TypeError); + }); +}); + +vitest.describe("decodeASCII()", () => { + vitest.test("valid code point", () => { + for (let i = 0; i <= 0x7f; i++) { + const s = String.fromCodePoint(i); + const encoded = encodeASCII(s); + const result = decodeASCII(encoded); + vitest.expect(result, `test code point ${i}`).toBe(s); + } + }); + + vitest.test("multiple valid code points", () => { + for (let i = 0; i <= 0x7f; i++) { + for (let j = 0; j <= 0x7f; j++) { + const s = String.fromCodePoint(i, j); + const encoded = encodeASCII(s); + const result = decodeASCII(encoded); + vitest.expect(result, `test code points ${i}, ${j}`).toBe(s); + } + } + }); + + vitest.test(" invalid code point", () => { + const bytes = new Uint8Array([0x80]); + vitest.expect(() => decodeASCII(bytes)).toThrow(TypeError); + }); +}); + +vitest.describe("isValidASCIIEncoding()", () => { + vitest.test("valid", () => { + for (let i = 0; i <= 0x7f; i++) { + for (let j = 0; j <= 0x7f; j++) { + const encoded = new Uint8Array([i, j]); + const result = isValidASCIIEncoding(encoded); + vitest.expect(result, `test code points ${i}, ${j}`).toBe(true); + } + } + }); + + vitest.test("invalid code point", () => { + const bytes = new Uint8Array([0x80]); + const result = isValidASCIIEncoding(bytes); + vitest.expect(result).toBe(false); + }); +}); diff --git a/src/ascii.ts b/src/ascii.ts new file mode 100644 index 0000000..9821ec5 --- /dev/null +++ b/src/ascii.ts @@ -0,0 +1,35 @@ +export function encodeASCII(s: string): Uint8Array { + const bytes = new Uint8Array(s.length); + for (let i = 0; i < s.length; i++) { + const charCode = s.charCodeAt(i); + if (Number.isNaN(charCode)) { + throw new TypeError("Invalid character"); + } + if (charCode > 0x7f) { + throw new TypeError("Invalid character"); + } + bytes[i] = charCode; + } + return bytes; +} + +export function decodeASCII(bytes: Uint8Array): string { + let s = ""; + for (let i = 0; i < bytes.length; i++) { + if (bytes[i] > 0x7f) { + throw new TypeError("Invalid encoding"); + } + s += String.fromCharCode(bytes[i]); + } + return s; +} + +/** Reports whether the byte sequence is a valid ASCII encoding. */ +export function isValidASCIIEncoding(bytes: Uint8Array): boolean { + for (let i = 0; i < bytes.length; i++) { + if (bytes[i] > 0x7f) { + return false; + } + } + return true; +} diff --git a/src/index.ts b/src/index.ts index 8789137..2701fea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,3 +19,4 @@ export { decodeBase64url, decodeBase64urlIgnorePadding } from "./base64.js"; +export { encodeASCII, decodeASCII, isValidASCIIEncoding } from "./ascii.js"; From 88d00d2bdffa8bcea8b9d8cec221396561b0021d Mon Sep 17 00:00:00 2001 From: pilcrowOnPaper Date: Fri, 7 Mar 2025 19:57:25 +0900 Subject: [PATCH 2/2] add docs --- docs/malta.config.json | 3 ++- docs/pages/examples/ascii.md | 17 +++++++++++++++++ docs/pages/reference/main/decodeASCII.md | 11 +++++++++++ docs/pages/reference/main/encodeASCII.md | 11 +++++++++++ docs/pages/reference/main/index.md | 3 +++ .../reference/main/isValidASCIIEncoding.md | 11 +++++++++++ src/ascii.ts | 2 ++ 7 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 docs/pages/examples/ascii.md create mode 100644 docs/pages/reference/main/decodeASCII.md create mode 100644 docs/pages/reference/main/encodeASCII.md create mode 100644 docs/pages/reference/main/isValidASCIIEncoding.md diff --git a/docs/malta.config.json b/docs/malta.config.json index 0698cf9..0d8543b 100644 --- a/docs/malta.config.json +++ b/docs/malta.config.json @@ -10,7 +10,8 @@ "pages": [ ["Hex encoding", "/examples/hex"], ["Base64 encoding", "/examples/base64"], - ["Base32 encoding", "/examples/base32"] + ["Base32 encoding", "/examples/base32"], + ["ASCII encoding", "/examples/ascii"] ] }, { diff --git a/docs/pages/examples/ascii.md b/docs/pages/examples/ascii.md new file mode 100644 index 0000000..bef2fb1 --- /dev/null +++ b/docs/pages/examples/ascii.md @@ -0,0 +1,17 @@ +--- +title: "ASCII encoding" +--- + +# ASCII + +Use `encodeASCII()` to ASCII encode strings to byte sequences. Use `decodeASCII()` to decode into a string. + +Use `isValidASCIIEncoding()` to validate an ASCII byte sequence without decoding it. + +```ts +import { encodeASCII, decodeASCII, isValidASCIIEncoding } from "@oslojs/encoding"; + +const encoded = encodeASCII("Hello world!"); +const decoded = decodeASCII(encoded); +const valid = isValidASCIIEncoding(encoded); +``` diff --git a/docs/pages/reference/main/decodeASCII.md b/docs/pages/reference/main/decodeASCII.md new file mode 100644 index 0000000..335215b --- /dev/null +++ b/docs/pages/reference/main/decodeASCII.md @@ -0,0 +1,11 @@ +--- +title: "decodeASCII()" +--- + +# decodeASCII() + +ASCII decodes a byte sequence into a string. Throws a `TypeError` if the encoding is invalid. + +```ts +function decodeASCII(bytes: Uint8Array): string; +``` diff --git a/docs/pages/reference/main/encodeASCII.md b/docs/pages/reference/main/encodeASCII.md new file mode 100644 index 0000000..7eda462 --- /dev/null +++ b/docs/pages/reference/main/encodeASCII.md @@ -0,0 +1,11 @@ +--- +title: "encodeASCII()" +--- + +# encodeASCII() + +ASCII encodes a string into a byte sequence. Throws a `TypeError` on invalid characters. + +```ts +function encodeASCII(s: string): Uint8Array; +``` diff --git a/docs/pages/reference/main/index.md b/docs/pages/reference/main/index.md index 5c2d052..376fb4f 100644 --- a/docs/pages/reference/main/index.md +++ b/docs/pages/reference/main/index.md @@ -6,6 +6,7 @@ title: "@oslojs/encoding" # Functions +- [`decodeASCII()`](/reference/main/decodeASCII) - [`decodeBase32()`](/reference/main/decodeBase32) - [`decodeBase32IgnorePadding()`](/reference/main/decodeBase32IgnorePadding) - [`decodeBase64()`](/reference/main/decodeBase64) @@ -13,6 +14,7 @@ title: "@oslojs/encoding" - [`decodeBase64url()`](/reference/main/decodeBase64url) - [`decodeBase64urlIgnorePadding()`](/reference/main/decodeBase64urlIgnorePadding) - [`decodeHex()`](/reference/main/decodeHex) +- [`encodeASCII()`](/reference/main/encodeASCII) - [`encodeBase32LowerCase()`](/reference/main/encodeBase32LowerCase) - [`encodeBase32LowerCaseNoPadding()`](/reference/main/encodeBase32LowerCaseNoPadding) - [`encodeBase32UpperCase()`](/reference/main/encodeBase32UpperCase) @@ -23,5 +25,6 @@ title: "@oslojs/encoding" - [`encodeBase64urlNoPadding()`](/reference/main/encodeBase64urlNoPadding) - [`encodeHexLowerCase()`](/reference/main/encodeHexLowerCase) - [`encodeHexUpperCase()`](/reference/main/encodeHexUpperCase) +- [`isValidASCIIEncoding()`](/reference/main/isValidASCIIEncoding) - _Replaced_ [`encodeBase32()`](/reference/main/encodeBase32) - _Replaced_ [`encodeBase32NoPadding()`](/reference/main/encodeBase32NoPadding) diff --git a/docs/pages/reference/main/isValidASCIIEncoding.md b/docs/pages/reference/main/isValidASCIIEncoding.md new file mode 100644 index 0000000..8e4215f --- /dev/null +++ b/docs/pages/reference/main/isValidASCIIEncoding.md @@ -0,0 +1,11 @@ +--- +title: "isValidASCIIEncoding()" +--- + +# isValidASCIIEncoding() + +Reports whether the byte sequence is a valid ASCII encoding. + +```ts +function isValidASCIIEncoding(bytes: Uint8Array): boolean; +``` diff --git a/src/ascii.ts b/src/ascii.ts index 9821ec5..cdeb295 100644 --- a/src/ascii.ts +++ b/src/ascii.ts @@ -1,3 +1,4 @@ +/** ASCII encodes a string into a byte sequence. Throws a `TypeError` on invalid characters. */ export function encodeASCII(s: string): Uint8Array { const bytes = new Uint8Array(s.length); for (let i = 0; i < s.length; i++) { @@ -13,6 +14,7 @@ export function encodeASCII(s: string): Uint8Array { return bytes; } +/** ASCII decodes a byte sequence into a string. Throws a `TypeError` if the encoding is invalid. */ export function decodeASCII(bytes: Uint8Array): string { let s = ""; for (let i = 0; i < bytes.length; i++) {