implementation module Text.Unicode.Encodings.JS

import StdString, StdArray, StdOverloaded, StdInt, StdChar, StdBool, StdFunc, StdMisc
import Data.List

import Text.Unicode
from Text.Unicode.UChar import instance fromInt UChar, instance toInt UChar, instance fromChar UChar
import qualified Text.Unicode.UChar

:: JSLit = JSLit !String

toJSLiteral :: !UString -> String
toJSLiteral ustr = encodeString ustr

instance fromUnicode JSLit
where
	fromUnicode :: !UString -> JSLit
	fromUnicode ustr = JSLit (encodeString ustr)
			
instance toUnicode JSLit 
where
	toUnicode :: !JSLit -> UString
	toUnicode (JSLit str) = decodeString str

instance fromString JSLit
where
	fromString :: !{#Char} -> JSLit
	fromString str = JSLit str
	 
instance toString JSLit 
where
	toString :: !JSLit -> {#Char}
	toString (JSLit str) = str

toHex 0
	= ['\\0']
toHex i 
	| i < 256
		= ['\\x'] ++ ['0' \\ a <- [1..2-length letters]] ++ reverse (toHex` i)
	| i <= 0xFFFF
	= ['\\u'] ++ ['0' \\ a <- [1..4-length letters]] ++ reverse (toHex` i)
	= toHex 0xFFFF
where
	letters = reverse (toHex` i)
	
	toHex` 0 = []
	toHex` i = [hex.[i bitand 15]:toHex` (i >> 4)] 
	where
		hex = "0123456789ABCDEF" 

encodeString :: UString -> String
encodeString us = {c \\ c <- concatMap (convert o toInt) us}
where
	convert cc 
		| cc == fromChar '\n' = ['\\n']
		| cc == fromChar '\r' = ['\\r']	
		| cc == fromChar '\t' = ['\\t']
		| cc == fromChar '\f' = ['\\f']
		| cc == fromChar '\v' = ['\\v']
		| cc == fromChar '\b' = ['\\b']
		| cc == fromChar '\'' = ['\\\'']	
		| cc == fromChar '"'  = ['\\"']	
		| cc == fromChar '\\' = ['\\\\']	
		| not ('Text.Unicode.UChar'.isControl c) && 'Text.Unicode.UChar'.isAscii c = [fromInt cc]
				= toHex cc
	where
		c :: UChar
		c = fromInt cc 

decodeString :: String -> UString
decodeString str = decode (fromString str)
where
	decode :: [Char] -> UString
	decode [] = []
	decode ['\\':chars] = let (c,chars2) = scanBSChar chars in [c: decode chars2]
	decode [c   :chars] = [fromChar c: decode chars] 

	scanBSChar ['\\':chars] = (fromChar '\\' , chars)
	scanBSChar ['0' :chars] = (fromInt 0, chars)
	scanBSChar ['n' :chars] = (fromChar '\n', chars)
	scanBSChar ['r' :chars] = (fromChar '\r', chars)
	scanBSChar ['f' :chars] = (fromChar '\f', chars)
	scanBSChar ['t' :chars] = (fromChar '\t', chars)
	scanBSChar ['v' :chars] = (fromChar '\v', chars)
	scanBSChar ['b' :chars] = (fromChar '\b', chars)			
	scanBSChar ['"' :chars] = (fromChar '"'  , chars)
	scanBSChar ['\'':chars] = (fromChar '\'' , chars)
	scanBSChar ['x':h1:h2:chars] | isHexDigit h1 && isHexDigit h2
			= (fromInt (dti h1 << 4 + dti h2), chars)
	scanBSChar ['u':h1:h2:h3:h4:chars] | isHexDigit h1 && isHexDigit h2 && isHexDigit h3 && isHexDigit h4
			= (fromInt (dti h1 << 12 + dti h2 << 8 + dti h3 << 4 + dti h4), chars)			
	// unrecognized escape sequense (e.g. less than 2 or 4 digits)
	scanBSChar [c: chars] = (fromChar c, chars)
	scanBSChar [] = abort "final \\ in decodeString\n"

	dti c
	| c >= '0' && c <= '9' = toInt (c - '0')
	| c >= 'a' && c <= 'f' = 10 + toInt (c - 'a')
	| c >= 'A' && c <= 'F' = 10 + toInt (c - 'A')
	| otherwise            = abort "error in decodeString\n"
