ghcjs / ghcjs-base

base library for GHCJS for JavaScript interaction and marshalling, used by higher level libraries like JSC
MIT License
45 stars 67 forks source link

Converting `undefined` value to Text #8

Closed geraldus closed 9 years ago

geraldus commented 10 years ago

I faced some issue when tried to convert an undefined javascript value to Text with fromJSRef_fromJSString. Consider following code:

foreign import javascript safe
    "$r = (function(){\
    \    if (window.config) { return window.config[$1]; }\
    \    else { return undefined; }\
    \}())"
    js_get_config_of :: JSString -> IO (JSRef a)
configRefByName :: ToJSString a => a -> IO (JSRef b)
configRefByName = js_get_config_of . toJSString

This is quite simple code to read globally stored configuration, if there is no configuration, undefined value will be returned. Converting undefined value to String works, but a bit strange:

do
    undefRef <- configRefByName "not defined"
    str <- fromJSRef_fromJSString undefRef :: IO (Maybe String)
    print str

This prints Just "", though I supposed to see Nothing in this case. At least it works. If I change result type to IO (Maybe Text) I'm facing runtime exception:

uncaught exception in Haskell main thread: TypeError: Cannot read property 'length' of undefined

Obviously, marshall code does not check if value actually valid and just treats it like array of chars (or something), and tries to read length of it.

geraldus commented 9 years ago

In addition, fromJSRef x :: IO (Maybe Text) will return Nothing, when x is empty text, i.e "". This brings some inconveniences. For example, I tried to define FromJSRef instance for some algebraic data type, which have several Text parameters. It uses getPropMaybe in conjunction with fromJSRef to retrieve reference's properties and convert each of them to Maybe Text values. Though the property exists in the object and its value is not null or undefined, but "", fromJSRef returns Nothing, thus whole conversion fails.

geraldus commented 9 years ago

By the way, I need to recheck behaviour described in issue body, because some things might have been changes since September.

geraldus commented 9 years ago

Ok, I've written qiuck test example 1.

You can grab the code and test it yourself. Example output follows each test case wrapped by {--} comment.

First, there is test cases for described issue. I'm trying to use fromJSRef with jsNull value twice, once to get String 2, and once to get Text3; then fromJSRef_fromJSSrtring, 3 and 4 respectively:

print =<< (fromJSRef jsNull :: IO (Maybe String)) -- Just "", expected Nothing
print =<< (fromJSRef jsNull :: IO (Maybe Text))   -- Exception, expected Nothing
print =<< (fromJSRef_fromJSString jsNull :: IO (Maybe String)) -- Just "", expected Nothing
print =<< (fromJSRef_fromJSString jsNull :: IO (Maybe Text))   -- Exception, expected Nothing

After that, there are several test cases with existing object (see index.html):

test = { propNull: null,
         propUndef: undefined,
         propEmpty: "",
         propNormal: "sometext"
       };

I'm trying to get property by name and convert it to String and Text with fromJSRef function. I've done tests for:

@luite, please have a look!

geraldus commented 9 years ago

Current proposed workaround it to try convert JSRef a to JSString type. I'll close it for now and check later.

hamishmack commented 9 years ago

Please try out the new-marshal branch of ghcjs-base. Amongst other things, it includes

Here is an example.

import GHCJS.Types
import GHCJS.Marshal
import GHCJS.Marshal.Pure
import GHCJS.Foreign
import Data.Text

main = do
        print (pfromJSRef (ptoJSRef Nothing) :: Maybe String) -- Nothing
        print (pfromJSRef (ptoJSRef Nothing) :: Maybe Text)   -- Nothing
        noString <- toJSRef Nothing :: IO (JSRef (Maybe String))
        noText   <- toJSRef Nothing :: IO (JSRef (Maybe Text))
        print =<< (fromJSRef noString :: IO (Maybe (Maybe String))) -- Just Nothing
        print =<< (fromJSRef noText   :: IO (Maybe (Maybe Text))) -- Just Nothing
        print =<< (fromJSRefUnchecked noString :: IO (Maybe String)) -- Nothing
        print =<< (fromJSRefUnchecked noText   :: IO (Maybe Text)) -- Nothing
        print =<< (fromJSRef (castRef noString) :: IO (Maybe String)) -- Nothing
        print =<< (fromJSRef (castRef noText  ) :: IO (Maybe Text)) -- Nothing
geraldus commented 9 years ago

I'll have a look. Thank you so much!

geraldus commented 9 years ago

@hamishmack can you please help me? What should I do to use this new-marshal branch? I have local clones of ghcjs and ghcjs-prim repos. But ghcjs-base is being installed during boot, right?

hamishmack commented 9 years ago

Install ghcjs as normal, then cabal install --ghcjs --global --force-reinstall in the new-marshal branch of the ghcjs-base repo. There is no change in ghcjs or ghcjs-prim.

geraldus commented 9 years ago

Oh, I've understood! Will try later this day! Thank you, Hamish!

geraldus commented 9 years ago

Oh, looks like new-marshal branch is outdated a bit, I'm confused.

hamishmack commented 9 years ago

Updated.

geraldus commented 9 years ago

Amazing, very appreciate!