diff --git a/config.json b/config.json index df27f02..c3ed6f3 100644 --- a/config.json +++ b/config.json @@ -447,6 +447,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "proverb", + "name": "Proverb", + "uuid": "e386a22c-b48a-4074-a997-09659976fc21", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "pythagorean-triplet", "name": "Pythagorean Triplet", diff --git a/exercises/practice/nth-prime/.meta/config.json b/exercises/practice/nth-prime/.meta/config.json index fc2a84d..771f68b 100644 --- a/exercises/practice/nth-prime/.meta/config.json +++ b/exercises/practice/nth-prime/.meta/config.json @@ -11,7 +11,7 @@ ], "example": [ ".meta/proof.ci.wat" - ], + ], "invalidator": [ "package.json" ] diff --git a/exercises/practice/proverb/.docs/instructions.append.md b/exercises/practice/proverb/.docs/instructions.append.md new file mode 100644 index 0000000..0eab267 --- /dev/null +++ b/exercises/practice/proverb/.docs/instructions.append.md @@ -0,0 +1,11 @@ +# Instruction append + +Each list of inputs is represented as a string, with a newline character at the end of each input. + +An example would be `"nail\nshoe\nhorse\nrider\nmessage\nbattle\nkingdom\n"` + +Each output line should end with a newline `\n`. + +## Reserved Memory + +Bytes 128-383 of the linear memory are reserved for the input string. diff --git a/exercises/practice/proverb/.docs/instructions.md b/exercises/practice/proverb/.docs/instructions.md new file mode 100644 index 0000000..f6fb859 --- /dev/null +++ b/exercises/practice/proverb/.docs/instructions.md @@ -0,0 +1,19 @@ +# Instructions + +For want of a horseshoe nail, a kingdom was lost, or so the saying goes. + +Given a list of inputs, generate the relevant proverb. +For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: + +```text +For want of a nail the shoe was lost. +For want of a shoe the horse was lost. +For want of a horse the rider was lost. +For want of a rider the message was lost. +For want of a message the battle was lost. +For want of a battle the kingdom was lost. +And all for the want of a nail. +``` + +Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. +No line of the output text should be a static, unchanging string; all should vary according to the input given. diff --git a/exercises/practice/proverb/.eslintrc b/exercises/practice/proverb/.eslintrc new file mode 100644 index 0000000..1dbeac2 --- /dev/null +++ b/exercises/practice/proverb/.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/proverb/.meta/config.json b/exercises/practice/proverb/.meta/config.json new file mode 100644 index 0000000..78020da --- /dev/null +++ b/exercises/practice/proverb/.meta/config.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "proverb.wat" + ], + "test": [ + "proverb.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ], + "invalidator": [ + "package.json" + ] + }, + "blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail", + "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/proverb/.meta/proof.ci.wat b/exercises/practice/proverb/.meta/proof.ci.wat new file mode 100644 index 0000000..d6d13ad --- /dev/null +++ b/exercises/practice/proverb/.meta/proof.ci.wat @@ -0,0 +1,148 @@ +(module + (memory (export "mem") 1) + + (data (i32.const 0) "For want of a ") + + (data (i32.const 20) " the ") + + (data (i32.const 30) " was lost.\n") + + (data (i32.const 50) "And all for the want of a ") + + (data (i32.const 80) ".\n") + + (global $NEWLINE i32 (i32.const 10)) + + (global $FOR_WANT_OFFSET i32 (i32.const 0)) + + (global $FOR_WANT_LENGTH i32 (i32.const 14)) + + (global $THE_OFFSET i32 (i32.const 20)) + + (global $THE_LENGTH i32 (i32.const 5)) + + (global $WAS_LOST_OFFSET i32 (i32.const 30)) + + (global $WAS_LOST_LENGTH i32 (i32.const 11)) + + (global $AND_ALL_OFFSET i32 (i32.const 50)) + + (global $AND_ALL_LENGTH i32 (i32.const 26)) + + (global $STOP_OFFSET i32 (i32.const 80)) + + (global $STOP_LENGTH i32 (i32.const 2)) + + (global $OUTPUT i32 (i32.const 400)) + + (func $scan (param $offset i32) (result i32) + (local $ch i32) + (loop $LOOP + (local.set $ch (i32.load8_u (local.get $offset))) + (local.set $offset (i32.add (local.get $offset) + (i32.const 1))) + (br_if $LOOP (i32.ne (local.get $ch) (global.get $NEWLINE))) + ) + (return (local.get $offset)) + ) + + (func $append (param $destination i32) (param $source i32) (param $length i32) (result i32) + (memory.copy (local.get $destination) + (local.get $source) + (local.get $length)) + (return (i32.add (local.get $destination) + (local.get $length))) + ) + + ;; + ;; The full text of the proverbial rhyme. + ;; + ;; @param {i32} offset - The offset of the input string in linear memory + ;; @param {i32} length - The length of the input string in linear memory + ;; + ;; @returns {(i32,i32)} - Offset and length of result string + ;; in linear memory. + ;; + (func (export "recite") (param $offset i32) (param $length i32) (result i32 i32) + (local $dest i32) + (local $current i32) + (local $first i32) + (local $next i32) + (local $second i32) + (local $stop i32) + (local $len i32) + + (if (i32.eqz (local.get $length)) (then + (return (i32.const 0) (i32.const 0)) + )) + + (local.set $dest (global.get $OUTPUT)) + + (local.set $current (local.get $offset)) + (local.set $first (local.get $offset)) + + (local.set $next (call $scan (local.get $current))) + (local.set $second (local.get $next)) + + (local.set $stop (i32.add (local.get $offset) + (local.get $length))) + + (loop $LOOP + (if (i32.ne (local.get $next) (local.get $stop)) (then + ;; "For want of a " + (local.set $dest (call $append (local.get $dest) + (global.get $FOR_WANT_OFFSET) + (global.get $FOR_WANT_LENGTH))) + + (local.set $len (i32.sub (i32.sub (local.get $next) + (i32.const 1)) + (local.get $current))) + (local.set $dest (call $append (local.get $dest) + (local.get $current) + (local.get $len))) + + (local.set $current (local.get $next)) + (local.set $next (call $scan (local.get $current))) + + ;; " the " + (local.set $dest (call $append (local.get $dest) + (global.get $THE_OFFSET) + (global.get $THE_LENGTH))) + + (local.set $len (i32.sub (i32.sub (local.get $next) + (i32.const 1)) + (local.get $current))) + (local.set $dest (call $append (local.get $dest) + (local.get $current) + (local.get $len))) + + ;; " was lost.\n" + (local.set $dest (call $append (local.get $dest) + (global.get $WAS_LOST_OFFSET) + (global.get $WAS_LOST_LENGTH))) + + (br $LOOP) + )) + ) + + ;; "And all for the want of a " + (local.set $dest (call $append (local.get $dest) + (global.get $AND_ALL_OFFSET) + (global.get $AND_ALL_LENGTH))) + + (local.set $len (i32.sub (i32.sub (local.get $second) + (i32.const 1)) + (local.get $first))) + (local.set $dest (call $append (local.get $dest) + (local.get $first) + (local.get $len))) + + ;; ".\n" + (local.set $dest (call $append (local.get $dest) + (global.get $STOP_OFFSET) + (global.get $STOP_LENGTH))) + + (return (global.get $OUTPUT) (i32.sub (local.get $dest) + (global.get $OUTPUT))) + ) +) diff --git a/exercises/practice/proverb/.meta/tests.toml b/exercises/practice/proverb/.meta/tests.toml new file mode 100644 index 0000000..dc92a0c --- /dev/null +++ b/exercises/practice/proverb/.meta/tests.toml @@ -0,0 +1,28 @@ +# 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. + +[e974b73e-7851-484f-8d6d-92e07fe742fc] +description = "zero pieces" + +[2fcd5f5e-8b82-4e74-b51d-df28a5e0faa4] +description = "one piece" + +[d9d0a8a1-d933-46e2-aa94-eecf679f4b0e] +description = "two pieces" + +[c95ef757-5e94-4f0d-a6cb-d2083f5e5a83] +description = "three pieces" + +[433fb91c-35a2-4d41-aeab-4de1e82b2126] +description = "full proverb" + +[c1eefa5a-e8d9-41c7-91d4-99fab6d6b9f7] +description = "four pieces modernized" diff --git a/exercises/practice/proverb/.npmrc b/exercises/practice/proverb/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/proverb/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/proverb/LICENSE b/exercises/practice/proverb/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/proverb/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/proverb/babel.config.js b/exercises/practice/proverb/babel.config.js new file mode 100644 index 0000000..9c17ba5 --- /dev/null +++ b/exercises/practice/proverb/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; diff --git a/exercises/practice/proverb/package.json b/exercises/practice/proverb/package.json new file mode 100644 index 0000000..d5bbf75 --- /dev/null +++ b/exercises/practice/proverb/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/wasm-proverb", + "description": "Exercism exercises in WebAssembly.", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/proverb" + }, + "jest": { + "maxWorkers": 1 + }, + "devDependencies": { + "@babel/core": "^7.23.3", + "@exercism/babel-preset-javascript": "^0.4.0", + "@exercism/eslint-config-javascript": "^0.6.0", + "@types/jest": "^29.5.8", + "@types/node": "^20.9.1", + "babel-jest": "^29.7.0", + "core-js": "^3.33.2", + "eslint": "^8.54.0", + "jest": "^29.7.0" + }, + "dependencies": { + "@exercism/wasm-lib": "^0.2.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/proverb/proverb.spec.js b/exercises/practice/proverb/proverb.spec.js new file mode 100644 index 0000000..fc03dc4 --- /dev/null +++ b/exercises/practice/proverb/proverb.spec.js @@ -0,0 +1,141 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; + +let wasmModule; +let currentInstance; + +beforeAll(async () => { + try { + const watPath = new URL("./proverb.wat", import.meta.url); + const { buffer } = await compileWat(watPath); + wasmModule = await WebAssembly.compile(buffer); + } catch (err) { + console.log(`Error compiling *.wat: \n${err}`); + process.exit(1); + } +}); + +function recite(strings) { + const inputBufferOffset = 128; + const inputBufferCapacity = 256; + + const inputLengthEncoded = new TextEncoder().encode(strings).length; + if (inputLengthEncoded > inputBufferCapacity) { + throw new Error( + `String is too large for buffer of size ${inputBufferCapacity} bytes` + ); + } + + currentInstance.set_mem_as_utf8(inputBufferOffset, inputLengthEncoded, strings); + + const [outputOffset, outputLength] = currentInstance.exports.recite( + inputBufferOffset, + inputLengthEncoded + ); + + // Decode JS string from returned offset and length + return currentInstance.get_mem_as_utf8(outputOffset, outputLength); +} + +describe("Proverb", () => { + 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("zero pieces", () => { + const strings = [ + ].join(""); + const expected = [ + ].join(""); + const actual = recite(strings); + expect(actual).toEqual(expected); + }); + + xtest("one piece", () => { + const strings = [ + "nail\n" + ].join(""); + const expected = [ + "And all for the want of a nail.\n" + ].join(""); + const actual = recite(strings); + expect(actual).toEqual(expected); + }); + + xtest("two pieces", () => { + const strings = [ + "nail\n", + "shoe\n" + ].join(""); + const expected = [ + "For want of a nail the shoe was lost.\n", + "And all for the want of a nail.\n" + ].join(""); + const actual = recite(strings); + expect(actual).toEqual(expected); + }); + + xtest("three pieces", () => { + const strings = [ + "nail\n", + "shoe\n", + "horse\n" + ].join(""); + const expected = [ + "For want of a nail the shoe was lost.\n", + "For want of a shoe the horse was lost.\n", + "And all for the want of a nail.\n" + ].join(""); + const actual = recite(strings); + expect(actual).toEqual(expected); + }); + + xtest("full proverb", () => { + const strings = [ + "nail\n", + "shoe\n", + "horse\n", + "rider\n", + "message\n", + "battle\n", + "kingdom\n" + ].join(""); + const expected = [ + "For want of a nail the shoe was lost.\n", + "For want of a shoe the horse was lost.\n", + "For want of a horse the rider was lost.\n", + "For want of a rider the message was lost.\n", + "For want of a message the battle was lost.\n", + "For want of a battle the kingdom was lost.\n", + "And all for the want of a nail.\n" + ].join(""); + const actual = recite(strings); + expect(actual).toEqual(expected); + }); + + xtest("four pieces modernized", () => { + const strings = [ + "pin\n", + "gun\n", + "soldier\n", + "battle\n" + ].join(""); + const expected = [ + "For want of a pin the gun was lost.\n", + "For want of a gun the soldier was lost.\n", + "For want of a soldier the battle was lost.\n", + "And all for the want of a pin.\n" + ].join(""); + const actual = recite(strings); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/proverb/proverb.wat b/exercises/practice/proverb/proverb.wat new file mode 100644 index 0000000..a964134 --- /dev/null +++ b/exercises/practice/proverb/proverb.wat @@ -0,0 +1,16 @@ +(module + (memory (export "mem") 1) + + ;; + ;; The full text of the proverbial rhyme. + ;; + ;; @param {i32} offset - The offset of the input string in linear memory + ;; @param {i32} length - The length of the input string in linear memory + ;; + ;; @returns {(i32,i32)} - Offset and length of result string + ;; in linear memory. + ;; + (func (export "recite") (param $startVerse i32) (param $endVerse i32) (result i32 i32) + (i32.const 0) (i32.const 0) + ) +) diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json index 09b867f..e78ae5d 100644 --- a/exercises/practice/saddle-points/.meta/config.json +++ b/exercises/practice/saddle-points/.meta/config.json @@ -15,7 +15,6 @@ "invalidator": [ "package.json" ] - }, "blurb": "Detect saddle points in a matrix.", "source": "J Dalbey's Programming Practice problems",