anchor.diff:  -module('utf8').
   
   %% API exports
  --export([encode/1]).
  +-export([encode/1, decode/1]).
   
   %%====================================================================
   %% API functions
  
positive.code: 
  -type code_point() :: 0..16#10FFFF.
  
  -spec encode([code_point()]) -> binary.
  encode(List) ->
      iolist_to_binary([encode_char(CP) || CP <- List]).
  
  %%====================================================================
  %% Internal functions
  %%====================================================================
  
  % Encodes ASCII
  encode_char(CodePoint) when 16#0000 =< CodePoint, CodePoint =< 16#007F ->
      <<CodePoint>>;
  % Encode 11 bit characters.
  encode_char(CodePoint) when 16#0080 =< CodePoint, CodePoint =< 16#07FF ->
      <<0:5, Upper:5, Lower:6>> = <<CodePoint:16/big>>,
      <<
  
negative.code: error_on_encode_surrogate_code_points_test() ->
      ?assertThrow(surrogate_value_disallowed, utf8:encode([16#D87f])).
  
  %%%
  
  decode_ascii_test() ->
      ?assertEqual("Hello, world!", utf8:decode(<<"Hello, world!">>)).
  
  decode_latin1_test() ->
      % "Cześć"
      Greeting = <<"Cze", 16#C5, 16#9B, 16#C4, 16#87>>,
      ?assertEqual("Cze" ++ [$ś, $ć], utf8:decode(Greeting)).
  
  decode_hangul_test() ->
      % "한국어" -- the Korean language
      Greeting = <<237,149,156,234,181,173,236,150,180>>,
      ?assertEqual([$한, $국, $어], utf8:decode(Greeting)).
  
  decode_emoji_test() ->
      % "🆒🔞"
      CoolNoUnder18 = <<240,159,134,146, 240,159,148,158>>,
      ?assertEqual([$🆒, $🔞], utf8:decode(CoolNoUnder18)).
  
  decode_rejects_overlong_form_test() ->
      <<Zero:8>> = <<2#10:2, 0:6>>,
      TwoBytes = <<"hello", 2#110:3, 0:5, Zero>>,
      ?assertThrow(overlong_form, utf8:decode(TwoBytes)),
      ThreeBytes = <<"hello", 2#1110:4, 0:4, Zero, Zero>>,
      ?assertThrow(overlong_form, utf8:decode(ThreeBytes)),
      FourBytes = <<"hello", 2#11110:5, 0:3, Zero, Zero, Zero>>,
      ?assertThrow(overlong_form, utf8:decode(FourBytes)).
  
  -endif.
  