Decode Arabic to Arabic Presentation Form Objective

Given an Arabic string consisting of ISO/IEC 8859-6 Arabic letters (U+0621–063A, 0640–064A), decode it to Arabic Presentation Forms-B (U+FE70–FEFF).

Behavior of Arabic letters

ISO/IEC 8859-6 Arabic letters change their displayed form to letters in Arabic Presentation Forms-B, which are categorized to isolated, initial, medial, or final letters.


If a letter is the first letter or follows another letter in isolated or final form:


If it is also the last letter or it lacks an initial form, it is in isolated form.
Otherwise, it is in initial form.

Otherwise, if the letter is not the last letter, its medial form is available, and the next letter is not ء(U+0621), it is in medial form.
Otherwise, the letter is in final form.
ـ (U+0640) is a special case. It doesn't have a presentation form, and always will be mapped to itself. It will be assumed to appear only where a letter in medial form could appear.


Note that Arabic letters are written right-to-left.

Mapping



Note that we don't care about ligatures (U+FEF5–FEFC).

Rule

If the string contains a letter not within U+0621–063A nor U+0640–064A, the challenge falls in don't care situation.

Example

The following word:

الله (U+0627 U+0644 U+0644 U+0647)


is decoded to:

ﺍﻟﻠﻪ (U+FE8D U+FEDF U+FEE0 U+FEEA)


The following word:

تكبر (U+062A U+0643 U+0628 U+0631)


is decoded to:

تكبر (U+FE97 U+FEDC U+FE92 U+FEAE)


The following word:

سلـام (U+0633 U+0644 U+0640 U+0627 U+0645)


is decoded to:

ﺳﻠـﺎﻡ (U+FEB3 U+FEE0 U+0640 U+FE8E U+FEE1)


The following word:

شيء (U+0634 U+064A U+0621)


is decoded to:

ﺷﻲﺀ (U+FEB7 U+FEF2 U+FE80)
JavaScript (ES6),  231 ... 148  146 bytes
I/O format: list of code points

a=&gt;a.map(t=(c,i)=&gt;c%32?(g=A=&gt;n^c%49?g([x=n%32&lt;26,k*x,x&amp;=454642&gt;&gt;n++*25%54%26,x].map(x=&gt;x&amp;&amp;++k)):A[t=t&gt;1^2*(a[i+1]&gt;1569)]||A[t^=2])(n=k=0)+65151:c)

Try it online!
a =&gt;                             // a[] = input array
a.map(t = (c, i) =&gt;              // for each code point c at position i in a[]:
  c % 32 ?                       //   if c is not 0x0640:
    ( g = A =&gt;                   //     g is a recursive function taking a list A[]
        n ^ c % 49 ?             //       if we haven't reached the correct character:
          g(                     //         do a recursive call:
            [                    //           build a list describing the existence of:
              x = n % 32 &lt; 26,   //           - isolated: if not in range 0x063B-0x0640
              k * x,             //           - final: same as isolated, except for the
                                 //             first entry which is undefined
              x &amp;=               //           - initial: we use a lookup bit-mask
                454642 &gt;&gt; n++    //             with the following hash formula:
                * 25 % 54 % 26,  //               ((n * 25) mod 54) mod 26
              x                  //           - medial: same as initial
            ].map(x =&gt; x &amp;&amp; ++k) //           turn it into a list of indices
          )                      //         end of recursive call
        :                        //       else:
          A[                     //         output the relevant entry from A[]:
            t =                  //           update t:
              t &gt; 1 ^ 2 * (      //             (previous was initial or medial) XOR
                a[i + 1] &gt; 1569  //             2 * (this is not the last letter 
              )                  //             and the next letter is not 0x0621)
          ] ||                   //         if A[t] is not defined:
            A[t ^= 2]            //           use A[t ^ 2] instead
    )(n = k = 0)                 //     initial call to g with n = k = 0
    + 65151                      //     add 0xFE7F
  :                              //   else:
    c                            //     output c unchanged
)                                // end of map()