From 69c0793f0f984663e5e2271ae585cffdaba8772d Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Sat, 14 Sep 2024 14:11:25 +1000 Subject: [PATCH] Add Bob exercise --- config.json | 8 + .../practice/bob/.docs/instructions.append.md | 5 + exercises/practice/bob/.docs/instructions.md | 19 ++ exercises/practice/bob/.docs/introduction.md | 10 + exercises/practice/bob/.eslintrc | 18 ++ exercises/practice/bob/.meta/config.json | 28 +++ exercises/practice/bob/.meta/proof.ci.wat | 90 ++++++++ exercises/practice/bob/.meta/tests.toml | 85 ++++++++ exercises/practice/bob/.npmrc | 1 + exercises/practice/bob/LICENSE | 21 ++ exercises/practice/bob/babel.config.js | 4 + exercises/practice/bob/bob.spec.js | 204 ++++++++++++++++++ exercises/practice/bob/bob.wat | 15 ++ exercises/practice/bob/package.json | 34 +++ 14 files changed, 542 insertions(+) create mode 100644 exercises/practice/bob/.docs/instructions.append.md create mode 100644 exercises/practice/bob/.docs/instructions.md create mode 100644 exercises/practice/bob/.docs/introduction.md create mode 100644 exercises/practice/bob/.eslintrc create mode 100644 exercises/practice/bob/.meta/config.json create mode 100644 exercises/practice/bob/.meta/proof.ci.wat create mode 100644 exercises/practice/bob/.meta/tests.toml create mode 100644 exercises/practice/bob/.npmrc create mode 100644 exercises/practice/bob/LICENSE create mode 100644 exercises/practice/bob/babel.config.js create mode 100644 exercises/practice/bob/bob.spec.js create mode 100644 exercises/practice/bob/bob.wat create mode 100644 exercises/practice/bob/package.json diff --git a/config.json b/config.json index 13a497c..2d0bd0f 100644 --- a/config.json +++ b/config.json @@ -55,6 +55,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "bob", + "name": "Bob", + "uuid": "86d6375c-d791-431b-8440-d47fe31d48a5", + "practices": [], + "prerequisites": [], + "difficulty": 3 + }, { "slug": "collatz-conjecture", "name": "Collatz Conjecture", diff --git a/exercises/practice/bob/.docs/instructions.append.md b/exercises/practice/bob/.docs/instructions.append.md new file mode 100644 index 0000000..a62a79d --- /dev/null +++ b/exercises/practice/bob/.docs/instructions.append.md @@ -0,0 +1,5 @@ +## Reserved Memory + +Bytes 64-319 of the linear memory are reserved for the input string. + +The input string can be modified in place if desired. \ No newline at end of file diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md new file mode 100644 index 0000000..bb702f7 --- /dev/null +++ b/exercises/practice/bob/.docs/instructions.md @@ -0,0 +1,19 @@ +# Instructions + +Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. + +Bob only ever answers one of five things: + +- **"Sure."** + This is his response if you ask him a question, such as "How are you?" + The convention used for questions is that it ends with a question mark. +- **"Whoa, chill out!"** + This is his answer if you YELL AT HIM. + The convention used for yelling is ALL CAPITAL LETTERS. +- **"Calm down, I know what I'm doing!"** + This is what he says if you yell a question at him. +- **"Fine. Be that way!"** + This is how he responds to silence. + The convention used for silence is nothing, or various combinations of whitespace characters. +- **"Whatever."** + This is what he answers to anything else. diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md new file mode 100644 index 0000000..ea4a807 --- /dev/null +++ b/exercises/practice/bob/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Bob is a [lackadaisical][] teenager. +He likes to think that he's very cool. +And he definitely doesn't get excited about things. +That wouldn't be cool. + +When people talk to him, his responses are pretty limited. + +[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical diff --git a/exercises/practice/bob/.eslintrc b/exercises/practice/bob/.eslintrc new file mode 100644 index 0000000..1dbeac2 --- /dev/null +++ b/exercises/practice/bob/.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/bob/.meta/config.json b/exercises/practice/bob/.meta/config.json new file mode 100644 index 0000000..aaf628d --- /dev/null +++ b/exercises/practice/bob/.meta/config.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "bob.wat" + ], + "test": [ + "bob.spec.js" + ], + "example": [ + ".meta/proof.ci.wat" + ], + "invalidator": [ + "package.json" + ] + }, + "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.", + "source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.", + "source_url": "https://pine.fm/LearnToProgram/?Chapter=06", + "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/bob/.meta/proof.ci.wat b/exercises/practice/bob/.meta/proof.ci.wat new file mode 100644 index 0000000..820c451 --- /dev/null +++ b/exercises/practice/bob/.meta/proof.ci.wat @@ -0,0 +1,90 @@ +(module + (memory (export "mem") 1) + + (data (i32.const 400) "Sure.") + (data (i32.const 410) "Whoa, chill out!") + (data (i32.const 430) "Calm down, I know what I'm doing!") + (data (i32.const 470) "Fine. Be that way!") + (data (i32.const 490) "Whatever.") + + (global $SURE_OFFSET i32 (i32.const 400)) + (global $SURE_LENGTH i32 (i32.const 5)) + + (global $WHOA_OFFSET i32 (i32.const 410)) + (global $WHOA_LENGTH i32 (i32.const 16)) + + (global $CALM_OFFSET i32 (i32.const 430)) + (global $CALM_LENGTH i32 (i32.const 33)) + + (global $FINE_OFFSET i32 (i32.const 470)) + (global $FINE_LENGTH i32 (i32.const 18)) + + (global $WHATEVER_OFFSET i32 (i32.const 490)) + (global $WHATEVER_LENGTH i32 (i32.const 9)) + + (global $SPACE i32 (i32.const 32)) + (global $QUESTION_MARK i32 (i32.const 63)) + (global $UPPER_A i32 (i32.const 65)) + (global $LOWER_A i32 (i32.const 97)) + + ;; + ;; Reply to someone when they say something or ask a question + ;; + ;; @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)} - The offset and length of the reversed string in linear memory + ;; + (func (export "response") (param $offset i32) (param $length i32) (result i32 i32) + (local $index i32) + (local $stop i32) + (local $value i32) + (local $silence i32) + (local $upper i32) + (local $lower i32) + (local $question i32) + + (local.set $index (local.get $offset)) + (local.set $stop (i32.add (local.get $offset) (local.get $length))) + (local.set $silence (i32.const 1)) + (local.set $upper (i32.const 0)) + (local.set $lower (i32.const 0)) + (local.set $question (i32.const 0)) + + (loop $read + (if (i32.lt_u (local.get $index) (local.get $stop)) (then + (local.set $value (i32.load8_u (local.get $index))) + (local.set $index (i32.add (local.get $index) (i32.const 1))) + (if (i32.gt_u (local.get $value) (global.get $SPACE)) (then + (local.set $silence (i32.const 0)) + (local.set $question (i32.eq (local.get $value) (global.get $QUESTION_MARK))) + (if (i32.lt_u (i32.sub (local.get $value) (global.get $UPPER_A)) (i32.const 26)) (then + (local.set $upper (i32.const 1)) + )) + (if (i32.lt_u (i32.sub (local.get $value) (global.get $LOWER_A)) (i32.const 26)) (then + (local.set $lower (i32.const 1)) + )) + )) + (br $read) + )) + ) + + (if (local.get $silence) (then + (return (global.get $FINE_OFFSET) (global.get $FINE_LENGTH)) + )) + + (if (i32.eq (i32.sub (local.get $upper) (local.get $lower)) (i32.const 1)) (then + (if (local.get $question) (then + (return (global.get $CALM_OFFSET) (global.get $CALM_LENGTH)) + )) + + (return (global.get $WHOA_OFFSET) (global.get $WHOA_LENGTH)) + )) + + (if (local.get $question) (then + (return (global.get $SURE_OFFSET) (global.get $SURE_LENGTH)) + )) + + (return (global.get $WHATEVER_OFFSET) (global.get $WHATEVER_LENGTH)) + ) +) diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml new file mode 100644 index 0000000..ea47d6b --- /dev/null +++ b/exercises/practice/bob/.meta/tests.toml @@ -0,0 +1,85 @@ +# 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. + +[e162fead-606f-437a-a166-d051915cea8e] +description = "stating something" + +[73a966dc-8017-47d6-bb32-cf07d1a5fcd9] +description = "shouting" + +[d6c98afd-df35-4806-b55e-2c457c3ab748] +description = "shouting gibberish" + +[8a2e771d-d6f1-4e3f-b6c6-b41495556e37] +description = "asking a question" + +[81080c62-4e4d-4066-b30a-48d8d76920d9] +description = "asking a numeric question" + +[2a02716d-685b-4e2e-a804-2adaf281c01e] +description = "asking gibberish" + +[c02f9179-ab16-4aa7-a8dc-940145c385f7] +description = "talking forcefully" + +[153c0e25-9bb5-4ec5-966e-598463658bcd] +description = "using acronyms in regular speech" + +[a5193c61-4a92-4f68-93e2-f554eb385ec6] +description = "forceful question" + +[a20e0c54-2224-4dde-8b10-bd2cdd4f61bc] +description = "shouting numbers" + +[f7bc4b92-bdff-421e-a238-ae97f230ccac] +description = "no letters" + +[bb0011c5-cd52-4a5b-8bfb-a87b6283b0e2] +description = "question with no letters" + +[496143c8-1c31-4c01-8a08-88427af85c66] +description = "shouting with special characters" + +[e6793c1c-43bd-4b8d-bc11-499aea73925f] +description = "shouting with no exclamation mark" + +[aa8097cc-c548-4951-8856-14a404dd236a] +description = "statement containing question mark" + +[9bfc677d-ea3a-45f2-be44-35bc8fa3753e] +description = "non-letters with question" + +[8608c508-f7de-4b17-985b-811878b3cf45] +description = "prattling on" + +[bc39f7c6-f543-41be-9a43-fd1c2f753fc0] +description = "silence" + +[d6c47565-372b-4b09-b1dd-c40552b8378b] +description = "prolonged silence" + +[4428f28d-4100-4d85-a902-e5a78cb0ecd3] +description = "alternate silence" + +[66953780-165b-4e7e-8ce3-4bcb80b6385a] +description = "multiple line question" + +[5371ef75-d9ea-4103-bcfa-2da973ddec1b] +description = "starting with whitespace" + +[05b304d6-f83b-46e7-81e0-4cd3ca647900] +description = "ending with whitespace" + +[72bd5ad3-9b2f-4931-a988-dce1f5771de2] +description = "other whitespace" + +[12983553-8601-46a8-92fa-fcaa3bc4a2a0] +description = "non-question ending with whitespace" diff --git a/exercises/practice/bob/.npmrc b/exercises/practice/bob/.npmrc new file mode 100644 index 0000000..d26df80 --- /dev/null +++ b/exercises/practice/bob/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/bob/LICENSE b/exercises/practice/bob/LICENSE new file mode 100644 index 0000000..90e73be --- /dev/null +++ b/exercises/practice/bob/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/bob/babel.config.js b/exercises/practice/bob/babel.config.js new file mode 100644 index 0000000..9c17ba5 --- /dev/null +++ b/exercises/practice/bob/babel.config.js @@ -0,0 +1,4 @@ +export default { + presets: ["@exercism/babel-preset-javascript"], + plugins: [], +}; diff --git a/exercises/practice/bob/bob.spec.js b/exercises/practice/bob/bob.spec.js new file mode 100644 index 0000000..65b562a --- /dev/null +++ b/exercises/practice/bob/bob.spec.js @@ -0,0 +1,204 @@ +import { compileWat, WasmRunner } from "@exercism/wasm-lib"; + +let wasmModule; +let currentInstance; + +beforeAll(async () => { + try { + const watPath = new URL("./bob.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 response(input) { + const inputBufferOffset = 64; + const inputBufferCapacity = 256; + + const inputLengthEncoded = new TextEncoder().encode(input).length; + if (inputLengthEncoded > inputBufferCapacity) { + throw new Error( + `String is too large for buffer of size ${inputBufferCapacity} bytes` + ); + } + + currentInstance.set_mem_as_utf8(inputBufferOffset, inputLengthEncoded, input); + + // Pass offset and length to WebAssembly function + const [outputOffset, outputLength] = currentInstance.exports.response( + inputBufferOffset, + inputLengthEncoded + ); + + // Decode JS string from returned offset and length + return currentInstance.get_mem_as_utf8(outputOffset, outputLength); +} + +describe("Bob", () => { + 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("stating something", () => { + const expected = "Whatever."; + const actual = response("Tom-ay-to, tom-aaaah-to."); + expect(actual).toEqual(expected); + }); + + xtest("shouting", () => { + const expected = "Whoa, chill out!"; + const actual = response("WATCH OUT!"); + expect(actual).toEqual(expected); + }); + + xtest("shouting gibberish", () => { + const expected = "Whoa, chill out!"; + const actual = response("FCECDFCAAB"); + expect(actual).toEqual(expected); + }); + + xtest("asking a question", () => { + const expected = "Sure."; + const actual = response("Does this cryogenic chamber make me look fat?"); + expect(actual).toEqual(expected); + }); + + xtest("asking a numeric question", () => { + const expected = "Sure."; + const actual = response("You are, what, like 15?"); + expect(actual).toEqual(expected); + }); + + xtest("asking gibberish", () => { + const expected = "Sure."; + const actual = response("fffbbcbeab?"); + expect(actual).toEqual(expected); + }); + + xtest("talking forcefully", () => { + const expected = "Whatever."; + const actual = response("Hi there!"); + expect(actual).toEqual(expected); + }); + + xtest("using acronyms in regular speech", () => { + const expected = "Whatever."; + const actual = response("It's OK if you don't want to go work for NASA."); + expect(actual).toEqual(expected); + }); + + xtest("forceful question", () => { + const expected = "Calm down, I know what I'm doing!"; + const actual = response("WHAT'S GOING ON?"); + expect(actual).toEqual(expected); + }); + + xtest("shouting numbers", () => { + const expected = "Whoa, chill out!"; + const actual = response("1, 2, 3 GO!"); + expect(actual).toEqual(expected); + }); + + xtest("no letters", () => { + const expected = "Whatever."; + const actual = response("1, 2, 3"); + expect(actual).toEqual(expected); + }); + + xtest("question with no letters", () => { + const expected = "Sure."; + const actual = response("4?"); + expect(actual).toEqual(expected); + }); + + xtest("shouting with special characters", () => { + const expected = "Whoa, chill out!"; + const actual = response("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"); + expect(actual).toEqual(expected); + }); + + xtest("shouting with no exclamation mark", () => { + const expected = "Whoa, chill out!"; + const actual = response("I HATE THE DENTIST"); + expect(actual).toEqual(expected); + }); + + xtest("statement containing question mark", () => { + const expected = "Whatever."; + const actual = response("Ending with ? means a question."); + expect(actual).toEqual(expected); + }); + + xtest("non-letters with question", () => { + const expected = "Sure."; + const actual = response(":) ?"); + expect(actual).toEqual(expected); + }); + + xtest("prattling on", () => { + const expected = "Sure."; + const actual = response("Wait! Hang on. Are you going to be OK?"); + expect(actual).toEqual(expected); + }); + + xtest("silence", () => { + const expected = "Fine. Be that way!"; + const actual = response(""); + expect(actual).toEqual(expected); + }); + + xtest("prolonged silence", () => { + const expected = "Fine. Be that way!"; + const actual = response(" "); + expect(actual).toEqual(expected); + }); + + xtest("alternate silence", () => { + const expected = "Fine. Be that way!"; + const actual = response("\t\t\t\t\t\t\t\t\t\t"); + expect(actual).toEqual(expected); + }); + + xtest("multiple line question", () => { + const expected = "Whatever."; + const actual = response("\nDoes this cryogenic chamber make me look fat?\nNo."); + expect(actual).toEqual(expected); + }); + + xtest("starting with whitespace", () => { + const expected = "Whatever."; + const actual = response(" hmmmmmmm..."); + expect(actual).toEqual(expected); + }); + + xtest("ending with whitespace", () => { + const expected = "Sure."; + const actual = response("Okay if like my spacebar quite a bit? "); + expect(actual).toEqual(expected); + }); + + xtest("other whitespace", () => { + const expected = "Fine. Be that way!"; + const actual = response("\n\r \t"); + expect(actual).toEqual(expected); + }); + + xtest("non-question ending with whitespace", () => { + const expected = "Whatever."; + const actual = response("This is a statement ending with whitespace "); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/bob/bob.wat b/exercises/practice/bob/bob.wat new file mode 100644 index 0000000..9f931f4 --- /dev/null +++ b/exercises/practice/bob/bob.wat @@ -0,0 +1,15 @@ +(module + (memory (export "mem") 1) + + ;; + ;; Reply to someone when they say something or ask a question + ;; + ;; @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)} - The offset and length of the reversed string in linear memory + ;; + (func (export "response") (param $offset i32) (param $length i32) (result i32 i32) + (return (local.get $offset) (local.get $length)) + ) +) diff --git a/exercises/practice/bob/package.json b/exercises/practice/bob/package.json new file mode 100644 index 0000000..6b77504 --- /dev/null +++ b/exercises/practice/bob/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/wasm-bob", + "description": "Exercism exercises in WebAssembly.", + "type": "module", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/wasm", + "directory": "exercises/practice/bob" + }, + "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 ." + } +}