Skip to content

Commit 0bd8398

Browse files
Merge pull request #118 from keiravillekode/micro-blog
Add micro-blog exercise
2 parents cb7a03d + 2f23151 commit 0bd8398

File tree

13 files changed

+423
-0
lines changed

13 files changed

+423
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@
4747
"prerequisites": [],
4848
"difficulty": 1
4949
},
50+
{
51+
"slug": "micro-blog",
52+
"name": "Micro Blog",
53+
"uuid": "45dc52f5-2e84-4a52-8272-bb4b1e36b43b",
54+
"practices": [],
55+
"prerequisites": [],
56+
"difficulty": 2
57+
},
5058
{
5159
"slug": "collatz-conjecture",
5260
"name": "Collatz Conjecture",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Reserved Memory
2+
3+
The buffer for the UTF-8 input string uses bytes 64-319 of linear memory.
4+
5+
The input string can be modified in place if desired.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Instructions
2+
3+
You have identified a gap in the social media market for very very short posts.
4+
Now that Twitter allows 280 character posts, people wanting quick social media updates aren't being served.
5+
You decide to create your own social media network.
6+
7+
To make your product noteworthy, you make it extreme and only allow posts of 5 or less characters.
8+
Any posts of more than 5 characters should be truncated to 5.
9+
10+
To allow your users to express themselves fully, you allow Emoji and other Unicode.
11+
12+
The task is to truncate input strings to 5 characters.
13+
14+
## Text Encodings
15+
16+
Text stored digitally has to be converted to a series of bytes.
17+
There are 3 ways to map characters to bytes in common use.
18+
19+
- **ASCII** can encode English language characters.
20+
All characters are precisely 1 byte long.
21+
- **UTF-8** is a Unicode text encoding.
22+
Characters take between 1 and 4 bytes.
23+
- **UTF-16** is a Unicode text encoding.
24+
Characters are either 2 or 4 bytes long.
25+
26+
UTF-8 and UTF-16 are both Unicode encodings which means they're capable of representing a massive range of characters including:
27+
28+
- Text in most of the world's languages and scripts
29+
- Historic text
30+
- Emoji
31+
32+
UTF-8 and UTF-16 are both variable length encodings, which means that different characters take up different amounts of space.
33+
34+
Consider the letter 'a' and the emoji '😛'.
35+
In UTF-16 the letter takes 2 bytes but the emoji takes 4 bytes.
36+
37+
The trick to this exercise is to use APIs designed around Unicode characters (codepoints) instead of Unicode codeunits.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"root": true,
3+
"extends": "@exercism/eslint-config-javascript",
4+
"env": {
5+
"jest": true
6+
},
7+
"overrides": [
8+
{
9+
"files": [
10+
"*.spec.js"
11+
],
12+
"excludedFiles": [
13+
"custom.spec.js"
14+
],
15+
"extends": "@exercism/eslint-config-javascript/maintainers"
16+
}
17+
]
18+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"authors": [
3+
"keiravillekode"
4+
],
5+
"files": {
6+
"solution": [
7+
"micro-blog.wat"
8+
],
9+
"test": [
10+
"micro-blog.spec.js"
11+
],
12+
"example": [
13+
".meta/proof.ci.wat"
14+
],
15+
"invalidator": [
16+
"package.json"
17+
]
18+
},
19+
"blurb": "Given an input string, truncate it to 5 characters.",
20+
"custom": {
21+
"version.tests.compatibility": "jest-27",
22+
"flag.tests.task-per-describe": false,
23+
"flag.tests.may-run-long": false,
24+
"flag.tests.includes-optional": false
25+
}
26+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
(module
2+
(memory (export "mem") 1)
3+
4+
;;
5+
;; Truncate UTF-8 input string to 5 characters
6+
;;
7+
;; @param {i32} offset - The offset of the input string in linear memory
8+
;; @param {i32} length - The length of the input string in linear memory
9+
;;
10+
;; @returns {(i32,i32)} - The offset and length of the truncated string in linear memory
11+
;;
12+
(func (export "truncate") (param $offset i32) (param $length i32) (result i32 i32)
13+
14+
(local $index i32)
15+
(local $stop i32)
16+
(local $remaining i32)
17+
(local $byte i32)
18+
(local $step i32)
19+
20+
(local.set $index (local.get $offset))
21+
(local.set $stop
22+
(i32.add
23+
(local.get $offset)
24+
(local.get $length)
25+
)
26+
)
27+
(local.set $remaining (i32.const 5))
28+
29+
(loop
30+
(if (i32.or (i32.eqz (local.get $remaining))
31+
(i32.eq (local.get $index) (local.get $stop)))
32+
(then (return (local.get $offset)
33+
(i32.sub (local.get $index) (local.get $offset)))
34+
)
35+
)
36+
37+
(local.set
38+
$remaining
39+
(i32.sub
40+
(local.get $remaining)
41+
(i32.const 1)
42+
)
43+
)
44+
45+
(local.set $byte (i32.load8_u (local.get $index)))
46+
47+
(if (i32.eq (i32.const 0xf0) (i32.and (i32.const 0xf0) (local.get $byte)))
48+
(then
49+
(local.set $step (i32.const 4))
50+
)
51+
(else
52+
(if (i32.eq (i32.const 0xe0) (i32.and (i32.const 0xe0) (local.get $byte)))
53+
(then
54+
(local.set $step (i32.const 3))
55+
)
56+
(else
57+
(if (i32.eq (i32.const 0xc0) (i32.and (i32.const 0xc0) (local.get $byte)))
58+
(then
59+
(local.set $step (i32.const 2))
60+
)
61+
(else
62+
(local.set $step (i32.const 1))
63+
)
64+
)
65+
)
66+
)
67+
)
68+
)
69+
70+
(local.set
71+
$index
72+
(i32.add
73+
(local.get $index)
74+
(local.get $step)
75+
)
76+
)
77+
78+
(br 0)
79+
)
80+
(unreachable)
81+
)
82+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[b927b57f-7c98-42fd-8f33-fae091dc1efc]
13+
description = "English language short"
14+
15+
[a3fcdc5b-0ed4-4f49-80f5-b1a293eac2a0]
16+
description = "English language long"
17+
18+
[01910864-8e15-4007-9c7c-ac956c686e60]
19+
description = "German language short (broth)"
20+
21+
[f263e488-aefb-478f-a671-b6ba99722543]
22+
description = "German language long (bear carpet → beards)"
23+
24+
[0916e8f1-41d7-4402-a110-b08aa000342c]
25+
description = "Bulgarian language short (good)"
26+
27+
[bed6b89c-03df-4154-98e6-a61a74f61b7d]
28+
description = "Greek language short (health)"
29+
30+
[485a6a70-2edb-424d-b999-5529dbc8e002]
31+
description = "Maths short"
32+
33+
[8b4b7b51-8f48-4fbe-964e-6e4e6438be28]
34+
description = "Maths long"
35+
36+
[71f4a192-0566-4402-a512-fe12878be523]
37+
description = "English and emoji short"
38+
39+
[6f0f71f3-9806-4759-a844-fa182f7bc203]
40+
description = "Emoji short"
41+
42+
[ce71fb92-5214-46d0-a7f8-d5ba56b4cc6e]
43+
description = "Emoji long"
44+
45+
[5dee98d2-d56e-468a-a1f2-121c3f7c5a0b]
46+
description = "Royal Flush?"

exercises/practice/micro-blog/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
audit=false

exercises/practice/micro-blog/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Exercism
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
presets: ["@exercism/babel-preset-javascript"],
3+
plugins: [],
4+
};
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { compileWat, WasmRunner } from "@exercism/wasm-lib";
2+
3+
let wasmModule;
4+
let currentInstance;
5+
6+
beforeAll(async () => {
7+
try {
8+
const watPath = new URL("./micro-blog.wat", import.meta.url);
9+
const { buffer } = await compileWat(watPath);
10+
wasmModule = await WebAssembly.compile(buffer);
11+
} catch (err) {
12+
console.log(`Error compiling *.wat: \n${err}`);
13+
process.exit(1);
14+
}
15+
});
16+
17+
function truncate(input) {
18+
const inputBufferOffset = 64;
19+
const inputBufferCapacity = 256;
20+
21+
const inputLengthEncoded = new TextEncoder().encode(input).length;
22+
if (inputLengthEncoded > inputBufferCapacity) {
23+
throw new Error(
24+
`String is too large for buffer of size ${inputBufferCapacity} bytes`
25+
);
26+
}
27+
28+
currentInstance.set_mem_as_utf8(inputBufferOffset, inputLengthEncoded, input);
29+
30+
// Pass offset and length to WebAssembly function
31+
const [outputOffset, outputLength] = currentInstance.exports.truncate(
32+
inputBufferOffset,
33+
inputLengthEncoded
34+
);
35+
36+
// Decode JS string from returned offset and length
37+
return currentInstance.get_mem_as_utf8(outputOffset, outputLength);
38+
}
39+
40+
describe("Truncate", () => {
41+
beforeEach(async () => {
42+
currentInstance = null;
43+
if (!wasmModule) {
44+
return Promise.reject();
45+
}
46+
try {
47+
currentInstance = await new WasmRunner(wasmModule);
48+
return Promise.resolve();
49+
} catch (err) {
50+
console.log(`Error instantiating WebAssembly module: ${err}`);
51+
return Promise.reject();
52+
}
53+
});
54+
55+
test("English language short", () => {
56+
const expected = "Hi";
57+
const actual = truncate("Hi");
58+
expect(actual).toEqual(expected);
59+
});
60+
61+
xtest("English language long", () => {
62+
const expected = "Hello";
63+
const actual = truncate("Hello there");
64+
expect(actual).toEqual(expected);
65+
});
66+
67+
xtest("German language short (broth)", () => {
68+
const expected = "brühe";
69+
const actual = truncate("brühe");
70+
expect(actual).toEqual(expected);
71+
});
72+
73+
xtest("German language long (bear carpet → beards)", () => {
74+
const expected = "Bärte";
75+
const actual = truncate("Bärteppich");
76+
expect(actual).toEqual(expected);
77+
});
78+
79+
xtest("Bulgarian language short (good)", () => {
80+
const expected = "Добър";
81+
const actual = truncate("Добър");
82+
expect(actual).toEqual(expected);
83+
});
84+
85+
xtest("Greek language short (health)", () => {
86+
const expected = "υγειά";
87+
const actual = truncate("υγειά");
88+
expect(actual).toEqual(expected);
89+
});
90+
91+
xtest("Maths short", () => {
92+
const expected = "a=πr²";
93+
const actual = truncate("a=πr²");
94+
expect(actual).toEqual(expected);
95+
});
96+
97+
xtest("Maths long", () => {
98+
const expected = "∅⊊ℕ⊊ℤ";
99+
const actual = truncate("∅⊊ℕ⊊ℤ⊊ℚ⊊ℝ⊊ℂ");
100+
expect(actual).toEqual(expected);
101+
});
102+
103+
xtest("English and emoji short", () => {
104+
const expected = "Fly 🛫";
105+
const actual = truncate("Fly 🛫");
106+
expect(actual).toEqual(expected);
107+
});
108+
109+
xtest("Emoji short", () => {
110+
const expected = "💇";
111+
const actual = truncate("💇");
112+
expect(actual).toEqual(expected);
113+
});
114+
115+
xtest("Emoji long", () => {
116+
const expected = "❄🌡🤧🤒🏥";
117+
const actual = truncate("❄🌡🤧🤒🏥🕰😀");
118+
expect(actual).toEqual(expected);
119+
});
120+
121+
xtest("Royal Flush?", () => {
122+
const expected = "🃎🂸🃅🃋🃍";
123+
const actual = truncate("🃎🂸🃅🃋🃍🃁🃊");
124+
expect(actual).toEqual(expected);
125+
});
126+
});

0 commit comments

Comments
 (0)