  Copyright ( c ) 2005 - 2006 , A2Z Development USA , Inc.   All Rights Reserved .
%%%
  The contents of this file are subject to the Erlang Public License ,
  Version 1.1 , ( the " License " ) ; you may not use this file except in
%%% compliance with the License. You should have received a copy of the
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at /.
%%% 
  Software distributed under the License is distributed on an " AS IS "
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%% 
  The Initial Developer of the Original Code is A2Z Development USA , Inc.
  All Rights Reserved .

%% This code was originally created for serializing/deserializing
  Erlang terms in JSON format . It was hacked to handle haXe
%% () serialization/deserialization.
%%
  Modified by   (  )

-module(haxe).
-export([encode/1, encode/2, decode_string/1, decode_string/2,
	 decode/1, decode/2, decode_next/1]).
-export([is_obj/1, obj_new/0, obj_fetch/2, obj_find/2, obj_is_key/2]).
-export([test/0]).
-export([obj_store/3, obj_from_list/1, obj_fold/3]).

-author("Jim Larson <>, Robert Wai-Chi Chu <>").
-author("Gaspar Chilingarov <>, Gurgen Tumanyan <>").
-autor("Yariv Sadan <>").
-vsn("1").

  This module translates haXe types into the following Erlang types :
%%%
	 haXe 			  Erlang
%%%	----			 ------
%%%	int, float		 number
	 string ( ascii , utf8 ) 	  string
%%%	array			 {array, ElementList}
%%%	true, false, null	 atoms 't', 'f', and 'n'
%%%     Number.NaN               atom 'nan'
%%%     Number.NEGATIVE_INFINITY atom 'neg_infinity'
%%%     Number.POSITIVE_INFINITY atom 'infinity'
%%%	object			 tagged proplist with string (or atom) keys
                               ( e.g. { struct , [ { foo , " bar " } , { baz , 4 } ] } )
%%%     class object             NOT SUPPORTED
%%%     enum                     NOT SUPPORTED
      reference                 { ref , Idx } , Idx - > int ( )
%%%     exception                {exception, Obj}, Obj is any of the haXe types above
%%%
  Classs objects and Enums are currently not supported
  because simulating such types in Erlang is quite cumbersome , plus
%%% anonymous objects should be sufficient for most RPC needs.
%%% (If you strongly believe otherwise, contact me at 
%%% and I will consider adding class/enum support in a future version).
%%%
%%% References are handled transparently by the encoding/decoding
%%% functions. The decoding functions automatically expand
%%% references, and the encoding functions automatically serialize
%%% a string as a reference if an equal string has already been
%%% serialized (note: the haXe serializer can reference strings,
  arrays and objects , but the Erlang encoder only references
  strings due to the lack of address comparison in Erlang ) .
%%%
%%% If you wish to avoid expensive string comparisons
%%% in the encoder, or to have references to non-string objects,
%%% you can define a reference explicitly as a
  { ref , Idx } tuple . However , this usage is confusing and
%%% error prone, so it's better to avoid it except in extreme
%%% cases.

-define(L(Obj), io:format("~s:~w: ~w\n", [?MODULE, ?LINE, Obj])).

encode({ref, _Idx} = Ref) -> encode_basic(Ref);


encode(Obj) -> encode(Obj, true).

  The second parameter indicates whether the cache should
%% be used. By default, it's turned on (this follows the default configuration
%% in haXe).
encode(Obj, true) ->
    {Result, _Cache} = encode(Obj, dict:new()),
    Result;
encode(Obj, false) ->
    {Result, _Cache} = encode(Obj, undefined),
    Result;
encode(L, Cache) when is_list(L) ->
    case is_string(L) of
	yes -> encode_string(L, $s, Cache);
	unicode -> encode_string(xmerl_ucs:to_utf8(L), $j, Cache);
	no -> exit({error, {haxe_encode, {not_string, L}}})
    end;
encode({array, Props}, Cache) -> encode_array(Props, Cache);
encode({struct, Props}, Cache) -> encode_object(Props, Cache);
encode({exception, E}, Cache) -> 
    {Result, Cache2} = encode(E, Cache),
    {[$x | Result], Cache2};
encode(Term, Cache) ->
    case encode_basic(Term) of
	{error, _Err} = Err ->
	    exit(Err);
	Result ->
	    {Result, Cache}
    end.

encode_basic(Term) ->
    case Term of
	true -> "t";
	false -> "f";
	null -> "n";
	undefined -> "n";
	{ref, Idx} -> [$r | integer_to_list(Idx)];
	nan -> "k";
	infinity -> "p";
	neg_infinity -> "m";
	0 -> "z";
	I when is_integer(I) -> [$i | integer_to_list(I)];
	F when is_float(F) -> [$d | io_lib:format("~g", [F])];
	_ -> {error, {bad_term, Term}}
    end.

    
%% Find a string in the list of previously encoded string
  end return { true , { ref , Idx } } , if the search succeeded ,
  where Idx is the value associated with the string .
%% Otherwise, return 'false'.
find_ref(_Str, undefined) ->
    false;
find_ref(Str, CacheDict) ->
    case dict:find(Str, CacheDict) of 
	error ->
	    false;
	{ok, Idx} ->
	    {true, Idx}
    end.

  Encode an Erlang string to haXe .
encode_string(Str, Cache) ->
    encode_string(Str, $s, Cache).

encode_string(Str, FirstChar, Cache) ->
    case find_ref(Str, Cache) of
	false ->
	    case encode_string2(Str, FirstChar) of
		{ok, Result} ->
		    NewCache =
			case Cache of
			    undefined ->
				undefined;
			    _ ->
				dict:store(Str, dict:size(Cache), Cache)
			end,
		    {Result, NewCache};
		Err ->
		    Err
	    end;
	{true, Idx} ->
	    {[$R | integer_to_list(Idx)], Cache}
    end.
encode_string2(S, FirstChar) ->
    encode_string2(S, FirstChar, []).
encode_string2([], FirstChar, Acc) ->
    Str = lists:reverse(Acc),
    Len = integer_to_list(
	    length(
	      case FirstChar of
		  $j ->
		      xmerl_ucs:from_utf8(Str);
		  _ ->
		      Str
	      end)),
    {ok, [FirstChar, Len, $: | Str]};
encode_string2([C | Cs], FirstChar, Acc) ->
    case C of
	$\\ -> encode_string2(Cs, FirstChar, [$\\, $\\ | Acc]);
	$\n -> encode_string2(Cs, FirstChar, [$n, $\\ | Acc]);
	$\r -> encode_string2(Cs, FirstChar, [$r, $\\ | Acc]);
        C when C =< 16#FFFF -> encode_string2(Cs, FirstChar, [C | Acc]);
        _ -> exit({error, {haxe_encode, {bad_char, C}}})
    end.

encode_object(Props, Cache) ->
    {Result, Cache2} = encode_object_rest(Props, Cache),
    {[$o | Result], Cache2}.
encode_object_rest(Props, Cache) ->
    {EncodedProps, Cache1} =
	lists:foldl(
	  fun({Key, Value}, {Acc, Cache2}) ->
		  {EncodedKey, Cache3} =
		      case Key of
			  L when is_list(L) -> encode_string(L, Cache2);
			  A when is_atom(A) -> encode_string(atom_to_list(A), Cache2);
			  _ -> exit({error, {haxe_encode, {bad_key, Key}}})
		      end,
		  {EncodedVal, Cache4} = encode(Value, Cache3),
		  case Acc of
		      [] -> {[[EncodedKey, EncodedVal]], Cache4};		  		      _  -> {[[EncodedKey, EncodedVal] | Acc], Cache4}
		  end
	  end,
	  
	  {[], Cache},
	  Props),

    Result = [lists:reverse(EncodedProps), $g],
    {Result, Cache1}.

encode_array(Props, Cache) ->
    {NullCount, Arr, Cache1} = lists:foldl(
	  fun
	      (Elem, {NullCount, Arr, Cache2}) when Elem == null;
						    Elem == undefined ->
		  {NullCount+1, Arr, Cache2};
	      (Elem, {0, Arr, Cache2}) ->
		  {Encoded, Cache3} = encode(Elem, Cache2),
		  {0, [Encoded | Arr], Cache3};
	      (Elem, {NullCount, Arr, Cache2}) ->
		  {Encoded, Cache3}  = encode(Elem, Cache2),
		  {0, [Encoded, encode_nulls(NullCount) | Arr], Cache3}
	  end,
	  {0, [$a], Cache}, Props),
    {lists:reverse([$h , encode_nulls(NullCount) | Arr]), Cache1}.

encode_nulls(0) -> [];
encode_nulls(1) -> [$n];
encode_nulls(Num) -> [$u | integer_to_list(Num)].

%%% SCANNING
%%%
%%% Scanning funs return either:
%%%    {done, Result, LeftOverChars}
%%% if a complete token is recognized, or
%%%    {more, Continuation}
%%% if more input is needed.
%%% Result is {ok, Term}, 'eof', or {error, Reason}.
  Here , the Continuation is a simple Erlang string .
%%%
%%% Currently, error handling is rather crude - errors are recognized
  by match failures .   EOF is handled only by number scanning , where
%%% it can delimit a number, and otherwise causes a match failure.
%%%

token([]) -> {more, []};
token(eof) -> {done, eof, []};

token([C | Rest]) ->
    case token_identifier(C) of
	int -> scan_int(Rest);
	float -> scan_float(Rest);
	string -> scan_string(Rest);
	utf8 -> scan_utf8(Rest);
	error -> {done, {error, invalid_token}, Rest};
	Token -> {done, {ok, Token}, Rest}
    end.

token_identifier(C) ->
    case C of
	$i -> int;
	$d -> float;
	$s -> string;
	$j -> utf8;
	$n -> null;
	$t -> true;
	$f -> false;   
	$z -> 0;
	$k -> nan;
	$p -> infinity;
	$m -> neg_infinity;
	$a -> array;
	$h -> array_end;
	$o -> obj;
	$c -> class;
	$g -> obj_end;
	$r -> ref;
	$R -> str_ref;
	$x -> exception;

	%% this token is only valid as an element
	%% of an array
	$u -> null_seq;
	_ -> error
    end.
	      
scan_utf8(Chars) ->
    scan_chars(Chars, true).

scan_string(Chars) ->
    scan_chars(Chars, false).

scan_chars(Chars, IsUtf8) ->
    case scan_int(Chars) of
	{done, {ok, _NumBytes}, []} ->
	    {more, Chars};
	{done, {ok, _NumBytes}, [C | _Rest]} when C /= $: ->
	    {done, {error, bad_char, C}, Chars};
	{done, {ok, NumBytes}, [_ | Rest]} when length(Rest) >= NumBytes ->
	    {Str, NewLength} =
		case IsUtf8 of
		    true ->
			NewStr = xmerl_ucs:to_utf8(lists:sublist(Rest, NumBytes)),
			{NewStr, length(NewStr)};
		    false ->
			{Rest, NumBytes}
		end,
	    scan_chars(Str, [], NewLength);
	{done, {ok, _NumBytes}, _Rest} ->
	    {more, Chars};
	Other ->
	    Other
    end.

scan_chars(eof, A, _NumLeft) ->
    {done, {error, premature_eof}, A};
scan_chars(Rest, A, 0) ->
    {done, {ok, lists:reverse(A)}, Rest};
scan_chars([$\\] = Chars, _A, 0) ->
    {done, {error, missing_escape_character}, Chars};

scan_chars([$\\, C | Rest], A, NumLeft) ->
    scan_chars(Rest, [esc_to_char(C) | A], NumLeft - 2);
scan_chars([C | Rest], A, NumLeft) ->
    scan_chars(Rest, [C | A], NumLeft - 1).

esc_to_char(C) ->
    case C of
	$n -> $\n;
	$r -> $\r;
	$\\ -> $\\
    end.

scan_float(Chars) ->
    scan_number(Chars, float).

scan_int(Chars) ->
    scan_number(Chars, int).

scan_number([], _Type) -> {more, []};
scan_number(eof, _Type) -> {done, {error, incomplete_number}, []};
scan_number([$-, $- | _Rest] = Input, _Type) -> {done, {error, invalid_number}, Input};
scan_number([$- | Ds] = Input, Type) ->
    case scan_number(Ds, Type) of
	{more, _Cont} -> {more, Input};
	{done, {ok, N}, CharList} -> {done, {ok, -1 * N}, CharList};
        {done, Other, Chars} -> {done, Other, Chars}
    end;
scan_number([D | Ds] = Input, Type) when D >= $0, D =< $9 ->
    scan_number(Ds, D - $0, Input, Type).

  Numbers do n't have a terminator , so stop at the first non - digit ,
%% and ask for more if we run out.

scan_number([], _Num, X, _Type) -> {more, X};
scan_number(eof, Num, _X, _Type) -> {done, {ok, Num}, eof};
scan_number([$.], _Num, X, float) -> {more, X};
scan_number([$., D | Ds], Num, X, float) when D >= $0, D =< $9 ->
    scan_fraction([D | Ds], Num, X);
scan_number([D | Ds], Num, X, Type) when Num > 0, D >= $0, D =< $9 ->
    % Note that nonzero numbers can't start with "0".
    scan_number(Ds, 10 * Num + (D - $0), X, Type);
scan_number([D | Ds], Num, X, float) when D == $E; D == $e ->
    scan_exponent_begin(Ds, float(Num), X);
scan_number([D | _] = Ds, Num, _X, _Type) when D < $0; D > $9 ->
    {done, {ok, Num}, Ds}.

scan_fraction(Ds, I, X) -> scan_fraction(Ds, [], I, X).
scan_fraction([], _Fs, _I, X) -> {more, X};
scan_fraction(eof, Fs, I, _X) ->
    R = I + list_to_float("0." ++ lists:reverse(Fs)),
    {done, {ok, R}, eof};
scan_fraction([D | Ds], Fs, I, X) when D >= $0, D =< $9 ->
    scan_fraction(Ds, [D | Fs], I, X);
scan_fraction([D | Ds], Fs, I, X) when D == $E; D == $e ->
    R = I + list_to_float("0." ++ lists:reverse(Fs)),
    scan_exponent_begin(Ds, R, X);
scan_fraction(Rest, Fs, I, _X) ->
    R = I + list_to_float("0." ++ lists:reverse(Fs)),
    {done, {ok, R}, Rest}.

scan_exponent_begin(Ds, R, X) ->
    scan_exponent_begin(Ds, [], R, X).
scan_exponent_begin([], _Es, _R, X) -> {more, X};
scan_exponent_begin(eof, _Es, _R, X) -> {done, {error, missing_exponent}, X};
scan_exponent_begin([D | Ds], Es, R, X) when D == $-;
                                             D == $+;
                                             D >= $0, D =< $9 ->
    scan_exponent(Ds, [D | Es], R, X). 

scan_exponent([], _Es, _R, X) -> {more, X};
scan_exponent(eof, Es, R, _X) ->
    X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
    {done, {ok, X}, eof};
scan_exponent([D | Ds], Es, R, X) when D >= $0, D =< $9 ->
    scan_exponent(Ds, [D | Es], R, X);
scan_exponent(Rest, Es, R, _X) ->
    X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
    {done, {ok, X}, Rest}.

%%% PARSING
%%%
%%% The decode function takes a char list as input, but
%%% interprets the end of the list as only an end to the available
%%% input, and returns a "continuation" requesting more input.
%%% When additional characters are available, they, and the
  continuation , are fed into decode/2 .   You can use the atom ' eof '
%%% as a character to signal a true end to the input stream, and
%%% possibly flush out an unfinished number.  The decode_string/1
%%% function appends 'eof' to its input and calls decode/1.
%%%
%%% Parsing and scanning errors are handled only by match failures.
%%% The external caller must take care to wrap the call in a "catch"
%%% or "try" if better error-handling is desired.  Eventually parse
%%% or scan errors will be returned explicitly with a description,
%%% and someday with line numbers too.
%%%
%%% The parsing code uses a continuation-passing style to allow
%%% for the parsing to suspend at any point and be resumed when
%%% more input is available.
%%% See 


  Return the first haXe value decoded from the input string .
  The string must contain at least one complete haXe value .
%%
  The second ( optional ) parameter indicates if caching should
%% be turned on. Caching is on by default, but it can be turned
%% off for performance and lower memory consumption depending
%% on the content of the messages.
decode_string(CharList) ->
    decode_string(CharList, true).
decode_string(CharList, UseCache) ->
    {done, V, _} = decode(CharList ++ [eof], UseCache),
    V.

%% Attempt to decode a haXe value from the input string
%% and continuation, using an empty initial continuation.
  Return { done , Result , { LeftoverChars , Cache } } if a value is recognized ,
%% or {more, Continuation} if more input characters are needed.
  The Result can be { ok , Value } , eof , or { error , Reason } .
  The Continuation is then fed as an argument to decode/2 when
%% more input is available.
%% Use the atom 'eof' instead of a char list to signal
%% a true end to the input, and may flush a final number.

decode(CharList) ->
    decode(CharList, true).

  Note : our Cache for decoding is the tuple
%% {ObjCache::list(), StringCache::dict()}
decode(CharList, true) ->
    decode2(CharList, {[], dict:new()});
decode(CharList, false) ->
    decode2(CharList, undefined).

decode2(CharList, Cache) ->
    decode3({{[], Cache}, fun first_cont_fun/2}, CharList).

decode3({{[],Cache}, Kv}, CharList) ->
    get_token({CharList, Cache}, Kv);
decode3({{OldChars, Cache}, Kv}, CharList) ->
    get_token({OldChars ++ CharList, Cache}, Kv).

decode_next({OldChars, Cache}) ->
    decode3({{[], Cache}, fun first_cont_fun/2}, OldChars).

first_cont_fun(eof, Cs) -> {done, eof, Cs};
first_cont_fun(T, Cs) ->
    parse_value(T, Cs,
		fun(V, C2) ->
			{done, {ok, V}, C2}
		end).

  Continuation Kt must accept ( TokenOrEof ,    Cache } )

get_token({Chars, Cache}, Kt) ->
    case token(Chars) of
	{done, {ok, T}, Rest} -> Kt(T, {Rest, Cache});
	{done, eof, Rest} -> Kt(eof, {Rest, Cache});
	{done, {error, Reason}, Rest} -> {done, {error, Reason}, {Rest, Cache}};
        {more, X} -> {more, {X, Kt}}
    end.

  Continuation Kv must accept ( Value , { Chars , Cache } )

parse_value(Token, C, Kv) ->
    parse_value(Token, C, Kv, false).
parse_value(Token, {Chars, Cache} = C, Kv, AcceptNullSeq)->
    case Token of
	eof -> {done, {error, premature_eof}, C};
	T when T == null; T == true; T == false; T == nan;
	       T == infinity; T == neg_infinity ->
	    Kv(T, C);
	obj -> parse_object(C, Kv);
	class -> parse_class(C, Kv);
	array -> parse_array(C, Kv);
	enum -> parse_enum(C, Kv);
	exception -> parse_exception(C, Kv);
	ref -> parse_ref(C, Kv);
	str_ref -> parse_ref(C, Kv, true);
	null_seq when AcceptNullSeq -> parse_null_seq(C, Kv);
	Str when is_list(Str) ->
	    Kv(Str,
	       {Chars,
		put_str(Str, Cache)});
	Num when is_number(Num) -> Kv(Num, C);
	_ -> {done, {error, syntax_error}, C}
    end.

parse_class(Chars, _Kv) ->
    {done, {error, class_objects_not_supported}, Chars}.

parse_object(Chars, Kv) ->
    parse_object(Chars, Kv, obj_new()).

parse_object(C, Kv, Obj) ->
    get_token(C,
	      fun(T, {Chars, Cache}) ->
		      case T of
			  obj_end ->
			      NewCache =
				  put_obj(Obj, Cache),
			      Kv(Obj, {Chars, NewCache});
			  _ ->
			      NewCache =
				  put_obj(placeholder, Cache),
			      parse_object_field(
				Obj, T,
				{Chars, NewCache}, Cache, Kv)
		      end
	      end).

put_obj(_xObj, undefined) ->
    undefined;
put_obj(Obj, {ObjCache, StrCache}) ->
    {[Obj | ObjCache], StrCache}.

put_str(_Str, undefined) ->
    undefined;
put_str(Str, {ObjCache, StrCache}) ->
    {ObjCache, dict:store(dict:size(StrCache), Str, StrCache)}.

parse_object_field(_Obj, eof, C, _OrigCache, _Kv) ->
    {done, {error, premature_eof}, C};

%% if the key is a reference, we deference it and continue
parse_object_field(Obj, RefType, C, OrigCache, Kv)
  when RefType == ref; RefType == str_ref ->
    parse_ref(C,
	      fun(Key, C1) ->
		      parse_object_val(Obj, Key, C1, OrigCache, Kv)
	      end,
	     RefType == str_ref);

%% if the key is a string, we put it in the cache and continue
parse_object_field(Obj, Field, {Chars, Cache}, OrigCache, Kv)
  when is_list(Field) ->
    NewCache = put_str(Field, Cache),
    parse_object_val(Obj, Field, {Chars, NewCache}, OrigCache, Kv).

parse_object_val(Obj, Field, {Chars, Cache}, OrigCache, Kv)
  when is_list(Field) ->
    get_token({Chars, Cache},
	      fun(T, C2) ->
		      parse_value
			(T, C2,
			 fun(Val, C3) ->
				 Obj2 = obj_store(Field, Val, Obj),
				 parse_object_next(Obj2, C3, OrigCache, Kv)
			 end)
	      end);
parse_object_val(_Obj, Field, C, _OrigCache, _Kv) ->
    {done, {error, {member_name_not_string, Field}}, C}.

parse_object_next({struct, Props} = Obj, C, OrigCache, Kv) ->
    get_token(C,
	      fun
		  (obj_end, {Chars, Cache}) ->
		      Obj1 = {struct, lists:reverse(Props)},
		      Cache1 = append_new_elems(
				    Cache, Obj1, OrigCache),
		      Kv(Obj1, {Chars, Cache1});
		  (eof, C1) ->
		      {done, {error, premature_eof}, C1};
		  (T, C1) ->		
		      parse_object_field(Obj, T, C1, OrigCache, Kv)
	      end).


append_new_elems(undefined, _Obj, _Cache) ->
    undefined;
append_new_elems({TempObjCache, NewStrCache}, Obj,
		 {ObjCache, _OrigStrCache}) ->
    ObjCache1 = [Obj | ObjCache],
    NumNewElts = length(TempObjCache) - length(ObjCache1),
    NewElts = lists:sublist(TempObjCache, NumNewElts),
    {NewElts ++ ObjCache1, NewStrCache}.


parse_array({Chars, Cache}, Kv) ->

    %% We need to put a temporary placeholder in the cache
    %% to comply with haXe's indexing scheme, which assumes
    %% the array is put in the cache *before* its members.
    %% When we finish parsing the array, we collect the new
    %% cache entries and put them in the old cache after
    %% first inserting the fully parsed array.
    %%
    %% If it sounds backwards, well, it is! :)

    parse_array([], {Chars, put_obj(placeholder, Cache)},
		Cache, Kv).

parse_array(Elems, C, OrigCache, Kv) ->
    get_token(C,
	      fun
		  (eof, C1) -> {done, {error, premature_eof}, C1};
		  (array_end, {Chars1, Cache1}) -> 
		      Arr = {array, lists:reverse(Elems)},
		      Cache2 = append_new_elems(
				 Cache1, Arr, OrigCache),
		      Kv(Arr, {Chars1, Cache2});
		  (T, C1) ->
		      parse_array_tok(Elems, T, C1, OrigCache, Kv) 
	      end).

parse_array_tok(Elems, T, Cont, OrigCache, Kv) ->
    parse_value(T, Cont,
		fun({null_seq, Nulls}, C1) ->
			parse_array(Nulls ++ Elems, C1, OrigCache, Kv);
		   (V, C1) ->
			parse_array([V | Elems], C1, OrigCache, Kv)
		end,
	       true).

parse_enum(Chars, _Kv) ->
    {done, {error, enums_not_supported}, Chars}.

%% it's safe to assume we'll never have to parse exceptions
%% on the server side, but this function is here for completeness
parse_exception(Cont, Kv) ->
    get_token(Cont,
	      fun(T, C1) ->
		      parse_value(T, C1,
				  fun(Val, C2) ->
					  Kv({exception, Val}, C2)
				  end)
	      end).
		      

  The next three functions help with storing references
%%% to deserialized objects for future lookup during decoding

parse_ref(Cont, Kv) ->
    parse_ref(Cont, Kv, false).

parse_ref({Chars, Cache}, Kv, IsStrRef) ->
    case scan_int(Chars) of
	{done, {ok, Idx}, Chars1} ->
	    parse_ref2(IsStrRef, {Chars1, Cache}, Idx, Kv);
	Other ->
	    Other
    end.

parse_ref2(_IsStrRef, {_Chars, undefined} = Cont, _Idx, _Kv) ->
    {done, {error, references_disabled}, Cont};

parse_ref2(false, {_Chars, {ObjCache, _StrCache}} = Cont, Idx, Kv) ->
    if Idx > length(ObjCache) ->
	    {done, {error, {ref_idx_out_of_bounds, Idx}}, Cont};
       true ->
	    Val = lists:nth(length(ObjCache) - Idx, ObjCache),
	    if
		Val == placeholder ->
		    {done, {error, {illegal_ref, Idx}}, Cont};
		true ->
		    Kv(Val, Cont)
	    end
    end;

parse_ref2(true, {_Chars, {_ObjCache, StrCache}} = Cont, Idx, Kv) ->
    Cond = Idx + 1 > dict:size(StrCache),
    if Cond ->
	    {done, {error, {str_ref_idx_out_of_bounds, Idx}}, Cont};
       true ->
	    {ok, Val} = dict:find(Idx, StrCache),
	    Kv(Val, Cont)
    end.
    


parse_null_seq({Chars, Cache}, Kv) ->
    case scan_int(Chars) of
	{done, {ok, Num}, C1} when Num > 0 ->
	    Kv({null_seq, lists:duplicate(Num, null)}, {C1, Cache});
	Other ->
	    Other
    end.

%%% OBJECTS
%%%
%%% We'll use tagged property lists as the internal representation
%%% of haXe objects.  Unordered lists perform worse than trees for
%%% lookup and modification of members, but we expect objects to be
%%% have only a few members.  Lists also print better.

is_obj(_) ->
    false.

%% create a simple haXe object
obj_new() ->
    {struct, []}.

%% Fetch an object member's value, expecting it to be in the object.
%% Return value, runtime error if no member found with that name.
obj_fetch(Key, {struct, Props}) ->
    case proplists:get_value(Key, Props) of
        undefined ->
            exit({struct_no_key, Key});
        Value ->
            Value
    end.

%% Fetch an object member's value, or indicate that there is no such member.
%% Return {ok, Value} or 'error'.

obj_find(Key, {struct, Props}) ->
    case proplists:get_value(Key, Props) of
        undefined ->
            error;
        Value ->
            {ok, Value}
    end.

obj_is_key(Key, {struct, Props}) ->
    proplists:is_defined(Key, Props).

%% Store a new member in an object.  Returns a new object.

obj_store(KeyStr, Value, {struct, Props}) ->
    Key = list_to_atom(KeyStr),
    NewProps = [{Key, Value} | proplists:delete(Key, Props)],
    {struct, NewProps}.

  Create an object from a list of Key / Value pairs .

obj_from_list(Props) ->
    {struct, {Props}}.

  Fold Fun across object , with initial accumulator Acc .
  Fun should take ( Value , Acc ) as arguments and return Acc .

obj_fold(Fun, Acc, {struct, Props}) ->
    lists:foldl(Fun, Acc, Props).

is_string([]) -> yes;
is_string(List) -> is_string(List, non_unicode).

is_string([C|Rest], non_unicode) when C >= 0, C =< 255 -> is_string(Rest, non_unicode);
is_string([C|Rest], _) when C =< 65000 -> is_string(Rest, unicode);
is_string([], non_unicode) -> yes;
is_string([], unicode) -> unicode;
is_string(_, _) -> no.


test() ->
    Tests = [
	     {1, "i1"},
	     {1.1, "d1.10000"},
	     {"foo", "s3:foo"},
	     %% todo test utf8
	     {null, "n"},
	     {true, "t"},
	     {false, "f"},
	     {0, "z"},
	     {nan, "k"},
	     {infinity, "p"},
	     {neg_infinity, "m"},
	     {{array, [1,2,3]}, "ai1i2i3h"},
	     {{array, [null]}, "anh"},
	     {{array, [null, null]}, "au2h"},
	     {{array, [3, 4, null, null, null, 5, null]}, "ai3i4u3i5nh"},
	     {{struct, [{foo, "bar"}, {baz, "boing"}]},
	      "os3:foos3:bars3:bazs5:boingg"},
	     {{array, ["foo", "bar", "foo", {struct, [{bar, "baz"}, {foo, 123}]}]}, "as3:foos3:bars3:fooos3:bars3:bazs3:fooi123gh"},
	     {{exception, "bad"}, "xs3:bad"}
	    ],

    {Passed, Failed} = run_tests(Tests, false),

    Tests1 = [
	      {{array, ["foo", "bar", "foo"]}, "as3:foos3:barR0h"},
	      {{struct, [{foo, "bar"}, {bar, "foo"}]}, "os3:foos3:barR1R0g"},
	      {{array, ["foo", "bar", "foo", {struct, [{bar, "baz"}, {foo, 123}]}]}, "as3:foos3:barR0oR1s3:bazR0i123gh"}
	     ],
    {Passed1, Failed1} = run_tests(Tests1, true),
    io:format("passed: ~w, failed: ~w\n",
	      [Passed + Passed1, Failed + Failed1]).

run_tests(Tests, EnableReferences) ->
    lists:foldl(
      fun({Term, Str}, Agg) ->
	      Encoded = lists:flatten(encode(Term,EnableReferences)),
	      {ok, Decoded} = decode_string(Str,EnableReferences),
	      Check = fun(Val1, Val2, {P, F}) ->
			      case Val1 == Val2 of
				  true -> {P + 1, F};
				  _ -> {P, F + 1}
			      end
		      end,
	      Agg1 = Check(Str, Encoded, Agg),
	      Agg2 = Check(Term, Decoded, Agg1),
	      io:format("~s == ~s\n~w\n~w == ~w\n~w\n\n", [Str, Encoded, Str == Encoded,
							   Term, Decoded, Term == Decoded]),
	      Agg2
      end,
      {0, 0}, Tests).


 compliance with the License. You should have received a copy of the
 Erlang Public License along with this software. If not, it can be
 retrieved via the world wide web at /.
 
 basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 the License for the specific language governing rights and limitations
 under the License.
 
 This code was originally created for serializing/deserializing
 () serialization/deserialization.


	----			 ------
	int, float		 number
	array			 {array, ElementList}
	true, false, null	 atoms 't', 'f', and 'n'
     Number.NaN               atom 'nan'
     Number.NEGATIVE_INFINITY atom 'neg_infinity'
     Number.POSITIVE_INFINITY atom 'infinity'
	object			 tagged proplist with string (or atom) keys
     class object             NOT SUPPORTED
     enum                     NOT SUPPORTED
     exception                {exception, Obj}, Obj is any of the haXe types above

 anonymous objects should be sufficient for most RPC needs.
 (If you strongly believe otherwise, contact me at 
 and I will consider adding class/enum support in a future version).

 References are handled transparently by the encoding/decoding
 functions. The decoding functions automatically expand
 references, and the encoding functions automatically serialize
 a string as a reference if an equal string has already been
 serialized (note: the haXe serializer can reference strings,

 If you wish to avoid expensive string comparisons
 in the encoder, or to have references to non-string objects,
 you can define a reference explicitly as a
 error prone, so it's better to avoid it except in extreme
 cases.
 be used. By default, it's turned on (this follows the default configuration
 in haXe).
 Find a string in the list of previously encoded string
 Otherwise, return 'false'.
 SCANNING

 Scanning funs return either:
    {done, Result, LeftOverChars}
 if a complete token is recognized, or
    {more, Continuation}
 if more input is needed.
 Result is {ok, Term}, 'eof', or {error, Reason}.

 Currently, error handling is rather crude - errors are recognized
 it can delimit a number, and otherwise causes a match failure.

 this token is only valid as an element
 of an array
 and ask for more if we run out.
 Note that nonzero numbers can't start with "0".
 PARSING

 The decode function takes a char list as input, but
 interprets the end of the list as only an end to the available
 input, and returns a "continuation" requesting more input.
 When additional characters are available, they, and the
 as a character to signal a true end to the input stream, and
 possibly flush out an unfinished number.  The decode_string/1
 function appends 'eof' to its input and calls decode/1.

 Parsing and scanning errors are handled only by match failures.
 The external caller must take care to wrap the call in a "catch"
 or "try" if better error-handling is desired.  Eventually parse
 or scan errors will be returned explicitly with a description,
 and someday with line numbers too.

 The parsing code uses a continuation-passing style to allow
 for the parsing to suspend at any point and be resumed when
 more input is available.
 See 

 be turned on. Caching is on by default, but it can be turned
 off for performance and lower memory consumption depending
 on the content of the messages.
 Attempt to decode a haXe value from the input string
 and continuation, using an empty initial continuation.
 or {more, Continuation} if more input characters are needed.
 more input is available.
 Use the atom 'eof' instead of a char list to signal
 a true end to the input, and may flush a final number.
 {ObjCache::list(), StringCache::dict()}
 if the key is a reference, we deference it and continue
 if the key is a string, we put it in the cache and continue
 We need to put a temporary placeholder in the cache
 to comply with haXe's indexing scheme, which assumes
 the array is put in the cache *before* its members.
 When we finish parsing the array, we collect the new
 cache entries and put them in the old cache after
 first inserting the fully parsed array.

 If it sounds backwards, well, it is! :)
 it's safe to assume we'll never have to parse exceptions
 on the server side, but this function is here for completeness
 to deserialized objects for future lookup during decoding
 OBJECTS

 We'll use tagged property lists as the internal representation
 of haXe objects.  Unordered lists perform worse than trees for
 lookup and modification of members, but we expect objects to be
 have only a few members.  Lists also print better.
 create a simple haXe object
 Fetch an object member's value, expecting it to be in the object.
 Return value, runtime error if no member found with that name.
 Fetch an object member's value, or indicate that there is no such member.
 Return {ok, Value} or 'error'.
 Store a new member in an object.  Returns a new object.
 todo test utf8
  Copyright ( c ) 2005 - 2006 , A2Z Development USA , Inc.   All Rights Reserved .
  The contents of this file are subject to the Erlang Public License ,
  Version 1.1 , ( the " License " ) ; you may not use this file except in
  Software distributed under the License is distributed on an " AS IS "
  The Initial Developer of the Original Code is A2Z Development USA , Inc.
  All Rights Reserved .

  Erlang terms in JSON format . It was hacked to handle haXe
  Modified by   (  )

-module(haxe).
-export([encode/1, encode/2, decode_string/1, decode_string/2,
	 decode/1, decode/2, decode_next/1]).
-export([is_obj/1, obj_new/0, obj_fetch/2, obj_find/2, obj_is_key/2]).
-export([test/0]).
-export([obj_store/3, obj_from_list/1, obj_fold/3]).

-author("Jim Larson <>, Robert Wai-Chi Chu <>").
-author("Gaspar Chilingarov <>, Gurgen Tumanyan <>").
-autor("Yariv Sadan <>").
-vsn("1").

  This module translates haXe types into the following Erlang types :
	 haXe 			  Erlang
	 string ( ascii , utf8 ) 	  string
                               ( e.g. { struct , [ { foo , " bar " } , { baz , 4 } ] } )
      reference                 { ref , Idx } , Idx - > int ( )
  Classs objects and Enums are currently not supported
  because simulating such types in Erlang is quite cumbersome , plus
  arrays and objects , but the Erlang encoder only references
  strings due to the lack of address comparison in Erlang ) .
  { ref , Idx } tuple . However , this usage is confusing and

-define(L(Obj), io:format("~s:~w: ~w\n", [?MODULE, ?LINE, Obj])).

encode({ref, _Idx} = Ref) -> encode_basic(Ref);


encode(Obj) -> encode(Obj, true).

  The second parameter indicates whether the cache should
encode(Obj, true) ->
    {Result, _Cache} = encode(Obj, dict:new()),
    Result;
encode(Obj, false) ->
    {Result, _Cache} = encode(Obj, undefined),
    Result;
encode(L, Cache) when is_list(L) ->
    case is_string(L) of
	yes -> encode_string(L, $s, Cache);
	unicode -> encode_string(xmerl_ucs:to_utf8(L), $j, Cache);
	no -> exit({error, {haxe_encode, {not_string, L}}})
    end;
encode({array, Props}, Cache) -> encode_array(Props, Cache);
encode({struct, Props}, Cache) -> encode_object(Props, Cache);
encode({exception, E}, Cache) -> 
    {Result, Cache2} = encode(E, Cache),
    {[$x | Result], Cache2};
encode(Term, Cache) ->
    case encode_basic(Term) of
	{error, _Err} = Err ->
	    exit(Err);
	Result ->
	    {Result, Cache}
    end.

encode_basic(Term) ->
    case Term of
	true -> "t";
	false -> "f";
	null -> "n";
	undefined -> "n";
	{ref, Idx} -> [$r | integer_to_list(Idx)];
	nan -> "k";
	infinity -> "p";
	neg_infinity -> "m";
	0 -> "z";
	I when is_integer(I) -> [$i | integer_to_list(I)];
	F when is_float(F) -> [$d | io_lib:format("~g", [F])];
	_ -> {error, {bad_term, Term}}
    end.

    
  end return { true , { ref , Idx } } , if the search succeeded ,
  where Idx is the value associated with the string .
find_ref(_Str, undefined) ->
    false;
find_ref(Str, CacheDict) ->
    case dict:find(Str, CacheDict) of 
	error ->
	    false;
	{ok, Idx} ->
	    {true, Idx}
    end.

  Encode an Erlang string to haXe .
encode_string(Str, Cache) ->
    encode_string(Str, $s, Cache).

encode_string(Str, FirstChar, Cache) ->
    case find_ref(Str, Cache) of
	false ->
	    case encode_string2(Str, FirstChar) of
		{ok, Result} ->
		    NewCache =
			case Cache of
			    undefined ->
				undefined;
			    _ ->
				dict:store(Str, dict:size(Cache), Cache)
			end,
		    {Result, NewCache};
		Err ->
		    Err
	    end;
	{true, Idx} ->
	    {[$R | integer_to_list(Idx)], Cache}
    end.
encode_string2(S, FirstChar) ->
    encode_string2(S, FirstChar, []).
encode_string2([], FirstChar, Acc) ->
    Str = lists:reverse(Acc),
    Len = integer_to_list(
	    length(
	      case FirstChar of
		  $j ->
		      xmerl_ucs:from_utf8(Str);
		  _ ->
		      Str
	      end)),
    {ok, [FirstChar, Len, $: | Str]};
encode_string2([C | Cs], FirstChar, Acc) ->
    case C of
	$\\ -> encode_string2(Cs, FirstChar, [$\\, $\\ | Acc]);
	$\n -> encode_string2(Cs, FirstChar, [$n, $\\ | Acc]);
	$\r -> encode_string2(Cs, FirstChar, [$r, $\\ | Acc]);
        C when C =< 16#FFFF -> encode_string2(Cs, FirstChar, [C | Acc]);
        _ -> exit({error, {haxe_encode, {bad_char, C}}})
    end.

encode_object(Props, Cache) ->
    {Result, Cache2} = encode_object_rest(Props, Cache),
    {[$o | Result], Cache2}.
encode_object_rest(Props, Cache) ->
    {EncodedProps, Cache1} =
	lists:foldl(
	  fun({Key, Value}, {Acc, Cache2}) ->
		  {EncodedKey, Cache3} =
		      case Key of
			  L when is_list(L) -> encode_string(L, Cache2);
			  A when is_atom(A) -> encode_string(atom_to_list(A), Cache2);
			  _ -> exit({error, {haxe_encode, {bad_key, Key}}})
		      end,
		  {EncodedVal, Cache4} = encode(Value, Cache3),
		  case Acc of
		      [] -> {[[EncodedKey, EncodedVal]], Cache4};		  		      _  -> {[[EncodedKey, EncodedVal] | Acc], Cache4}
		  end
	  end,
	  
	  {[], Cache},
	  Props),

    Result = [lists:reverse(EncodedProps), $g],
    {Result, Cache1}.

encode_array(Props, Cache) ->
    {NullCount, Arr, Cache1} = lists:foldl(
	  fun
	      (Elem, {NullCount, Arr, Cache2}) when Elem == null;
						    Elem == undefined ->
		  {NullCount+1, Arr, Cache2};
	      (Elem, {0, Arr, Cache2}) ->
		  {Encoded, Cache3} = encode(Elem, Cache2),
		  {0, [Encoded | Arr], Cache3};
	      (Elem, {NullCount, Arr, Cache2}) ->
		  {Encoded, Cache3}  = encode(Elem, Cache2),
		  {0, [Encoded, encode_nulls(NullCount) | Arr], Cache3}
	  end,
	  {0, [$a], Cache}, Props),
    {lists:reverse([$h , encode_nulls(NullCount) | Arr]), Cache1}.

encode_nulls(0) -> [];
encode_nulls(1) -> [$n];
encode_nulls(Num) -> [$u | integer_to_list(Num)].

  Here , the Continuation is a simple Erlang string .
  by match failures .   EOF is handled only by number scanning , where

token([]) -> {more, []};
token(eof) -> {done, eof, []};

token([C | Rest]) ->
    case token_identifier(C) of
	int -> scan_int(Rest);
	float -> scan_float(Rest);
	string -> scan_string(Rest);
	utf8 -> scan_utf8(Rest);
	error -> {done, {error, invalid_token}, Rest};
	Token -> {done, {ok, Token}, Rest}
    end.

token_identifier(C) ->
    case C of
	$i -> int;
	$d -> float;
	$s -> string;
	$j -> utf8;
	$n -> null;
	$t -> true;
	$f -> false;   
	$z -> 0;
	$k -> nan;
	$p -> infinity;
	$m -> neg_infinity;
	$a -> array;
	$h -> array_end;
	$o -> obj;
	$c -> class;
	$g -> obj_end;
	$r -> ref;
	$R -> str_ref;
	$x -> exception;

	$u -> null_seq;
	_ -> error
    end.
	      
scan_utf8(Chars) ->
    scan_chars(Chars, true).

scan_string(Chars) ->
    scan_chars(Chars, false).

scan_chars(Chars, IsUtf8) ->
    case scan_int(Chars) of
	{done, {ok, _NumBytes}, []} ->
	    {more, Chars};
	{done, {ok, _NumBytes}, [C | _Rest]} when C /= $: ->
	    {done, {error, bad_char, C}, Chars};
	{done, {ok, NumBytes}, [_ | Rest]} when length(Rest) >= NumBytes ->
	    {Str, NewLength} =
		case IsUtf8 of
		    true ->
			NewStr = xmerl_ucs:to_utf8(lists:sublist(Rest, NumBytes)),
			{NewStr, length(NewStr)};
		    false ->
			{Rest, NumBytes}
		end,
	    scan_chars(Str, [], NewLength);
	{done, {ok, _NumBytes}, _Rest} ->
	    {more, Chars};
	Other ->
	    Other
    end.

scan_chars(eof, A, _NumLeft) ->
    {done, {error, premature_eof}, A};
scan_chars(Rest, A, 0) ->
    {done, {ok, lists:reverse(A)}, Rest};
scan_chars([$\\] = Chars, _A, 0) ->
    {done, {error, missing_escape_character}, Chars};

scan_chars([$\\, C | Rest], A, NumLeft) ->
    scan_chars(Rest, [esc_to_char(C) | A], NumLeft - 2);
scan_chars([C | Rest], A, NumLeft) ->
    scan_chars(Rest, [C | A], NumLeft - 1).

esc_to_char(C) ->
    case C of
	$n -> $\n;
	$r -> $\r;
	$\\ -> $\\
    end.

scan_float(Chars) ->
    scan_number(Chars, float).

scan_int(Chars) ->
    scan_number(Chars, int).

scan_number([], _Type) -> {more, []};
scan_number(eof, _Type) -> {done, {error, incomplete_number}, []};
scan_number([$-, $- | _Rest] = Input, _Type) -> {done, {error, invalid_number}, Input};
scan_number([$- | Ds] = Input, Type) ->
    case scan_number(Ds, Type) of
	{more, _Cont} -> {more, Input};
	{done, {ok, N}, CharList} -> {done, {ok, -1 * N}, CharList};
        {done, Other, Chars} -> {done, Other, Chars}
    end;
scan_number([D | Ds] = Input, Type) when D >= $0, D =< $9 ->
    scan_number(Ds, D - $0, Input, Type).

  Numbers do n't have a terminator , so stop at the first non - digit ,

scan_number([], _Num, X, _Type) -> {more, X};
scan_number(eof, Num, _X, _Type) -> {done, {ok, Num}, eof};
scan_number([$.], _Num, X, float) -> {more, X};
scan_number([$., D | Ds], Num, X, float) when D >= $0, D =< $9 ->
    scan_fraction([D | Ds], Num, X);
scan_number([D | Ds], Num, X, Type) when Num > 0, D >= $0, D =< $9 ->
    scan_number(Ds, 10 * Num + (D - $0), X, Type);
scan_number([D | Ds], Num, X, float) when D == $E; D == $e ->
    scan_exponent_begin(Ds, float(Num), X);
scan_number([D | _] = Ds, Num, _X, _Type) when D < $0; D > $9 ->
    {done, {ok, Num}, Ds}.

scan_fraction(Ds, I, X) -> scan_fraction(Ds, [], I, X).
scan_fraction([], _Fs, _I, X) -> {more, X};
scan_fraction(eof, Fs, I, _X) ->
    R = I + list_to_float("0." ++ lists:reverse(Fs)),
    {done, {ok, R}, eof};
scan_fraction([D | Ds], Fs, I, X) when D >= $0, D =< $9 ->
    scan_fraction(Ds, [D | Fs], I, X);
scan_fraction([D | Ds], Fs, I, X) when D == $E; D == $e ->
    R = I + list_to_float("0." ++ lists:reverse(Fs)),
    scan_exponent_begin(Ds, R, X);
scan_fraction(Rest, Fs, I, _X) ->
    R = I + list_to_float("0." ++ lists:reverse(Fs)),
    {done, {ok, R}, Rest}.

scan_exponent_begin(Ds, R, X) ->
    scan_exponent_begin(Ds, [], R, X).
scan_exponent_begin([], _Es, _R, X) -> {more, X};
scan_exponent_begin(eof, _Es, _R, X) -> {done, {error, missing_exponent}, X};
scan_exponent_begin([D | Ds], Es, R, X) when D == $-;
                                             D == $+;
                                             D >= $0, D =< $9 ->
    scan_exponent(Ds, [D | Es], R, X). 

scan_exponent([], _Es, _R, X) -> {more, X};
scan_exponent(eof, Es, R, _X) ->
    X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
    {done, {ok, X}, eof};
scan_exponent([D | Ds], Es, R, X) when D >= $0, D =< $9 ->
    scan_exponent(Ds, [D | Es], R, X);
scan_exponent(Rest, Es, R, _X) ->
    X = R * math:pow(10, list_to_integer(lists:reverse(Es))),
    {done, {ok, X}, Rest}.

  continuation , are fed into decode/2 .   You can use the atom ' eof '


  Return the first haXe value decoded from the input string .
  The string must contain at least one complete haXe value .
  The second ( optional ) parameter indicates if caching should
decode_string(CharList) ->
    decode_string(CharList, true).
decode_string(CharList, UseCache) ->
    {done, V, _} = decode(CharList ++ [eof], UseCache),
    V.

  Return { done , Result , { LeftoverChars , Cache } } if a value is recognized ,
  The Result can be { ok , Value } , eof , or { error , Reason } .
  The Continuation is then fed as an argument to decode/2 when

decode(CharList) ->
    decode(CharList, true).

  Note : our Cache for decoding is the tuple
decode(CharList, true) ->
    decode2(CharList, {[], dict:new()});
decode(CharList, false) ->
    decode2(CharList, undefined).

decode2(CharList, Cache) ->
    decode3({{[], Cache}, fun first_cont_fun/2}, CharList).

decode3({{[],Cache}, Kv}, CharList) ->
    get_token({CharList, Cache}, Kv);
decode3({{OldChars, Cache}, Kv}, CharList) ->
    get_token({OldChars ++ CharList, Cache}, Kv).

decode_next({OldChars, Cache}) ->
    decode3({{[], Cache}, fun first_cont_fun/2}, OldChars).

first_cont_fun(eof, Cs) -> {done, eof, Cs};
first_cont_fun(T, Cs) ->
    parse_value(T, Cs,
		fun(V, C2) ->
			{done, {ok, V}, C2}
		end).

  Continuation Kt must accept ( TokenOrEof ,    Cache } )

get_token({Chars, Cache}, Kt) ->
    case token(Chars) of
	{done, {ok, T}, Rest} -> Kt(T, {Rest, Cache});
	{done, eof, Rest} -> Kt(eof, {Rest, Cache});
	{done, {error, Reason}, Rest} -> {done, {error, Reason}, {Rest, Cache}};
        {more, X} -> {more, {X, Kt}}
    end.

  Continuation Kv must accept ( Value , { Chars , Cache } )

parse_value(Token, C, Kv) ->
    parse_value(Token, C, Kv, false).
parse_value(Token, {Chars, Cache} = C, Kv, AcceptNullSeq)->
    case Token of
	eof -> {done, {error, premature_eof}, C};
	T when T == null; T == true; T == false; T == nan;
	       T == infinity; T == neg_infinity ->
	    Kv(T, C);
	obj -> parse_object(C, Kv);
	class -> parse_class(C, Kv);
	array -> parse_array(C, Kv);
	enum -> parse_enum(C, Kv);
	exception -> parse_exception(C, Kv);
	ref -> parse_ref(C, Kv);
	str_ref -> parse_ref(C, Kv, true);
	null_seq when AcceptNullSeq -> parse_null_seq(C, Kv);
	Str when is_list(Str) ->
	    Kv(Str,
	       {Chars,
		put_str(Str, Cache)});
	Num when is_number(Num) -> Kv(Num, C);
	_ -> {done, {error, syntax_error}, C}
    end.

parse_class(Chars, _Kv) ->
    {done, {error, class_objects_not_supported}, Chars}.

parse_object(Chars, Kv) ->
    parse_object(Chars, Kv, obj_new()).

parse_object(C, Kv, Obj) ->
    get_token(C,
	      fun(T, {Chars, Cache}) ->
		      case T of
			  obj_end ->
			      NewCache =
				  put_obj(Obj, Cache),
			      Kv(Obj, {Chars, NewCache});
			  _ ->
			      NewCache =
				  put_obj(placeholder, Cache),
			      parse_object_field(
				Obj, T,
				{Chars, NewCache}, Cache, Kv)
		      end
	      end).

put_obj(_xObj, undefined) ->
    undefined;
put_obj(Obj, {ObjCache, StrCache}) ->
    {[Obj | ObjCache], StrCache}.

put_str(_Str, undefined) ->
    undefined;
put_str(Str, {ObjCache, StrCache}) ->
    {ObjCache, dict:store(dict:size(StrCache), Str, StrCache)}.

parse_object_field(_Obj, eof, C, _OrigCache, _Kv) ->
    {done, {error, premature_eof}, C};

parse_object_field(Obj, RefType, C, OrigCache, Kv)
  when RefType == ref; RefType == str_ref ->
    parse_ref(C,
	      fun(Key, C1) ->
		      parse_object_val(Obj, Key, C1, OrigCache, Kv)
	      end,
	     RefType == str_ref);

parse_object_field(Obj, Field, {Chars, Cache}, OrigCache, Kv)
  when is_list(Field) ->
    NewCache = put_str(Field, Cache),
    parse_object_val(Obj, Field, {Chars, NewCache}, OrigCache, Kv).

parse_object_val(Obj, Field, {Chars, Cache}, OrigCache, Kv)
  when is_list(Field) ->
    get_token({Chars, Cache},
	      fun(T, C2) ->
		      parse_value
			(T, C2,
			 fun(Val, C3) ->
				 Obj2 = obj_store(Field, Val, Obj),
				 parse_object_next(Obj2, C3, OrigCache, Kv)
			 end)
	      end);
parse_object_val(_Obj, Field, C, _OrigCache, _Kv) ->
    {done, {error, {member_name_not_string, Field}}, C}.

parse_object_next({struct, Props} = Obj, C, OrigCache, Kv) ->
    get_token(C,
	      fun
		  (obj_end, {Chars, Cache}) ->
		      Obj1 = {struct, lists:reverse(Props)},
		      Cache1 = append_new_elems(
				    Cache, Obj1, OrigCache),
		      Kv(Obj1, {Chars, Cache1});
		  (eof, C1) ->
		      {done, {error, premature_eof}, C1};
		  (T, C1) ->		
		      parse_object_field(Obj, T, C1, OrigCache, Kv)
	      end).


append_new_elems(undefined, _Obj, _Cache) ->
    undefined;
append_new_elems({TempObjCache, NewStrCache}, Obj,
		 {ObjCache, _OrigStrCache}) ->
    ObjCache1 = [Obj | ObjCache],
    NumNewElts = length(TempObjCache) - length(ObjCache1),
    NewElts = lists:sublist(TempObjCache, NumNewElts),
    {NewElts ++ ObjCache1, NewStrCache}.


parse_array({Chars, Cache}, Kv) ->


    parse_array([], {Chars, put_obj(placeholder, Cache)},
		Cache, Kv).

parse_array(Elems, C, OrigCache, Kv) ->
    get_token(C,
	      fun
		  (eof, C1) -> {done, {error, premature_eof}, C1};
		  (array_end, {Chars1, Cache1}) -> 
		      Arr = {array, lists:reverse(Elems)},
		      Cache2 = append_new_elems(
				 Cache1, Arr, OrigCache),
		      Kv(Arr, {Chars1, Cache2});
		  (T, C1) ->
		      parse_array_tok(Elems, T, C1, OrigCache, Kv) 
	      end).

parse_array_tok(Elems, T, Cont, OrigCache, Kv) ->
    parse_value(T, Cont,
		fun({null_seq, Nulls}, C1) ->
			parse_array(Nulls ++ Elems, C1, OrigCache, Kv);
		   (V, C1) ->
			parse_array([V | Elems], C1, OrigCache, Kv)
		end,
	       true).

parse_enum(Chars, _Kv) ->
    {done, {error, enums_not_supported}, Chars}.

parse_exception(Cont, Kv) ->
    get_token(Cont,
	      fun(T, C1) ->
		      parse_value(T, C1,
				  fun(Val, C2) ->
					  Kv({exception, Val}, C2)
				  end)
	      end).
		      

  The next three functions help with storing references

parse_ref(Cont, Kv) ->
    parse_ref(Cont, Kv, false).

parse_ref({Chars, Cache}, Kv, IsStrRef) ->
    case scan_int(Chars) of
	{done, {ok, Idx}, Chars1} ->
	    parse_ref2(IsStrRef, {Chars1, Cache}, Idx, Kv);
	Other ->
	    Other
    end.

parse_ref2(_IsStrRef, {_Chars, undefined} = Cont, _Idx, _Kv) ->
    {done, {error, references_disabled}, Cont};

parse_ref2(false, {_Chars, {ObjCache, _StrCache}} = Cont, Idx, Kv) ->
    if Idx > length(ObjCache) ->
	    {done, {error, {ref_idx_out_of_bounds, Idx}}, Cont};
       true ->
	    Val = lists:nth(length(ObjCache) - Idx, ObjCache),
	    if
		Val == placeholder ->
		    {done, {error, {illegal_ref, Idx}}, Cont};
		true ->
		    Kv(Val, Cont)
	    end
    end;

parse_ref2(true, {_Chars, {_ObjCache, StrCache}} = Cont, Idx, Kv) ->
    Cond = Idx + 1 > dict:size(StrCache),
    if Cond ->
	    {done, {error, {str_ref_idx_out_of_bounds, Idx}}, Cont};
       true ->
	    {ok, Val} = dict:find(Idx, StrCache),
	    Kv(Val, Cont)
    end.
    


parse_null_seq({Chars, Cache}, Kv) ->
    case scan_int(Chars) of
	{done, {ok, Num}, C1} when Num > 0 ->
	    Kv({null_seq, lists:duplicate(Num, null)}, {C1, Cache});
	Other ->
	    Other
    end.


is_obj(_) ->
    false.

obj_new() ->
    {struct, []}.

obj_fetch(Key, {struct, Props}) ->
    case proplists:get_value(Key, Props) of
        undefined ->
            exit({struct_no_key, Key});
        Value ->
            Value
    end.


obj_find(Key, {struct, Props}) ->
    case proplists:get_value(Key, Props) of
        undefined ->
            error;
        Value ->
            {ok, Value}
    end.

obj_is_key(Key, {struct, Props}) ->
    proplists:is_defined(Key, Props).


obj_store(KeyStr, Value, {struct, Props}) ->
    Key = list_to_atom(KeyStr),
    NewProps = [{Key, Value} | proplists:delete(Key, Props)],
    {struct, NewProps}.

  Create an object from a list of Key / Value pairs .

obj_from_list(Props) ->
    {struct, {Props}}.

  Fold Fun across object , with initial accumulator Acc .
  Fun should take ( Value , Acc ) as arguments and return Acc .

obj_fold(Fun, Acc, {struct, Props}) ->
    lists:foldl(Fun, Acc, Props).

is_string([]) -> yes;
is_string(List) -> is_string(List, non_unicode).

is_string([C|Rest], non_unicode) when C >= 0, C =< 255 -> is_string(Rest, non_unicode);
is_string([C|Rest], _) when C =< 65000 -> is_string(Rest, unicode);
is_string([], non_unicode) -> yes;
is_string([], unicode) -> unicode;
is_string(_, _) -> no.


test() ->
    Tests = [
	     {1, "i1"},
	     {1.1, "d1.10000"},
	     {"foo", "s3:foo"},
	     {null, "n"},
	     {true, "t"},
	     {false, "f"},
	     {0, "z"},
	     {nan, "k"},
	     {infinity, "p"},
	     {neg_infinity, "m"},
	     {{array, [1,2,3]}, "ai1i2i3h"},
	     {{array, [null]}, "anh"},
	     {{array, [null, null]}, "au2h"},
	     {{array, [3, 4, null, null, null, 5, null]}, "ai3i4u3i5nh"},
	     {{struct, [{foo, "bar"}, {baz, "boing"}]},
	      "os3:foos3:bars3:bazs5:boingg"},
	     {{array, ["foo", "bar", "foo", {struct, [{bar, "baz"}, {foo, 123}]}]}, "as3:foos3:bars3:fooos3:bars3:bazs3:fooi123gh"},
	     {{exception, "bad"}, "xs3:bad"}
	    ],

    {Passed, Failed} = run_tests(Tests, false),

    Tests1 = [
	      {{array, ["foo", "bar", "foo"]}, "as3:foos3:barR0h"},
	      {{struct, [{foo, "bar"}, {bar, "foo"}]}, "os3:foos3:barR1R0g"},
	      {{array, ["foo", "bar", "foo", {struct, [{bar, "baz"}, {foo, 123}]}]}, "as3:foos3:barR0oR1s3:bazR0i123gh"}
	     ],
    {Passed1, Failed1} = run_tests(Tests1, true),
    io:format("passed: ~w, failed: ~w\n",
	      [Passed + Passed1, Failed + Failed1]).

run_tests(Tests, EnableReferences) ->
    lists:foldl(
      fun({Term, Str}, Agg) ->
	      Encoded = lists:flatten(encode(Term,EnableReferences)),
	      {ok, Decoded} = decode_string(Str,EnableReferences),
	      Check = fun(Val1, Val2, {P, F}) ->
			      case Val1 == Val2 of
				  true -> {P + 1, F};
				  _ -> {P, F + 1}
			      end
		      end,
	      Agg1 = Check(Str, Encoded, Agg),
	      Agg2 = Check(Term, Decoded, Agg1),
	      io:format("~s == ~s\n~w\n~w == ~w\n~w\n\n", [Str, Encoded, Str == Encoded,
							   Term, Decoded, Term == Decoded]),
	      Agg2
      end,
      {0, 0}, Tests).
