├── .gitignore ├── .psci ├── .travis.yml ├── LICENSE.md ├── README.md ├── package.json ├── packages.dhall ├── spago.dhall ├── src ├── EasyFFI.js └── EasyFFI.purs └── test └── Main.purs /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | /node_modules/ 3 | /.pulp-cache/ 4 | /output/ 5 | /.psci* 6 | /src/.webpack.js 7 | /.psc-ide-port 8 | .purs-repl 9 | /.spago/ -------------------------------------------------------------------------------- /.psci: -------------------------------------------------------------------------------- 1 | :m src/Easy.purs 2 | :i Data.Foreign.Easy -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | sudo: required 4 | node_js: 6 5 | env: 6 | - PATH=$HOME/purescript:$PATH 7 | install: 8 | - TAG=$(wget -q -O - https://github.com/purescript/purescript/releases/latest --server-response --max-redirect 0 2>&1 | sed -n -e 's/.*Location:.*tag\///p') 9 | - wget -O $HOME/purescript.tar.gz https://github.com/purescript/purescript/releases/download/$TAG/linux64.tar.gz 10 | - tar -xvf $HOME/purescript.tar.gz -C $HOME/ 11 | - chmod a+x $HOME/purescript 12 | - npm install 13 | - npm run install 14 | script: 15 | - npm test 16 | after_success: 17 | - >- 18 | test $TRAVIS_TAG && 19 | echo $GITHUB_TOKEN | pulp login && 20 | echo y | pulp publish --no-push -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Tom Crockett 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-easy-ffi [![Build Status](https://travis-ci.org/pelotom/purescript-easy-ffi.svg?branch=master)](https://travis-ci.org/pelotom/purescript-easy-ffi) 2 | 3 | Most foreign imports in PureScript follow a familiar pattern: 4 | 5 | ```purescript 6 | -- In Module.purs 7 | foreign import foo :: Number -> Number -> Number -> Number 8 | ``` 9 | 10 | ```javascript 11 | // In Module.js 12 | export function foo (x) { 13 | return function (y) { 14 | return function (z) { 15 | return (x + y) * z; // <- the actually interesting part! 16 | }; 17 | }; 18 | }; 19 | ``` 20 | 21 | Yuck! Using Easy FFI you can scrap all that boilerplate and write the above as: 22 | 23 | ```haskell 24 | foo :: Number -> Number -> Number -> Number 25 | foo = unsafeForeignFunction ["x", "y", "z"] "(x + y) * z" 26 | ``` 27 | 28 | Easy! We can also define foreign functions returning monadic actions, by including an empty argument, e.g. 29 | 30 | ```haskell 31 | log :: forall r. String -> Eff (console :: Unit | r) Unit 32 | log = unsafeForeignProcedure ["string", ""] "console.log(string);" -- note the extra "" 33 | ``` 34 | 35 | which is equivalent to this: 36 | 37 | ```purescript 38 | foreign import log :: forall r. String -> Eff (console :: Unit | r) Unit 39 | ``` 40 | 41 | ```javascript 42 | export function log (string) { 43 | return function () { 44 | console.log(string); 45 | }; 46 | }; 47 | ``` 48 | 49 | The only difference between `unsafeForeignFunction` and `unsafeForeignProcedure` is that the former takes an expression as its second argument, and the latter a statement. 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "install": "spago install", 5 | "build": "spago build", 6 | "test": "spago test" 7 | }, 8 | "devDependencies": { 9 | "spago": "^0.20.8" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages.dhall: -------------------------------------------------------------------------------- 1 | {- 2 | Welcome to your new Dhall package-set! 3 | 4 | Below are instructions for how to edit this file for most use 5 | cases, so that you don't need to know Dhall to use it. 6 | 7 | ## Use Cases 8 | 9 | Most will want to do one or both of these options: 10 | 1. Override/Patch a package's dependency 11 | 2. Add a package not already in the default package set 12 | 13 | This file will continue to work whether you use one or both options. 14 | Instructions for each option are explained below. 15 | 16 | ### Overriding/Patching a package 17 | 18 | Purpose: 19 | - Change a package's dependency to a newer/older release than the 20 | default package set's release 21 | - Use your own modified version of some dependency that may 22 | include new API, changed API, removed API by 23 | using your custom git repo of the library rather than 24 | the package set's repo 25 | 26 | Syntax: 27 | where `entityName` is one of the following: 28 | - dependencies 29 | - repo 30 | - version 31 | ------------------------------- 32 | let upstream = -- 33 | in upstream 34 | with packageName.entityName = "new value" 35 | ------------------------------- 36 | 37 | Example: 38 | ------------------------------- 39 | let upstream = -- 40 | in upstream 41 | with halogen.version = "master" 42 | with halogen.repo = "https://example.com/path/to/git/repo.git" 43 | 44 | with halogen-vdom.version = "v4.0.0" 45 | with halogen-vdom.dependencies = [ "extra-dependency" ] # halogen-vdom.dependencies 46 | ------------------------------- 47 | 48 | ### Additions 49 | 50 | Purpose: 51 | - Add packages that aren't already included in the default package set 52 | 53 | Syntax: 54 | where `` is: 55 | - a tag (i.e. "v4.0.0") 56 | - a branch (i.e. "master") 57 | - commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977") 58 | ------------------------------- 59 | let upstream = -- 60 | in upstream 61 | with new-package-name = 62 | { dependencies = 63 | [ "dependency1" 64 | , "dependency2" 65 | ] 66 | , repo = 67 | "https://example.com/path/to/git/repo.git" 68 | , version = 69 | "" 70 | } 71 | ------------------------------- 72 | 73 | Example: 74 | ------------------------------- 75 | let upstream = -- 76 | in upstream 77 | with benchotron = 78 | { dependencies = 79 | [ "arrays" 80 | , "exists" 81 | , "profunctor" 82 | , "strings" 83 | , "quickcheck" 84 | , "lcg" 85 | , "transformers" 86 | , "foldable-traversable" 87 | , "exceptions" 88 | , "node-fs" 89 | , "node-buffer" 90 | , "node-readline" 91 | , "datetime" 92 | , "now" 93 | ] 94 | , repo = 95 | "https://github.com/hdgarrood/purescript-benchotron.git" 96 | , version = 97 | "v7.0.0" 98 | } 99 | ------------------------------- 100 | -} 101 | let upstream = 102 | https://github.com/purescript/package-sets/releases/download/psc-0.15.0-20220504/packages.dhall 103 | sha256:fd37736ecaa24491c907af6a6422156417f21fbf25763de19f65bd641e8340d3 104 | 105 | in upstream 106 | -------------------------------------------------------------------------------- /spago.dhall: -------------------------------------------------------------------------------- 1 | {- 2 | Welcome to a Spago project! 3 | You can edit this file as you like. 4 | 5 | Need help? See the following resources: 6 | - Spago documentation: https://github.com/purescript/spago 7 | - Dhall language tour: https://docs.dhall-lang.org/tutorials/Language-Tour.html 8 | 9 | When creating a new Spago project, you can use 10 | `spago init --no-comments` or `spago init -C` 11 | to generate this file without the comments in this block. 12 | -} 13 | { name = "easy-ffi" 14 | , dependencies = [ "arrays", "console", "effect", "prelude", "quickcheck" ] 15 | , packages = ./packages.dhall 16 | , sources = [ "src/**/*.purs", "test/**/*.purs" ] 17 | } 18 | -------------------------------------------------------------------------------- /src/EasyFFI.js: -------------------------------------------------------------------------------- 1 | export function unsafeForeignProcedure(args) { 2 | return function (stmt) { 3 | return Function(wrap(args.slice()))(); 4 | function wrap() { 5 | return !args.length 6 | ? stmt 7 | : 'return function (' + args.shift() + ') { ' + wrap() + ' };'; 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /src/EasyFFI.purs: -------------------------------------------------------------------------------- 1 | module Data.Foreign.EasyFFI 2 | ( unsafeForeignFunction 3 | , unsafeForeignProcedure 4 | ) where 5 | 6 | import Prelude 7 | ( ($) 8 | , (<>) 9 | ) 10 | 11 | foreign import unsafeForeignProcedure :: forall a. Array String -> String -> a 12 | 13 | unsafeForeignFunction :: forall a. Array String -> String -> a 14 | unsafeForeignFunction args expr = unsafeForeignProcedure args $ "return " <> expr <> ";" 15 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Effect (Effect) 4 | import Effect.Console (log) 5 | import Data.Array (sort) 6 | -- Import the library's module(s) 7 | import Data.Foreign.EasyFFI (unsafeForeignFunction) 8 | -- Import Test.QuickCheck, which supports property-based testing 9 | import Test.QuickCheck (quickCheck) 10 | import Prelude (Unit, const, discard, ($), (+), (==)) 11 | 12 | ffi :: forall a. Array String -> String -> a 13 | ffi = unsafeForeignFunction 14 | 15 | easyConst :: Int -> Int -> Int 16 | easyConst = ffi [ "n", "" ] "n" 17 | 18 | easyAdd :: Int -> Int -> Int 19 | easyAdd = ffi [ "x", "y" ] "x + y" 20 | 21 | easySort :: Array Int -> Array Int 22 | easySort = ffi [ "xs" ] "xs.slice().sort(function(a,b){return a-b;})" 23 | 24 | main :: Effect Unit 25 | main = do 26 | log "Constant" 27 | quickCheck $ \n m -> easyConst n m == const n m 28 | log "Addition" 29 | quickCheck $ \n m -> easyAdd n m == n + m 30 | log "Sorting" 31 | quickCheck $ \xs -> easySort xs == sort xs 32 | --------------------------------------------------------------------------------