From a79584eef3d5deace72539073116b4f3c67fe380 Mon Sep 17 00:00:00 2001 From: Kah Goh Date: Fri, 20 Oct 2023 23:23:53 +0800 Subject: [PATCH] Add rotation-cipher exercise --- config.json | 7 ++ .../rotational-cipher/.docs/instructions.md | 29 +++++ .../practice/rotational-cipher/.eslintrc | 18 ++++ .../rotational-cipher/.meta/config.json | 25 +++++ .../rotational-cipher/.meta/proof.ci.wat | 100 ++++++++++++++++++ .../rotational-cipher/.meta/tests.toml | 40 +++++++ exercises/practice/rotational-cipher/.npmrc | 1 + exercises/practice/rotational-cipher/LICENSE | 21 ++++ .../rotational-cipher/babel.config.js | 4 + .../practice/rotational-cipher/package.json | 35 ++++++ .../rotational-cipher.spec.js | 96 +++++++++++++++++ .../rotational-cipher/rotational-cipher.wat | 7 ++ 12 files changed, 383 insertions(+) create mode 100644 exercises/practice/rotational-cipher/.docs/instructions.md create mode 100644 exercises/practice/rotational-cipher/.eslintrc create mode 100644 exercises/practice/rotational-cipher/.meta/config.json create mode 100644 exercises/practice/rotational-cipher/.meta/proof.ci.wat create mode 100644 exercises/practice/rotational-cipher/.meta/tests.toml create mode 100644 exercises/practice/rotational-cipher/.npmrc create mode 100644 exercises/practice/rotational-cipher/LICENSE create mode 100644 exercises/practice/rotational-cipher/babel.config.js create mode 100644 exercises/practice/rotational-cipher/package.json create mode 100644 exercises/practice/rotational-cipher/rotational-cipher.spec.js create mode 100644 exercises/practice/rotational-cipher/rotational-cipher.wat diff --git a/config.json b/config.json index 3cdb892..01a162d 100644 --- a/config.json +++ b/config.json @@ -188,6 +188,13 @@ "practices": [], "prerequisites": [], "difficulty": 2 + }, { + "slug": "rotational-cipher", + "name": "Rotational Cipher", + "uuid" : "039a7a86-d954-4bb6-a144-93b2eb17b016", + "practices" : [], + "prerequisites": [], + "difficulty": 4 } ] }, diff --git a/exercises/practice/rotational-cipher/.docs/instructions.md b/exercises/practice/rotational-cipher/.docs/instructions.md new file mode 100644 index 0000000..4bf64ca --- /dev/null +++ b/exercises/practice/rotational-cipher/.docs/instructions.md @@ -0,0 +1,29 @@ +# Instructions + +Create an implementation of the rotational cipher, also sometimes called the Caesar cipher. + +The Caesar cipher is a simple shift cipher that relies on transposing all the letters in the alphabet using an integer key between `0` and `26`. +Using a key of `0` or `26` will always yield the same output due to modular arithmetic. +The letter is shifted for as many values as the value of the key. + +The general notation for rotational ciphers is `ROT + `. +The most commonly used rotational cipher is `ROT13`. + +A `ROT13` on the Latin alphabet would be as follows: + +```text +Plain: abcdefghijklmnopqrstuvwxyz +Cipher: nopqrstuvwxyzabcdefghijklm +``` + +It is stronger than the Atbash cipher because it has 27 possible keys, and 25 usable keys. + +Ciphertext is written out in the same formatting as the input including spaces and punctuation. + +## Examples + +- ROT5 `omg` gives `trl` +- ROT0 `c` gives `c` +- ROT26 `Cool` gives `Cool` +- ROT13 `The quick brown fox jumps over the lazy dog.` gives `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` +- ROT13 `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` gives `The quick brown fox jumps over the lazy dog.` diff --git a/exercises/practice/rotational-cipher/.eslintrc b/exercises/practice/rotational-cipher/.eslintrc new file mode 100644 index 0000000..1dbeac2 --- /dev/null +++ b/exercises/practice/rotational-cipher/.eslintrc @@ -0,0 +1,18 @@ +{ + "root": true, + "extends": "@exercism/eslint-config-javascript", + "env": { + "jest": true + }, + "overrides": [ + { + "files": [ + "*.spec.js" + ], + "excludedFiles": [ + "custom.spec.js" + ], + "extends": "@exercism/eslint-config-javascript/maintainers" + } + ] +} diff --git a/exercises/practice/rotational-cipher/.meta/config.json b/exercises/practice/rotational-cipher/.meta/config.json new file mode 100644 index 0000000..d646fe6 --- /dev/null +++ b/exercises/practice/rotational-cipher/.meta/config.json @@ -0,0 +1,25 @@ +{ + "authors": [ + "kahgoh" + ], + "files": { + "solution": [ + "rotational-cipher.wat" + ], + "test": [ + "rotational-cipher.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ] + }, + "blurb": "Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Caesar_cipher", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/practice/rotational-cipher/.meta/proof.ci.wat b/exercises/practice/rotational-cipher/.meta/proof.ci.wat new file mode 100644 index 0000000..0817d2a --- /dev/null +++ b/exercises/practice/rotational-cipher/.meta/proof.ci.wat @@ -0,0 +1,100 @@ +(module + (memory (export "mem") 1) + + (func $wrap (param $low i32) (param $high i32) (param $value i32) (param $shiftKey i32) (result i32) + (local $newValue i32) + (local.set $newValue (local.get $value)) + + (if + (i32.ge_u + (local.get $value) + (local.get $low) + ) + (then + (if + (i32.le_u + (local.get $value) + (local.get $high) + ) + (then + ;; shift the value + (local.set + $newValue + (i32.add + (local.get $value) + (local.get $shiftKey) + ) + ) + + ;; wrap when value greater than $high + (if (i32.gt_u (local.get $newValue) (local.get $high)) + (then + (local.set $newValue + (i32.sub + (local.get $newValue) + (i32.const 26) + ) + ) + ) + ) + ) + ) + ) + ) + + (return (local.get $newValue)) + ) + + (func (export "rotate") (param $textOffset i32) (param $textLength i32) (param $shiftKey i32) (result i32 i32) + + (local $index i32) + (local $stop i32) + (local $value i32) + + (local.set $index (local.get $textOffset)) + (local.set $stop + (i32.add + (local.get $textOffset) + (local.get $textLength) + ) + ) + + (loop $process + ;; shift upper case + (local.set $value + (call $wrap + (i32.const 65) + (i32.const 90) + (i32.load8_u (local.get $index)) + (local.get $shiftKey) + ) + ) + + ;; shift lower case + (local.set $value + (call $wrap + (i32.const 97) + (i32.const 122) + (local.get $value) + (local.get $shiftKey) + ) + ) + + (i32.store8 + (local.get $index) + (local.get $value) + ) + + (local.set $index + (i32.add (local.get $index) (i32.const 1)) + ) + + (br_if + $process + (i32.lt_u (local.get $index) (local.get $stop)) + ) + ) + + (return (local.get $textOffset) (local.get $textLength)) + ) +) diff --git a/exercises/practice/rotational-cipher/.meta/tests.toml b/exercises/practice/rotational-cipher/.meta/tests.toml new file mode 100644 index 0000000..53441ed --- /dev/null +++ b/exercises/practice/rotational-cipher/.meta/tests.toml @@ -0,0 +1,40 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[74e58a38-e484-43f1-9466-877a7515e10f] +description = "rotate a by 0, same output as input" + +[7ee352c6-e6b0-4930-b903-d09943ecb8f5] +description = "rotate a by 1" + +[edf0a733-4231-4594-a5ee-46a4009ad764] +description = "rotate a by 26, same output as input" + +[e3e82cb9-2a5b-403f-9931-e43213879300] +description = "rotate m by 13" + +[19f9eb78-e2ad-4da4-8fe3-9291d47c1709] +description = "rotate n by 13 with wrap around alphabet" + +[a116aef4-225b-4da9-884f-e8023ca6408a] +description = "rotate capital letters" + +[71b541bb-819c-4dc6-a9c3-132ef9bb737b] +description = "rotate spaces" + +[ef32601d-e9ef-4b29-b2b5-8971392282e6] +description = "rotate numbers" + +[32dd74f6-db2b-41a6-b02c-82eb4f93e549] +description = "rotate punctuation" + +[9fb93fe6-42b0-46e6-9ec1-0bf0a062d8c9] +description = "rotate all letters" diff --git a/exercises/practice/rotational-cipher/.npmrc b/exercises/practice/rotational-cipher/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/rotational-cipher/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/rotational-cipher/LICENSE b/exercises/practice/rotational-cipher/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/rotational-cipher/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/rotational-cipher/babel.config.js b/exercises/practice/rotational-cipher/babel.config.js new file mode 100644 index 0000000..c406a4d --- /dev/null +++ b/exercises/practice/rotational-cipher/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; \ No newline at end of file diff --git a/exercises/practice/rotational-cipher/package.json b/exercises/practice/rotational-cipher/package.json new file mode 100644 index 0000000..cc73802 --- /dev/null +++ b/exercises/practice/rotational-cipher/package.json @@ -0,0 +1,35 @@ +{ + "name": "@exercism/wasm-rotational-cipher", + "description": "Exercism exercises in WebAssembly.", + "author": "Kah Goh", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/rotational-cipher" + }, + "jest": { + "maxWorkers": 1 + }, + "devDependencies": { + "@babel/core": "^7.20.12", + "@exercism/babel-preset-javascript": "^0.2.1", + "@exercism/eslint-config-javascript": "^0.6.0", + "@types/jest": "^29.4.0", + "@types/node": "^18.13.0", + "babel-jest": "^29.4.2", + "core-js": "^3.27.2", + "eslint": "^8.34.0", + "jest": "^29.4.2" + }, + "dependencies": { + "@exercism/wasm-lib": "^0.1.0" + }, + "scripts": { + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js ./*", + "watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch ./*", + "lint": "eslint ." + } +} diff --git a/exercises/practice/rotational-cipher/rotational-cipher.spec.js b/exercises/practice/rotational-cipher/rotational-cipher.spec.js new file mode 100644 index 0000000..2577326 --- /dev/null +++ b/exercises/practice/rotational-cipher/rotational-cipher.spec.js @@ -0,0 +1,96 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; +import exp from "constants"; + +let wasmModule; +let currentInstance; + +beforeAll(async () => { + try { + const watPath = new URL("./rotational-cipher.wat", import.meta.url); + const { buffer } = await compileWat(watPath); + wasmModule = await WebAssembly.compile(buffer); + } catch (err) { + console.log(`Error compiling *.wat: ${err}`); + process.exit(1); + } +}); + +function rotate(text, shiftKey) { + const inputBufferOffset = 64; + const inputBufferCapacity = 256; + + const inputLengthEncoded = new TextEncoder().encode(text).length; + if (inputLengthEncoded > inputBufferCapacity) { + throw new Error( + `String is too large for buffer of size ${inputBufferCapacity} bytes` + ); + } + + currentInstance.set_mem_as_utf8(inputBufferOffset, inputLengthEncoded, text) + + const [outputOffset, outputLength] = currentInstance.exports.rotate( + inputBufferOffset, + text.length, + shiftKey + ); + expect(outputLength).toEqual(text.length); + + return currentInstance.get_mem_as_utf8(outputOffset, outputLength); +} + +describe("Rotational Cipher", () => { + beforeEach(async () => { + currentInstance = null; + + if (!wasmModule) { + return Promise.reject(); + } + try { + currentInstance = await new WasmRunner(wasmModule); + return Promise.resolve(); + } catch (err) { + console.log(`Error instantiating WebAssembly module: ${err}`); + return Promise.reject(); + } + }); + + test("rotate a by 0, same output as input", () => { + expect(rotate("a", 0)).toEqual("a"); + }); + + test("rotate a by 1", () => { + expect(rotate("a", 1)).toEqual("b"); + }); + + test("rotate a by 26, same output as input", () => { + expect(rotate("a", 26)).toEqual("a"); + }); + + test("rotate m by 13", () => { + expect(rotate("m", 13)).toEqual("z"); + }); + + test("rotate n by 13 with wrap around alphabet", () => { + expect(rotate("n", 13)).toEqual("a"); + }); + + test("rotate capital letters", () => { + expect(rotate("OMG", 5)).toEqual("TRL"); + }); + + test("rotate spaces", () => { + expect(rotate("O M G", 5)).toEqual("T R L"); + }); + + test("rotate numbers", () => { + expect(rotate("Testing 1 2 3 testing", 4)).toEqual("Xiwxmrk 1 2 3 xiwxmrk"); + }); + + test("rotate punctuation", () => { + expect(rotate("Let's eat, Grandma!", 21)).toEqual("Gzo'n zvo, Bmviyhv!"); + }); + + test("rotate all letters", () => { + expect(rotate("The quick brown fox jumps over the lazy dog.", 13)).toEqual("Gur dhvpx oebja sbk whzcf bire gur ynml qbt."); + }); +}); \ No newline at end of file diff --git a/exercises/practice/rotational-cipher/rotational-cipher.wat b/exercises/practice/rotational-cipher/rotational-cipher.wat new file mode 100644 index 0000000..937e8ba --- /dev/null +++ b/exercises/practice/rotational-cipher/rotational-cipher.wat @@ -0,0 +1,7 @@ +(module + (memory (export "mem") 1) + + (func (export "rotate") (param $textOffset i32) (param $textLength i32) (param $shiftKey i32) (result i32 i32) + (return (local.get $textOffset) (local.get $textLength)) + ) +)