Is this a word?

Toki pona is a minimalist constructed language, and thus it has minimalist phonotactics (rules describing what sounds make valid words).

Toki Pona has 8 consonant sounds m, n, p, t, k, s, w, l and j and 5 vowel sounds a, e, i, o, u. A single basic syllable in toki pona consists of any 1 consonant, any one vowel and optionally an n. So all of the following are valid:

pi
ko
wa
san
jen


There are four exceptions of sequences that are forbidden they are ji, wu, wo, ti.

With the basic syllables we can build words. A word is just a string of basic syllables with two special rules:

1. When we have an n followed by either n or m we drop the n. e.g. jan + mo is jamo not janmo
2. The initial syllable of a word can drop the initial consonant. e.g. awen and pawen are both legal and distinct words. This does allow for words consisting of just a vowel.

Your task is to take a non-empty string consisting of lowercase alphabetic characters (a through z) and determine if it makes a valid toki pona word.

When the input is a valid word you should output one value and when it is not you should output another distinct value.

This is so answers will be scored in bytes with the goal being to minimize the size of the source.

Test cases

Accept:

awen
jan
e
monsuta
kepekin
ike
sinpin
pakala
tamako
jamo
pankulato
pawen
an
nene
nenen


Reject:

bimi
ponas
plani
womo
won
tin
wu
miji
sunmo
anna
sam
kain
op
p
n

• "we drop the n" - is that both of them in the case when there are two? Jan 14 at 12:22
• @JonathanAllan Only 1. Jan 14 at 12:23
• Since it is a decision problem I have, so far, assumed truthy and falsey are valid as output sets (even though the post states stuff about distinct values). Is that OK? Jan 14 at 13:22
• @JonathanAllan The outputs are as stated in the question. Jan 14 at 14:08
• Fair enough, but I'd suggest rethinking that standpoint for the future - if a language has no if-else, logical processing or equivalent construct then those languages simply have no concept of truthy and falsey (by definition) and therefore should just use the two distinct values option that should be available to them. Most languages do, so the strict IO just adds extra, boring work. Furthermore competition is intra-language, so the "disadvantage" argument holds no weight. Jan 14 at 14:25

Retina 0.8.2, 48 47 bytes

Aji|nm|nn|ti|wu|wo
^((^|[j-npstw])[aeiou]n?)+$ Try it online! Link includes test cases. Edit: Saved 1 obvious byte thanks to @ovs. Explanation: Aji|nm|nn|ti|wu|wo  Delete invalid inputs. ^((^|[j-npstw])[aeiou]n?)+$


Match valid inputs that weren't invalidated above.

• j-mn -> j-n for -1 ;)
– ovs
Jan 14 at 12:31
• @ovs Ugh, I can't believe I missed that...
– Neil
Jan 14 at 14:35

Jelly,  56  51 bytes

+1 to cater for strict IO (two distinct outputs rather than truthy vs falsey being allowed)

“jtklmnpsw”,ØẹŒpṖṖ¬3,8¦p”n;ƊṗⱮLẎF€⁾mnyw⁾nnƲÐḟḊ€;$e@  A (very inefficiant) monadic Link that yields 0 when the input string is not a Toki Pona word and 1 when it is. (Don't) Try it online! (it's so inefficient it'll only complete for words of length three or less!) ...but here is a test-suite that has all tests except the four syllable pankulato that (a) limits to three base-syllables, rather than that of the number of characters in the input string and (b) only calls the word-generating code once for all (hence the e@ has been moved out to the footer). How? We construct a list containing ALL valid Toki Pona words constructed from at most length(input) syllables and check if the input is in there. Yep that's soooo nasty, but without easy regex access I imagine it's the golfiest way. “jtklmnpsw”,ØẹŒpṖṖ¬3,8¦p”n;Ɗṗ - (partial) Link: integer (from below!) “jtklmnpsw” - "jtklmnpsw" Øẹ - "aeiou" , - pair Œp - Catesian product ṖṖ - pop off "wu" and "wo" 3,8¦ - apply to indices 3 & 8 ("ji" & "ti"): ¬ - logical NOT (replace these with [0,0] (integers) Ɗ - last three links as a monad: ”n - 'n' p - Cartesian product (appends 'n' to each) ; - concatenate ṗ - Catiasian power (the integer) ...ⱮLẎF€⁾mnyw⁾nnƲÐḟḊ€;$e@ - (continued) Link: string, S
... L                     - length of S
...Ɱ                      - map across [1..length(S)] with:
...                       -   code above -> base-syllable combos of each length
Ẏ                    - tighten
F€                  - flatten each
Ðḟ       - filter discard those for which:
⁾mn               -     "mn"
y              -     translate (convert ms to ns)
⁾nn          -     "nn"
w             -     index of first occurrence (or zero)

Python 3, 97 88 86 bytes

lambda x:re.sub("((?!ji|wu|wo|ti|.*n[nm])(^|[j-npstw])[aeiou]n?)*$","",x)>"" import re  Try it online! return False for valid word, True for invalid Thanks to @14m2 for -2 bytes How it works: • at each syllable, we chek for ji|wu|wo|ti and prevent any capture if it is present. We also chek for the presence of either nn or nm further in the word. • if it was absent, we capture the syllable (consonant + voyel (+ n)) • All the syllables captured are replaced by the empty string • We then check if the result is greater than the empty string (falsey) or equal to the empty string (thruthy) • 86 – l4m2 Jan 17 at 12:49 • took me a while to understand the modification ^^ Jan 18 at 8:52 C (gcc), 438 bytes #define R return int c(l){char a[]={'n','m','p','t','k','s','w','l','j'};for(int i=0;i<9;i++)if(l==a[i])R 1;R 0;} int v(l){R l==97||l==101||l==105||l==111||l==117?1:0;} int f(char* s){int i,a,b;for(i=0;*s!=0;s++,i++){a =*s;b=*(s+1);if(!(c(a)||v(a))||((a=='j'||a=='t')&&b=='i'||a=='w'&&(b=='u'||b=='o')||a=='n'&&(b=='n'||b=='m'))||(c(a)&&c(b)&&a!='n')||(v(a)&&v(b))) R 0;}if(i==1&&c(*(s-1))) R 0;if(*s==0&&v(*(s-2))&&*(s-1)!='n') R 0;R 1;}  Try it online! Explanations : #define R return // function to detect a consonant int c(l){char a[]={'n','m','p','t','k','s','w','l','j'};for(int i=0;i<9;i++)if(l==a[i])R 1;R 0;} // function to detect a vowel int v(l){R l==97||l==101||l==105||l==111||l==117?1:0;} int f(char* s){int i,a,b;for(i=0;*s!=0;s++,i++) { a =*s;b=*(s+1); if(!(c(a)||v(a))|| // detect if characters are allowed ((a=='j'||a=='t')&&b=='i'||a=='w'&&(b=='u'||b=='o')||a=='n'&&(b=='n'||b=='m'))|| // detect if sequences ji, wu, wo & ti are not used (c(a)&&c(b)&&a!='n')|| // detect if there are not 2 consecutives consonants (v(a)&&v(b))) // detect if there are not 2 consecutives vowels R 0; if(i==1&&c(*(s-1))) R 0; // detect if it a single letter word & a vowel if(*s==0&&v(*(s-2))&&*(s-1)!='n') R 0; // test if the last character is not a consonant except 'n' R 1; } $$$$  • 283 bytes 2 days ago Lexurgy, 195 bytes Lexurgy is a tool made for conlangers for applying sound changes, so this is perfect for this challenge! (and here I am bashing it into code golf) Outputs the original word if it's valid Toki Pona, and an empty string otherwise. Extremely slow version: Class c {m,n,p,t,k,s,w,l,j} Class v {a,e,i,o,u} a: {({j,t} i),(w {o,u}),({m,n} {m,n}),!@c&!@v}=> {(!n&@c @c),(@v @v)}=> * !@v&!n=>/_$
n=>/$_$
c propagate:
[]=>/{ _,_ }
d:
=>*


Much faster version, 199 bytes:

Class c {m,n,p,t,k,s,w,l,j}
Class v {a,e,i,o,u}
a:
{j,t} i=>
w {o,u}=>
{m,n} {m,n}=>
!n&@c @c=> *
@v @v=> *
!@v&!n=>/_ $n=>/$ _ $!@c&!@v=> c propagate: []=>/{ _,_ } d: =>*  Ungolfed: Class cons {m,n,p,t,k,s,w,l,j} Class vow {a,e,i,o,u} remove-forbidden: {j,t} i =>  # ji, ti w {o,u} =>  # wo, wu {m,n} {m,n} =>  # mn, mm, etc !n&@cons @cons =>  * # no consecutive consonants @vow @vow =>  * # no consecutive vowels !@vow&!n =>  / _$ # ending with a vowel or n
n =>  / $_$ # nothing of length 1
Then:
!@cons&!@vow =>  # convert any invalid character
Then propagate:
[] =>  / { _, _ } # spread the invalid
Then:
 => * # delete the invalid
`