From b6291b58668af3362180f11079ca81e10678df9d Mon Sep 17 00:00:00 2001 From: "Bruce M. Axtens" Date: Tue, 24 Mar 2026 21:56:22 +0800 Subject: [PATCH] added acronym. tweaked inhale.sno. --- bin/mktest.r8 | 102 ++++++++++++++++ config.json | 8 ++ .../practice/acronym/.docs/instructions.md | 17 +++ exercises/practice/acronym/.meta/config.json | 24 ++++ exercises/practice/acronym/.meta/example.sno | 9 ++ exercises/practice/acronym/.meta/tests.toml | 37 ++++++ exercises/practice/acronym/acronym.sno | 5 + exercises/practice/acronym/lib/cmprep.sno | 21 ++++ exercises/practice/acronym/lib/inhale.sno | 15 +++ exercises/practice/acronym/lib/listtests.sno | 15 +++ exercises/practice/acronym/lib/nextword.sno | 13 ++ exercises/practice/acronym/lib/whereami.sno | 6 + exercises/practice/acronym/test.sno | 115 ++++++++++++++++++ exercises/practice/hello-world/lib/inhale.sno | 4 +- exercises/practice/isogram/lib/inhale.sno | 4 +- exercises/practice/leap/lib/inhale.sno | 4 +- exercises/practice/pangram/lib/inhale.sno | 4 +- exercises/practice/raindrops/lib/inhale.sno | 4 +- 18 files changed, 397 insertions(+), 10 deletions(-) create mode 100644 bin/mktest.r8 create mode 100644 exercises/practice/acronym/.docs/instructions.md create mode 100644 exercises/practice/acronym/.meta/config.json create mode 100644 exercises/practice/acronym/.meta/example.sno create mode 100644 exercises/practice/acronym/.meta/tests.toml create mode 100644 exercises/practice/acronym/acronym.sno create mode 100644 exercises/practice/acronym/lib/cmprep.sno create mode 100644 exercises/practice/acronym/lib/inhale.sno create mode 100644 exercises/practice/acronym/lib/listtests.sno create mode 100644 exercises/practice/acronym/lib/nextword.sno create mode 100644 exercises/practice/acronym/lib/whereami.sno create mode 100644 exercises/practice/acronym/test.sno diff --git a/bin/mktest.r8 b/bin/mktest.r8 new file mode 100644 index 0000000..83f2ae0 --- /dev/null +++ b/bin/mktest.r8 @@ -0,0 +1,102 @@ +"use strict"; +mktest(); +function mktest() { + var _a; + if (__.argc === 0) { + print("Usage: mktest [output-filename]"); + exit(1); + } + const parsed = argparse(); + const filename = parsed.names[0]; + const output = (_a = parsed.names[1]) !== null && _a !== void 0 ? _a : "test.sno"; + if (parsed.keys.includes("/debug")) + debugger; + print(`Using canonical data: ${filename}`); + const JObject = JSON.parse(slurp(filename)); + const exercise = JObject["exercise"]; + const cases = JObject["cases"]; + if (JObject.cases.cases) { + print("Embedded cases. Skipping..."); + } + const snofile = []; + snofile.push('-include \'bq.sno\''); + snofile.push('-include \'lib/inhale.sno\''); + snofile.push('-include \'lib/whereami.sno\''); + snofile.push('-include \'lib/cmprep.sno\''); + snofile.push('-include \'lib/nextword.sno\''); + snofile.push('-include \'lib/listtests.sno\''); + snofile.push(' pass.count = 0'); + snofile.push(' fail.count = 0'); + snofile.push(' test.count = 0'); + snofile.push(` corecode = inhale('./${exercise}.sno') :s(file.exists)`); + snofile.push(` output = 'file ./${exercise}.sno not found' :(end)`); + snofile.push('file.exists'); + snofile.push(" codeblock = corecode '; :(test.after)'"); + snofile.push(" compiled = code(codeblock)"); + snofile.push(' testlist = host(0)'); + snofile.push(' testlist pos(0) "-list" rpos(0) :f(test.numbers)'); + snofile.push(' listtests()'); + snofile.push(' :(end)'); + snofile.push('test.numbers'); + snofile.push(' ident(testlist) :f(test.loop)'); + snofile.push(` testlist = '${cases.map((_, i) => (i + 1).toString()).join(" ")}'`); + snofile.push('test.loop'); + snofile.push(' testitem = nextword(testlist)'); + snofile.push(' testno = testitem<1>'); + snofile.push(" :($('test.' testno))"); + snofile.push('test.after'); + snofile.push(` output = cmprep(description, result, expected)`); + snofile.push(' ident(testitem<2>) :s(tests.done)'); + snofile.push(' testlist = testitem<2>'); + snofile.push(' :(test.loop)'); + cases.forEach((testcase, i) => { + const description = testcase["description"]; + const uuid = testcase["uuid"]; + const property = testcase["property"]; + const inputKeys = Object.keys(testcase["input"]); + const expected = testcase["expected"]; + snofile.push(`test.${i + 1}`); + snofile.push(`*#${i + 1}. ${description}`); + snofile.push(` description = '${description}'`); + snofile.push(` expected = ${render(expected)}`); + snofile.push(` property = ${render(property)}`); + inputKeys.forEach(key => { + snofile.push(` input.${key} = ${render(testcase.input[key])}`); + }); + snofile.push(` :`); + snofile.push(' :(test.after)'); + snofile.push(`test.${i + 1}.end`); + }); + snofile.push('tests.done'); + snofile.push(' output = test.count " tests. " pass.count " passes, " fail.count " failures."'); + snofile.push(' &CODE = fail.count'); + snofile.push('END'); + spit(output, snofile.join("\n")); + function render(o) { + if ("boolean" === typeof o) + return o ? 1 : 0; + if ("string" === typeof o) { + if (o.includes('"')) + return "'" + o.replace(/\n/g, "' char(10) '").replace(/\r/g, "' char(13) '") + "'"; + if (o.includes("'")) + return '"' + o.replace(/\n/g, '" char(10) "').replace(/\r/g, '" char(13) "') + '"'; + return "'" + o.replace(/\n/g, "' char(10) '").replace(/\r/g, "' char(13) '") + "'"; + } + if (o instanceof Array) { + return `'${o.join(";")}'`; + } + return o; + } + function argparse() { + const keys = []; + const names = []; + for (let i = 0; i < __.argc; i++) { + const kvp = __.args(i); + if (kvp.substring(0, 1) === "/") + keys.push(kvp); + else + names.push(kvp); + } + return { keys, names }; + } +} diff --git a/config.json b/config.json index 44f88d3..0af5637 100644 --- a/config.json +++ b/config.json @@ -74,6 +74,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "acronym", + "name": "Acronym", + "uuid": "037d7a32-c202-43e3-ab3f-ec9406d0b84d", + "practices": [], + "prerequisites": [], + "difficulty": 1 } ] }, diff --git a/exercises/practice/acronym/.docs/instructions.md b/exercises/practice/acronym/.docs/instructions.md new file mode 100644 index 0000000..133bd2c --- /dev/null +++ b/exercises/practice/acronym/.docs/instructions.md @@ -0,0 +1,17 @@ +# Instructions + +Convert a phrase to its acronym. + +Techies love their TLA (Three Letter Acronyms)! + +Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG). + +Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input. + +For example: + +| Input | Output | +| ------------------------- | ------ | +| As Soon As Possible | ASAP | +| Liquid-crystal display | LCD | +| Thank George It's Friday! | TGIF | diff --git a/exercises/practice/acronym/.meta/config.json b/exercises/practice/acronym/.meta/config.json new file mode 100644 index 0000000..ab699f4 --- /dev/null +++ b/exercises/practice/acronym/.meta/config.json @@ -0,0 +1,24 @@ +{ + "authors": ["axtens"], + "files": { + "solution": [ + "acronym.sno" + ], + "test": [ + "test.sno" + ], + "example": [ + ".meta/example.sno" + ], + "invalidator": [ + "lib/cmprep.sno", + "lib/inhale.sno", + "lib/listtests.sno", + "lib/nextword.sno", + "lib/whereami.sno" + ] + }, + "blurb": "Convert a long phrase to its acronym.", + "source": "Julien Vanier", + "source_url": "https://github.com/monkbroc" +} diff --git a/exercises/practice/acronym/.meta/example.sno b/exercises/practice/acronym/.meta/example.sno new file mode 100644 index 0000000..bc05ce5 --- /dev/null +++ b/exercises/practice/acronym/.meta/example.sno @@ -0,0 +1,9 @@ + input.phrase = ident(description) input +acronym input.phrase = replace(input.phrase, &lcase, &ucase) +clean input.phrase notany(' -' &ucase) = :s(clean) + result = "" +loop input.phrase (span(&ucase) . word) = "" :f(done) + result = result substr(word, 1, 1) :(loop) +done +end + diff --git a/exercises/practice/acronym/.meta/tests.toml b/exercises/practice/acronym/.meta/tests.toml new file mode 100644 index 0000000..6e3277c --- /dev/null +++ b/exercises/practice/acronym/.meta/tests.toml @@ -0,0 +1,37 @@ +# 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. + +[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] +description = "basic" + +[79ae3889-a5c0-4b01-baf0-232d31180c08] +description = "lowercase words" + +[ec7000a7-3931-4a17-890e-33ca2073a548] +description = "punctuation" + +[32dd261c-0c92-469a-9c5c-b192e94a63b0] +description = "all caps word" + +[ae2ac9fa-a606-4d05-8244-3bcc4659c1d4] +description = "punctuation without whitespace" + +[0e4b1e7c-1a6d-48fb-81a7-bf65eb9e69f9] +description = "very long abbreviation" + +[6a078f49-c68d-4b7b-89af-33a1a98c28cc] +description = "consecutive delimiters" + +[5118b4b1-4572-434c-8d57-5b762e57973e] +description = "apostrophes" + +[adc12eab-ec2d-414f-b48c-66a4fc06cdef] +description = "underscore emphasis" diff --git a/exercises/practice/acronym/acronym.sno b/exercises/practice/acronym/acronym.sno new file mode 100644 index 0000000..bf222ab --- /dev/null +++ b/exercises/practice/acronym/acronym.sno @@ -0,0 +1,5 @@ + input.phrase = ident(description) input +acronym result = 0 + output = result +end + diff --git a/exercises/practice/acronym/lib/cmprep.sno b/exercises/practice/acronym/lib/cmprep.sno new file mode 100644 index 0000000..6233ec9 --- /dev/null +++ b/exercises/practice/acronym/lib/cmprep.sno @@ -0,0 +1,21 @@ + define('cmprep(testname,result,expected)lhs,rhs,bhs') :(e.cmprep) +cmprep test.count = test.count + 1 + datatype(result) len(1) . lhs + datatype(expected) len(1) . rhs + bhs = lhs rhs '.cmprep' + :($bhs) +is.cmprep + eq(result, 0 + expected) :s(s.cmprep)f(f.cmprep) +si.cmprep + eq(0 + result, expected) :s(s.cmprep)f(f.cmprep) +ii.cmprep + eq(result, expected) :s(s.cmprep)f(f.cmprep) +ss.cmprep + cmprep = ident(result, expected) :s(s.cmprep)f(f.cmprep) +s.cmprep + pass.count = pass.count + 1 + cmprep = testname ' ... OK' :(return) +f.cmprep + fail.count = fail.count + 1 + cmprep = testname ' ... FAIL' :(return) +e.cmprep diff --git a/exercises/practice/acronym/lib/inhale.sno b/exercises/practice/acronym/lib/inhale.sno new file mode 100644 index 0000000..de1194b --- /dev/null +++ b/exercises/practice/acronym/lib/inhale.sno @@ -0,0 +1,15 @@ + define('inhale(script)codeblock,unit,line') :(e.inhale) +inhale codeblock = '' + unit = io_findunit() + input(.file, unit, , script) :f(f.inhale) +l.inhale + line = file :f(s.inhale) + line pos(0) ('E' | 'e') ('N' | 'n') ('D' | 'd') rpos(0) :s(l.inhale) + codeblock = codeblock line ';' :(l.inhale) +s.inhale + endfile(file) + detach(.file) + inhale = codeblock :(return) +f.inhale + output = script ' not found' :(freturn) +e.inhale diff --git a/exercises/practice/acronym/lib/listtests.sno b/exercises/practice/acronym/lib/listtests.sno new file mode 100644 index 0000000..667421b --- /dev/null +++ b/exercises/practice/acronym/lib/listtests.sno @@ -0,0 +1,15 @@ + define('listtests()file,unit,line') :(e.listtests) +listtests + output = 'Reading tests in ' host(2,1) '...' + unit = io_findunit() + input(.file, unit, , host(2,1)) :f(f.listtests) +n.listtests + line = file :f(s.listtests) + line pos(0) '*#' any('0123456789') :f(n.listtests) + output = line :(n.listtests) +s.listtests + endfile(file) + detach(.file) :(return) +f.listtests + output = host(2,1) ' not found' :(freturn) +e.listtests diff --git a/exercises/practice/acronym/lib/nextword.sno b/exercises/practice/acronym/lib/nextword.sno new file mode 100644 index 0000000..584bfd4 --- /dev/null +++ b/exercises/practice/acronym/lib/nextword.sno @@ -0,0 +1,13 @@ + define('nextword(text)result,first') :(e.nextword) +nextword result = array("1:2") + text break(' ') . first span(' ') = :f(f.nextword) + result<1> = first + result<2> = text +* output = 'nextword: ' result<1> ',' result<2> + nextword = result :(return) +f.nextword + result<1> = text + result<2> = null +* output = 'nextword: ' result<1> ',' result<2> + nextword = result :(return) +e.nextword diff --git a/exercises/practice/acronym/lib/whereami.sno b/exercises/practice/acronym/lib/whereami.sno new file mode 100644 index 0000000..42678aa --- /dev/null +++ b/exercises/practice/acronym/lib/whereami.sno @@ -0,0 +1,6 @@ + define('whereami()tmp') :(e.whereami) +whereami tmp = bq("pwd") + tmp = reverse(tmp) + tmp ? break('/') . tmp + whereami = reverse(tmp) :(return) +e.whereami diff --git a/exercises/practice/acronym/test.sno b/exercises/practice/acronym/test.sno new file mode 100644 index 0000000..77b4b82 --- /dev/null +++ b/exercises/practice/acronym/test.sno @@ -0,0 +1,115 @@ +-include 'bq.sno' +-include 'lib/inhale.sno' +-include 'lib/whereami.sno' +-include 'lib/cmprep.sno' +-include 'lib/nextword.sno' +-include 'lib/listtests.sno' + pass.count = 0 + fail.count = 0 + test.count = 0 + corecode = inhale('./acronym.sno') :s(file.exists) + output = 'file ./acronym.sno not found' :(end) +file.exists + codeblock = corecode '; :(test.after)' + compiled = code(codeblock) + testlist = host(0) + testlist pos(0) "-list" rpos(0) :f(test.numbers) + listtests() + :(end) +test.numbers + ident(testlist) :f(test.loop) + testlist = '1 2 3 4 5 6 7 8 9' +test.loop + testitem = nextword(testlist) + testno = testitem<1> + :($('test.' testno)) +test.after + output = cmprep(description, result, expected) + ident(testitem<2>) :s(tests.done) + testlist = testitem<2> + :(test.loop) +test.1 +*#1. basic + description = 'basic' + expected = 'PNG' + property = 'abbreviate' + input.phrase = 'Portable Network Graphics' + : + :(test.after) +test.1.end +test.2 +*#2. lowercase words + description = 'lowercase words' + expected = 'ROR' + property = 'abbreviate' + input.phrase = 'Ruby on Rails' + : + :(test.after) +test.2.end +test.3 +*#3. punctuation + description = 'punctuation' + expected = 'FIFO' + property = 'abbreviate' + input.phrase = 'First In, First Out' + : + :(test.after) +test.3.end +test.4 +*#4. all caps word + description = 'all caps word' + expected = 'GIMP' + property = 'abbreviate' + input.phrase = 'GNU Image Manipulation Program' + : + :(test.after) +test.4.end +test.5 +*#5. punctuation without whitespace + description = 'punctuation without whitespace' + expected = 'CMOS' + property = 'abbreviate' + input.phrase = 'Complementary metal-oxide semiconductor' + : + :(test.after) +test.5.end +test.6 +*#6. very long abbreviation + description = 'very long abbreviation' + expected = 'ROTFLSHTMDCOALM' + property = 'abbreviate' + input.phrase = 'Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me' + : + :(test.after) +test.6.end +test.7 +*#7. consecutive delimiters + description = 'consecutive delimiters' + expected = 'SIMUFTA' + property = 'abbreviate' + input.phrase = 'Something - I made up from thin air' + : + :(test.after) +test.7.end +test.8 +*#8. apostrophes + description = 'apostrophes' + expected = 'HC' + property = 'abbreviate' + input.phrase = "Halley's Comet" + : + :(test.after) +test.8.end +test.9 +*#9. underscore emphasis + description = 'underscore emphasis' + expected = 'TRNT' + property = 'abbreviate' + input.phrase = 'The Road _Not_ Taken' + : + :(test.after) +test.9.end +tests.done + output = test.count " tests. " pass.count " passes, " fail.count " failures." + &CODE = fail.count +END \ No newline at end of file diff --git a/exercises/practice/hello-world/lib/inhale.sno b/exercises/practice/hello-world/lib/inhale.sno index 1c9c9e1..de1194b 100644 --- a/exercises/practice/hello-world/lib/inhale.sno +++ b/exercises/practice/hello-world/lib/inhale.sno @@ -4,12 +4,12 @@ inhale codeblock = '' input(.file, unit, , script) :f(f.inhale) l.inhale line = file :f(s.inhale) - line pos(0) 'END' rpos(0) :s(l.inhale) + line pos(0) ('E' | 'e') ('N' | 'n') ('D' | 'd') rpos(0) :s(l.inhale) codeblock = codeblock line ';' :(l.inhale) s.inhale endfile(file) detach(.file) - inhale = codeblock :(return) + inhale = codeblock :(return) f.inhale output = script ' not found' :(freturn) e.inhale diff --git a/exercises/practice/isogram/lib/inhale.sno b/exercises/practice/isogram/lib/inhale.sno index 1c9c9e1..de1194b 100644 --- a/exercises/practice/isogram/lib/inhale.sno +++ b/exercises/practice/isogram/lib/inhale.sno @@ -4,12 +4,12 @@ inhale codeblock = '' input(.file, unit, , script) :f(f.inhale) l.inhale line = file :f(s.inhale) - line pos(0) 'END' rpos(0) :s(l.inhale) + line pos(0) ('E' | 'e') ('N' | 'n') ('D' | 'd') rpos(0) :s(l.inhale) codeblock = codeblock line ';' :(l.inhale) s.inhale endfile(file) detach(.file) - inhale = codeblock :(return) + inhale = codeblock :(return) f.inhale output = script ' not found' :(freturn) e.inhale diff --git a/exercises/practice/leap/lib/inhale.sno b/exercises/practice/leap/lib/inhale.sno index 1c9c9e1..de1194b 100644 --- a/exercises/practice/leap/lib/inhale.sno +++ b/exercises/practice/leap/lib/inhale.sno @@ -4,12 +4,12 @@ inhale codeblock = '' input(.file, unit, , script) :f(f.inhale) l.inhale line = file :f(s.inhale) - line pos(0) 'END' rpos(0) :s(l.inhale) + line pos(0) ('E' | 'e') ('N' | 'n') ('D' | 'd') rpos(0) :s(l.inhale) codeblock = codeblock line ';' :(l.inhale) s.inhale endfile(file) detach(.file) - inhale = codeblock :(return) + inhale = codeblock :(return) f.inhale output = script ' not found' :(freturn) e.inhale diff --git a/exercises/practice/pangram/lib/inhale.sno b/exercises/practice/pangram/lib/inhale.sno index 1c9c9e1..de1194b 100644 --- a/exercises/practice/pangram/lib/inhale.sno +++ b/exercises/practice/pangram/lib/inhale.sno @@ -4,12 +4,12 @@ inhale codeblock = '' input(.file, unit, , script) :f(f.inhale) l.inhale line = file :f(s.inhale) - line pos(0) 'END' rpos(0) :s(l.inhale) + line pos(0) ('E' | 'e') ('N' | 'n') ('D' | 'd') rpos(0) :s(l.inhale) codeblock = codeblock line ';' :(l.inhale) s.inhale endfile(file) detach(.file) - inhale = codeblock :(return) + inhale = codeblock :(return) f.inhale output = script ' not found' :(freturn) e.inhale diff --git a/exercises/practice/raindrops/lib/inhale.sno b/exercises/practice/raindrops/lib/inhale.sno index 1c9c9e1..de1194b 100644 --- a/exercises/practice/raindrops/lib/inhale.sno +++ b/exercises/practice/raindrops/lib/inhale.sno @@ -4,12 +4,12 @@ inhale codeblock = '' input(.file, unit, , script) :f(f.inhale) l.inhale line = file :f(s.inhale) - line pos(0) 'END' rpos(0) :s(l.inhale) + line pos(0) ('E' | 'e') ('N' | 'n') ('D' | 'd') rpos(0) :s(l.inhale) codeblock = codeblock line ';' :(l.inhale) s.inhale endfile(file) detach(.file) - inhale = codeblock :(return) + inhale = codeblock :(return) f.inhale output = script ' not found' :(freturn) e.inhale