Giter Site home page Giter Site logo

faylang / fay Goto Github PK

View Code? Open in Web Editor NEW
1.3K 1.3K 86.0 2.57 MB

A proper subset of Haskell that compiles to JavaScript

Home Page: https://github.com/faylang/fay/wiki

License: BSD 3-Clause "New" or "Revised" License

Haskell 99.67% Shell 0.10% ReScript 0.23%

fay's People

Contributors

ajnsit avatar bergmark avatar bmillwood avatar chrisdone avatar commandodev avatar dag avatar henrylaxen avatar ian-ross avatar johnpmayer avatar jorpic avatar junjihashimoto avatar maxigit avatar mgsloan avatar michalseweryn avatar mpickering avatar mwrshl avatar nanotech avatar osa1 avatar relrod avatar robinp avatar ryachza avatar ryskajakub avatar scitesy avatar sebastiaanvisser avatar sixohsix avatar snoyberg avatar soenkehahn avatar stepcut avatar swamp-agr avatar techtangents avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fay's Issues

Add Guard Support

Guard support:

g x | x ==1 = 0
      | otherwise = 1

Seems to be missing, fay gives the error:

fay: UnsupportedRhs (GuardedRhss [GuardedRhs (SrcLoc {srcFilename = ".hs", srcLine = 3, srcColumn = 5}) [Qualifier (InfixApp (Var (UnQual (Ident "x"))) (QVarOp (UnQual (Symbol "=="))) (Lit (Int 1)))](Lit %28Int 0%29),GuardedRhs (SrcLoc {srcFilename = ".hs", srcLine = 4, srcColumn = 5}) [Qualifier (Var (UnQual (Ident "otherwise")))](Lit %28Int 1%29)])

This is with GHC 7.4.2 on a x86_64 Linux system.

Support infix notation for functions and constructors

data R = R Int Int
f = 1 `R` 2
  => UnsupportedOperator (QConOp (UnQual (Ident "R")))

f (1 `R` 2) = 1
  => UnsupportedPattern (PInfixApp (PLit (Int 1)) (UnQual (Ident "R")) (PLit (Int 2)))

a x y = x + y
f = 1 `a` 2
  => UnsupportedOperator (QVarOp (UnQual (Ident "a")))

data X = Int :-> Int
  => UnsupportedDeclaration (DataDecl (SrcLoc {...}) DataType [] (Ident "X") [] [QualConDecl (SrcLoc {...}) [] [] (InfixConDecl (UnBangedTy (TyCon (UnQual (Ident "Int")))) (Symbol ":->") (UnBangedTy (TyCon (UnQual (Ident "Int")))))] [])

f (1 :-> 2) = 1
  => UnsupportedPattern (PInfixApp (PLit (Int 1)) (UnQual (Symbol ":->")) (PLit (Int 2)))

Print help text when running 'fay' w/ no args

Running 'fay' with no arguments produces no output. That means it's impossible to determine how to use it without visiting the website.

Some documentation should be displayed when 'fay' is run with no files or with a '-help' argument.

No fromInteger support

I see references to fromInteger in Prelude, which if I understood correctly is for changing types for GHC. However there is no handling for them on the javascript-side. I haven't tested, but I assume fromRational suffers from the same problem.

{-# LANGUAGE NoImplicitPrelude #-}
module Test where

import Language.Fay.Prelude
import Language.Fay.FFI

print :: Foreign a => a -> Fay ()
print = foreignFay "console.log" ""

main :: Fay ()
main = print $ show $ fromInteger 5

ReferenceError: fromInteger is not defined

Trying out alert example

Hey,

I was trying the alert example the examples subdirectory. I did:

ghc alert.hs
[1 of 1] Compiling Main             ( alert.hs, alert.o )

alert.hs:8:9: Not in scope: type constructor or class `IO'

fay alert.hs

Which gives an alert.js. But if I add it to an html page and run it in chromium, I do not get anything (no alert window).
I am on ubuntu 12.04 64bit.

Enumeration of objects

If there a way I can produce code like this:

var k, v;
for (k in obj) {
  v = obj[k];
  f(v);
}

I'm trying to whip up something that I can use to semi auto generate FFI bindings and I thought it would be fun to dog food.

apostrophes in variable names are not escaped in pattern matches

I modified tests/16.hs to look like this:

data Person = Person String String Int

p' = Person "Chris" "Done" 13

main = print (case p' of
                Person "Chris" "Done" 13 -> "Hello!")

print :: String -> Fay ()
print = ffi "console.log(%1)"

The generated 16.js gives the variable definition the correct name:

var p$39$ = new $(function() { ...

But in the pattern match:

...
return _(print)((function($_p'){if ((_($_p'))[0].name === "Person") {
...

There is a parse error.

Console example code does not run

[jpmayer@jpmayer-ASUS-fedora learn-fay]$ cat console.hs
{-# LANGUAGE NoImplicitPrelude #-}

module Console where

import Language.Fay.FFI
import Language.Fay.Prelude

main = print "Hello, World!"

-- | Print using console.log.
print :: String -> Fay ()
print = ffi "console.log(%1)"
[jpmayer@jpmayer-ASUS-fedora learn-fay]$ fay --autorun console.hs
[jpmayer@jpmayer-ASUS-fedora learn-fay]$ nodejs --version
v0.6.18
[jpmayer@jpmayer-ASUS-fedora learn-fay]$ nodejs console.js
[jpmayer@jpmayer-ASUS-fedora learn-fay]$

Out of the box Haskell Platform (ghc 7.0.4) running on fedora 17.

Support list comprehensions

l = [i | i <- [1,2]]

produces

UnsupportedExpression (ListComp (Var (UnQual (Ident "i"))) [QualStmt (Generator (SrcLoc {srcFilename = "<unknown>.hs", srcLine = 9, srcColumn = 10}) (PVar (Ident "i")) (List [Lit (Int 1),Lit (Int 2)]))])

Use the strict equality operator.

Fay should print === instead of == for the JsEq node. == can perform type coercion in javascript. === is a strict comparison and performs no type conversions.

Fully support imported records

I pushed a change to allow record definitions to be imported and the records to be used, but the emitFayToJs and emitJsToFay functions update the state only when a declaration occurs, so these changes are dropped when a module is imported, and records can not be (de)serialized when they don't belong to the main module.

I see two possible solutions:

  • Merge CompileState fully to include everything from imported modules including the emit states (how will this behave if only parts of a module are imported?) Con is that the emit functions contain partial JS AST's and that doesn't seem pretty to store in the state...
  • Wait until all declarations are parsed, store all record information in the state and just iterate through that and create the emits then.

Make use of the XmlSyntax extension in HSE

Creating this ticket to track this, even if I end up adding it myself (still no guarantees!).

  • Make the Fay monad an instance of HSX.XMLGenerator.XMLGen so that GHC can type-check modules by using the trhsx preprocessor.
  • Translate the XML-related nodes in the HSE AST, either by rendering them as XML strings, or as calls to DOM methods.

Benefits:

  • XML syntax is ensured to be correct at compile-time
  • Can embed content via interpolation and ensure it is properly escaped (potentially avoiding XSS attacks)
  • Could have a dedicated type for XML improving type safety
  • Could reuse HSX templates from server code
  • More readable than strings; can be syntax highlighted in editors

More `where` support

#37 implemented some of it. But this still isn't supported:

f :: String -> String
f x = friends ++ family
  where friends = x
        family = " and family"

main = print (f "my friends")

causes

UnsupportedWhereInMatch (Match (SrcLoc {...}) (Ident "f") [PVar (Ident "x")] Nothing (UnGuardedRhs (InfixApp (Var (UnQual (Ident "friends"))) (QVarOp (UnQual (Symbol "++"))) (Var (UnQual (Ident "family"))))) (BDecls [PatBind (SrcLoc {...}) (PVar (Ident "friends")) Nothing (UnGuardedRhs (Var (UnQual (Ident "x")))) (BDecls []),PatBind (SrcLoc {...}) (PVar (Ident "family")) Nothing (UnGuardedRhs (Lit (String " and family"))) (BDecls [])]))

@sixohsix would you be interested in helping out some more? :)

Add unserializing for callbacks

I'll just include the relevant excerpt of our email exchange here:

On an unrelated note, I was trying to write the variant of addClass that takes a callback function but couldn't get it to work. My naive attempt was this:

addClassWith :: (Double -> String -> Fay String) -> JQuery -> Fay JQuery
addClassWith = ffi "%2.addClass(_(%1))" FayNone

Ah, good point. It should be merely %2.addClass(%1). That works for me, but the string is not serialized into a Fay string, so I need to add support to unserialize the arguments back to Fay from the callback. For now what you can do is:

addClassWith :: (Double -> String -> Fay String) -> JQuery -> Fay JQuery
addClassWith = ffi "%2.addClass(function(x,i){ return %1(x,Fay$$list(i)); })" FayNone

Until I've added unserializing for callbacks. Can you make a ticket for it on Github?

Support record initialization with field names, and record updates

data R = R { x :: Integer, y :: String }

r1 :: R
r1 = R { x = 1, y = "a" }
=>  UnsupportedExpression (RecConstr (UnQual (Ident "R")) [FieldUpdate (UnQual (Ident "x")) (Lit (Int 1)),FieldUpdate (UnQual (Ident "y")) (Lit (String "a"))])

r2 :: R
r2 = R 2 "y"
r3 :: R
r3 = r2 { x = 3 }
=> UnsupportedExpression (RecUpdate (Var (UnQual (Ident "r2"))) [FieldUpdate (UnQual (Ident "x")) (Lit (Int 3))])

Where syntax not supported, but no warning given

Where syntax is not supported, but no warning is given until you try to run the script.

{-# LANGUAGE NoImplicitPrelude #-}
module Test where

import Language.Fay.Prelude
import Language.Fay.FFI

fac :: Integer -> Integer
fac n = go 1 n
  where go acc 1 = acc
        go acc n = go (acc * n) (n - 1)

print :: Foreign a => a -> Fay ()
print = foreignFay "console.log" ""

main :: Fay ()
main = print $ show $ fac 5

ReferenceError: go is not defined

Records have to be defined before they are used

This compiles with GHC but fay errors out with "Record name was not found in stateRecords, should be impossible" since the record definition is parsed after the function. Also would be nice to get the name of the record in the error message.

{-# LANGUAGE NoImplicitPrelude #-}

module M where

f C = 1
data R = C

Edit: Is it possible to reorder the parse tree so records occur first, or will this just cause another similar issue later? Perhaps we can make an initial pass-through and collect all the type/declaration information we need?

Using modules

See code and output below - shouldn't the behavior be "Import syntax not supported"? Referring to line 177 in src/Language/Fay.hs

[jpmayer@jpmayer-ASUS-fedora learn-fay]$ cat makefile
all: Alert.js Write.js

Write.js: Write.hs
    ./fay Write.hs

Alert.js: Alert.hs Write.js
    ./fay --html-wrapper --autorun Alert.hs
[jpmayer@jpmayer-ASUS-fedora learn-fay]$ cat Write.hs 
{-# LANGUAGE NoImplicitPrelude #-}

module Write where

import Language.Fay.FFI

write :: String -> Fay ()
write = ffi "document.write(%1)"
[jpmayer@jpmayer-ASUS-fedora learn-fay]$ cat Alert.hs 
{-# LANGUAGE NoImplicitPrelude #-}

module Alert where

import           Language.Fay.FFI
import           Language.Fay.Prelude

import Write

main = do alert "Hello, World!"
          alert "Dwubwubwub"

-- | Alert using console.log.
alert :: String -> Fay ()
alert = ffi "window.alert(%1)"
[jpmayer@jpmayer-ASUS-fedora learn-fay]$ make
./fay --html-wrapper --autorun Alert.hs
fay: Could not find import: Write
make: *** [Alert.js] Error 1
[jpmayer@jpmayer-ASUS-fedora learn-fay]$ 

Question about "UnsupportedPattern"

When trying to convert the snippet:

g [] = 0
g [x] = x 
g [x,y] = max x y
g (x:xs@(y:ys)) = max (x + g ys) (g xs)

I get:

fay: UnsupportedPattern (PAsPat (Ident "xs") (PParen (PInfixApp (PVar (Ident "y")) (Special Cons) (PVar (Ident "ys")))))

Would someone be so kind and explain what I'm doing wrong / what I need to change?

Question: how do I use nub() in the resulting JavaScript environment?

I successfully generated a .js file with fay and I noticed that nub() is part of fay's "standard library". However I tried this in Chrome's console:

main.nub([2,1,1,3]).value().value()

expecting to receive back [2,1,3] which is what Haskell's nub would give.
Instead I'm getting the empty list back, and with a red warning sign to the left of it.
I'm clearly doing something wrong, but I don't know what.
Any help is appreciated.

Referential transparency failure with ffi

Consider the following code:

selectObject :: (Foreign a) => a -> Fay JQuery
selectObject = ffi "jQuery(%1)" FayNone

select       :: String -> Fay JQuery
select       = ffi "jQuery(%1)" FayNone

This performs as expected, allowing arbitrary objects to be made into jQuery objects, and allowing haskell strings to be used as selectors.

One would be tempted to refactor by declaring "select = selectObject". However, doing so prevents select from working.

The generated code for the original case:

var select = function($_a){return new $(function(){return new Fay$$Monad(Fay$$unserialize("",jQuery(Fay$$serialize(StringType,$_a))));});};

And the refactored case:

var select = new $(function(){return selectObject;});

Can not compile alert example

When I try to compile the alert example, I get this:

ghc alert.hs
[1 of 1] Compiling Alert            ( alert.hs, alert.o )

fay alert.hs
fay: FfiNeedsTypeSig (PatBind (SrcLoc {srcFilename = "<unknown>.hs", srcLine = 9, srcColumn = 1}) (PVar (Ident "main")) Nothing (UnGuardedRhs (App (Var (UnQual (Ident "alert"))) (Lit (String "Hello, World!")))) (BDecls []))

Support LANGUAGE pragmas after module declaration

module Test where
{-# LANGUAGE EmptyDataDecls    #-}
{-# LANGUAGE NoImplicitPrelude #-}

causes

ParseError
  (SrcLoc{srcFilename = "<unknown>.hs", srcLine = 4, srcColumn = 1})
  "Parse error: {-# LANGUAGE"

is this a problem with src-exts?

Converting JsPtr a -> a and a -> JsPtr a

Hey,

I want to share information between different callbacks (keyup, keydown, setInterval). In Javascript I would store this information in global variables.
So I need to create "globalVariables". My idea is to write a javascript function:

var allObjects = {}

function jsSaveGlobalObject(name, obj) {
     allObjects[name] = obj;
}

function jsLoadGlobalObject(name) {
     return allObjects[name];
}

But the signature would have an open type variable in haskell:

saveGlobalObject :: String -> a -> Fay ()

Or it needs to be of type (JsPtr a). I am guessing that is the right approach. But how do construct and deconstruct a JsPtr?
As far as a I can see the constructor is not accessible ...

Thanks!
Nathan

Emit JavaScript modules

This is a nifty project! I write JavaScript code professionally and I also really like Haskell. I'm digging this combination of the two.

I think that it would be helpful to map Haskell modules to JavaScript modules. That would improve interoperability with native JavaScript by allowing JavaScript code to reference and call into compiled Haskell code and vice-versa.

There is a relatively recent standard from CommonJS called the Asynchronous Module Definition (AMD). This spec is designed to support modules in browser code via a library that implements a function called define. AMD modules can also be used in server-side code with some shimming.

Using AMD, this contrived code:

module MyModule (a, b)

import Language.Fay.Prelude
import ModuleA (foo)
import ModuleB (bar)

a = foo
b = bar

could be transformed into JavaScript like this:

define("MyModule", [
    "Fay.Prelude",
    "ModuleA",
    "ModuleB"
], function(Fay, ModuleA, ModuleB) {
    var a = Fay.$(function() { return Fay._(ModuleA.foo); });
    var b = Fay.$(function() { return Fay._(ModuleB.bar); });
    return {
        a: a,
        b: b
    };
});

Putting Fay's Prelude into its own module is one way of sharing that code among multiple emitted modules without duplication. But it would also work just fine to stick with the current method of putting the Prelude code in a scoping function with the compiled code:

(function() {
var True = true;
var False = false;
/* ... */
define("MyModule", ["ModuleA", "ModuleB"], function(ModuleA, ModuleB) {
    var a = $(function() { return _(ModuleA.foo); });
    var b = $(function() { return _(ModuleB.bar); });
    return {
        a: a,
        b: b
    };
});
/* ... */
}());

An advantage to putting Fay's Prelude in its own module is that you could provide a command-line switch or something to leave it out of the emitted code in case someone prefers to load that code separately.

For best possible interoperability, I recommend including a check in the emitted code for the existence of a define function. If such a function exists you will want to use it. Otherwise you can include a minimal definition. This snippet maps a global define function to a locally-scoped alias called def, or supplies a new implementation for def:

var def = typeof define === "function" ? define : function() {
    /* minimal implementation */
};

There are a number of AMD implementations. For this case I suggest either tAMD or almond since both are intended to be minimal. Disclaimer: I wrote tAMD.

Support set/get with expression properties

Unless I'm mistaken there is no way to generate an expression of the form so[exp], o[exp] = exp, exp[exp] = exp. Has this not been handled or is there a reason for this that I didn't get? :)

The reason I'm asking is that I'm working on changing the representation of records and would like to be able to map a pattern match on a RecDecl to the correct properties, such as case r of Rec 1 -> ... => rec[rec._fields[0]] === 1.

We have JsSetProp JsName JsName JsExp and JsGetProp JsExp JsName which should in that case be changed to JsSetProp JsExp JsExp JsExp and JsGetProp JsExp JsExp, perhaps with helper functions for the simpler old cases.

implement foreignPropFay

It would be useful to be able to set properties of JS objects through a function like this, analogous to how foreignFay operates.

For instance:

setInnerHTML :: Foreign f => Fay f -> Fay f
setInnerHTML = `foreignFay "innerHTML" ""

main = do
  body <- getBody
  setInnerHTML body "foo"

would correspond to writing document.body.innerHTML = "foo";. There are a lot of more or less obscure browser cases where this might be useful, such as setting cancelBubble for IE<9.

As far as I can tell this isn't implemented yet. Also, is there a way of reading global properties such as when defining getBody?

Implement tail-call optimization for big speed win

How to optimize the following simple function:

sum 0 acc = acc
sum n acc = sum (n - 1) (acc + n)

As Fay outputs presently:

var sum = function ($_a) {
    return function ($_b) {
        return new $(function () {
            var acc = $_b;
            if (_($_a) === 0) {
                return acc;
            }
            var acc = $_b;
            var n = $_a;
            return _(_(sum)(_(n) - _(1)))(_(acc) + _(n));
        });
    };
};

Evaluation times:
500000500000
492ms
500000500000
457ms
500000500000
454ms
500000500000
467ms

What Fay could output by detecting that the function is tail-recursive:

var sum = function($_a) {
    return function ($_b) {
        return new $(function(){
            while (true) {
                var acc = $_b;
                if (_($_a) === 0) {
                    return acc;
                }
                var acc = $_b;
                var n = $_a;
                $_a = _(n) - _(1);
                $_b = _(acc) + _(n);
            }
        });
    };
};

Evaluation times:
500000500000
137ms
500000500000
137ms
500000500000
129ms
500000500000
124ms

What Fay would ideally output with strictness annotations in order to remove the forcing:

var sum = function($_a) {
    return function ($_b) {
        return new $(function(){
            $_a = _($_a);
            $_b = _($_b);
            while (true) {
                var acc = $_b;
                if ($_a === 0) {
                    return acc;
                }
                var acc = $_b;
                var n = $_a;
                $_a = n - 1;
                $_b = acc + n;
            }
        });
    };
};

Evaluation times:
500000500000
28ms
500000500000
20ms
500000500000
11ms
500000500000
11ms

This becomes basically native JavaScript speed.

What Fay could do without strictness annotations and simple inlining of the forcer (which is much easier than pervading strictness meta data):

var sum = function($_a) {
    return function ($_b) {
        return new $(function(){
            while (true) {
                var acc = $_b;
                if ($_a instanceof $?_($_a):$_a === 0) {
                    return acc;
                }
                var acc = $_b;
                var n = $_a;
                $_a = (n instanceof $?_(n):n) - 1;
                $_b = (acc instanceof $?_(acc):acc) + (n instanceof $?_(n):n);
            }
        });
    };
};

Evaluation times:
500000500000
66ms
500000500000
56ms
500000500000
56ms
500000500000
57ms

Collapse all the FFI types by using the type signature instead

Currently there is:

foreignFay :: Foreign a => String -> FayReturnType -> a
foreignPure :: Foreign a => String -> FayReturnType -> a
foreignValue :: Foreign a => String -> FayReturnType -> a
foreignProp :: Foreign a => String -> FayReturnType -> a
foreignPropFay :: Foreign a => String -> FayReturnType -> a
foreignMethodFay :: Foreign a => String -> FayReturnType -> a
foreignMethod :: Foreign a => String -> FayReturnType -> a
foreignSetProp ::
  (Foreign object, Foreign value) =>
  String -> object -> value -> Fay ()

By using the type signature of an FFI declaration, we can determine enough information, to have only:

ffi :: Foreign a => String -> FayReturnType -> a

And allow to write arbitrary content:

%1.foo=%2

As described in UHC's JS backend paper. This gives us a fair bit of flexibility.

JSON Example

Hi,

Fay is looking great. I think it would be cool to include a way to show an example of JSON.

I wrote up a quick example here and included how easy it would be to interface with the Aeson library for data serialization.

https://gist.github.com/3422759

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.