packages feed

haskelisp (empty) → 0.1.0.0

raw patch · 11 files changed

+1152/−0 lines, 11 filesdep +basedep +clockdep +containerssetup-changed

Dependencies added: base, clock, containers, formatting, logging-effect, mtl, protolude, text, turtle

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cbits/stab.c view
@@ -0,0 +1,129 @@+#include <HsFFI.h>+#include <string.h>+#include <emacs-module.h>+/* extern void __stginit_Lib(void); */++/* Declare mandatory GPL symbol.  */+/* Shoudn't declare here. Library user should declare. */+/* int plugin_is_GPL_compatible; */++void __attribute__ ((constructor)) my_load(void);+void __attribute__ ((destructor)) my_unload(void);++// https://www.vex.net/~trebla/haskell/so.xhtml 参照+void my_load(void) {+  static int argc = 1;+  static char *argv[] = { "haskelisp", 0}, **argv_ = argv;+  hs_init(&argc, &argv_);+}+void my_unload(void) {+  hs_exit();+}++/* wrappers */++emacs_env *_get_emacs_env_from_rt(struct emacs_runtime *ert) {+  return ert->get_environment(ert);+}++/* Function registration.  */++emacs_value _make_function(emacs_env *env,+                           ptrdiff_t min_arity,+                           ptrdiff_t max_arity,+                           emacs_value (*function) (emacs_env *env,+                                                    ptrdiff_t nargs,+                                                    emacs_value args[],+                                                    void *),+                           const char *documentation,+                           void *data) {+  return env->make_function(env,+                            min_arity,+                            max_arity,+                            function,+                            documentation,+                            data);+}++emacs_value _funcall(emacs_env *env,+                     emacs_value function,+                     ptrdiff_t nargs,+                     emacs_value args[]) {+  return env->funcall(env,+                      function,+                      nargs,+                      args);+}++emacs_value _intern(emacs_env *env, const char *symbol_name) {+  return env->intern(env, symbol_name);+}++/* Type conversion.  */++emacs_value _type_of(emacs_env *env, emacs_value value) {+  return env->type_of(env,value);+}++int _is_not_nil(emacs_env *env, emacs_value value) {+  return env->is_not_nil(env,value) == true ? 1 : 0;+}++int _eq(emacs_env *env, emacs_value a, emacs_value b) {+  return env->eq(env, a, b) == true ? 1 : 0;+}++intmax_t _extract_integer(emacs_env *env, emacs_value value) {+  return env->extract_integer(env, value);+}++emacs_value _make_integer(emacs_env *env, intmax_t value) {+  return env->make_integer(env, value);+}++/* double (*extract_float) (emacs_env *env, emacs_value value); */++/* emacs_value (*make_float) (emacs_env *env, double value); */++emacs_value _make_string(emacs_env *env,+                         const char *contents,+                         ptrdiff_t length) {+  return env->make_string(env,contents,length);+}++/* Memory management.  */++emacs_value _make_global_ref(emacs_env *env,+                             emacs_value any_reference) {+  return env->make_global_ref(env, any_reference);+}++/* Non-local exit handling.  */++enum emacs_funcall_exit _non_local_exit_check(emacs_env *env) {+  return env->non_local_exit_check(env);+}++void _non_local_exit_clear(emacs_env *env) {+  env->non_local_exit_clear(env);+}++enum emacs_funcall_exit _non_local_exit_get(emacs_env *env,+                                            emacs_value *non_local_exit_symbol_out,+                                            emacs_value *non_local_exit_data_out) {+  return env->non_local_exit_get(env,+                                 non_local_exit_symbol_out,+                                 non_local_exit_data_out);+}++void _non_local_exit_signal(emacs_env *env,+                            emacs_value non_local_exit_symbol,+                            emacs_value non_local_exit_data) {+  env->non_local_exit_signal(env, non_local_exit_symbol, non_local_exit_data);+}++void _non_local_exit_throw(emacs_env *env,+                           emacs_value tag,+                           emacs_value value) {+  env->non_local_exit_throw(env,tag,value);+}
+ haskelisp.cabal view
@@ -0,0 +1,65 @@+name:                haskelisp+version:             0.1.0.0+synopsis:            Initial project template from stack+description:         Please see README.md+homepage:            http://github.com/githubuser/haskelisp#readme+license:             GPL-3+license-file:        LICENSE+author:              Takenari Shinohara+maintainer:          takenari.shinohara@gmail.com+copyright:           2016 Takenari Shinohara+category:            Editor+build-type:          Simple+-- extra-source-files:+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Emacs+                     , Emacs.Type+                     , Emacs.Internal+                     , Emacs.Core+                     , Emacs.Variable+                     , Emacs.Function+                     , Emacs.Command+  build-depends:       base >= 4.7 && < 5+                     , protolude+                     , turtle+                     , containers+                     , mtl+                     , text+                     , logging-effect+                     , clock+                     , formatting+  default-language:    Haskell2010+  include-dirs:        cbits+  c-sources:           cbits/stab.c++-- executable edm.so+--   hs-source-dirs:      app+--   main-is:             Main.hs+--   -- ghc-options:         -threaded -rtsopts -with-rtsopts=-N+--   -- extra-libraries:     HSrts-ghc7.10.3+--   cc-options:          -fPIC+--   ghc-options:         -shared -dynamic -fPIC -lHSrts-ghc7.10.3+--   build-depends:       base+--                      , haskelisp+--                      , text+--                      , protolude+--                      , turtle+--                      , system-filepath+--                      , foldl+--   default-language:    Haskell2010++-- test-suite haskelisp-test+--   type:                exitcode-stdio-1.0+--   hs-source-dirs:      test+--   main-is:             Spec.hs+--   build-depends:       base+--                      , haskelisp+--   ghc-options:         -threaded -rtsopts -with-rtsopts=-N+--   default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/githubuser/haskelisp
+ src/Emacs.hs view
@@ -0,0 +1,6 @@+module Emacs (module Emacs.Core, module Emacs.Variable, module Emacs.Function, module Emacs.Command) where++import Emacs.Core+import Emacs.Variable+import Emacs.Function+import Emacs.Command
+ src/Emacs/Command.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}+module Emacs.Command+ ( setCommand'+ , defcommand+ ) where++import Emacs.Core+import Data.Text+import Control.Monad (void)++-- 依存型とか駆使すれば多分 interacvie form と関数の引数の型の整合性が+-- 取れていることが多分保証できる。++-- これは直接は使って欲くないため、' postfix を付けている。+-- EmacsValue を引数として取っているので function 以外のものを渡せてしまう+setCommand' :: Text -> InteractiveForm -> EmacsValue -> EmacsM ()+setCommand' fname form f = do+  fnameQ <- mkSymbol fname+  interactiveFormQ <- mkSymbol "interactive-form"+  void $ funcall2 "fset" fnameQ f+  void $ funcall3 "put"  fnameQ interactiveFormQ =<< evalString "'(interactive nil)"++-- TODO: interacitve-form の携帯によってarity は決まる?かな+defcommand+  :: Text+  -> Doc+  -> InteractiveForm+  -> Arity+  -> ([EmacsValue] -> EmacsM EmacsValue)+  -> EmacsM ()+defcommand fname (Doc doc) form (Arity arity) f =+  setCommand' fname form =<< mkFunction f arity arity doc
+ src/Emacs/Core.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DataKinds #-}++module Emacs.Core (+    module Emacs.Internal,+    defmodule,+    -- mk+    mkCons,+    -- funcall+    ToEmacsValue(..),+    funcall1, funcall2, funcall3,+    --+    car,+    cdr,+    --+    evalString,+    provide,+    message,+    ) where++import Prelude()+import Protolude hiding (mkInteger)+import Foreign.C.Types+import Foreign.StablePtr+import Emacs.Type+import Emacs.Internal++-- emacsModuleInit に渡す関数+defmodule :: Text -> EmacsM a -> Ptr () -> IO CInt+defmodule name mod ert = do+  env <- getEmacsEnvFromRT ert+  errorHandle env $ do+    ctx <- initCtx env+    runEmacsM ctx $ mod >> funcall1 "provide" (Symbol name)+  return 0++-- | Haskell -> EmacsValue への変換+-- 利便性だけのためかな?+class ToEmacsValue h where+  toEv :: h -> EmacsM EmacsValue+instance ToEmacsValue Int where+  toEv = mkInteger+instance ToEmacsValue Text where+  toEv = mkString+instance ToEmacsValue Symbol where+  toEv (Symbol name) = mkSymbol name+instance ToEmacsValue () where+  toEv _ = mkNil+instance ToEmacsValue EmacsValue where+  toEv = pure+-- TODO: 以下のはやりすぎか?+-- join を含むのはやりすぎだ...+-- instance ToEmacsValue h => ToEmacsValue (EmacsM h) where+--   toEv = join . fmap toEv+instance ToEmacsValue h => ToEmacsValue [h] where+  toEv xs =+    join $ mkList <$> mapM toEv xs+instance (ToEmacsValue a, ToEmacsValue b) => ToEmacsValue (a,b) where+  toEv (a,b) = do+    av <- toEv a+    bv <- toEv b+    mkCons av bv++-- シンボルを渡す場所は多いので。ただ ToEmacsValue と関係持たせていは+-- いけない。+--+-- 通常は Text の引数でいいと思うんだけ(いちいち)文字列ではなく、シン+-- ボルにするよと明示したいところでは利用するのがいいかな?後は関数か+-- らのシンボルの返値を利用する場合とかかな(Text だと、一旦+-- symbol-name で文字列表現を取り出す必要が出てくる)。+--+-- ああ、やはりどうだろう。そもそもシンボルという概念がhaskellには必要+-- ないか?objectではなく、eq で等価性が判定されるので文字列と同義にな+-- るのかな?(ただ空文字列は駄目かな)。+-- ESymbol でも動作させたい関数は ' バージョンを作るのがいいかな。+-- class ToESymbol h where+--   toESymbol  :: h -> EmacsM ESymbol+--   toESymbol' :: h -> EmacsM EmacsValue+--   toESymbol' = fmap (untag) . toESymbol+-- instance ToESymbol Symbol where+--   toESymbol (Symbol name) = mkESymbol name+-- instance ToESymbol ESymbol where+--   toESymbol = pure+-- instance ToESymbol (EmacsM ESymbol) where+--   toESymbol = id++-- for internal usage.+-- 関数名は ToESymbol ではなく、Text で受け取っているのは利便性のため。+funcall1 :: (ToEmacsValue a)+         => Text -> a -> EmacsM EmacsValue+funcall1 fname ev0 =+  join $ funcall <$> mkSymbol fname+                   <*> sequence [toEv ev0]++funcall2 :: (ToEmacsValue a, ToEmacsValue b)+         => Text -> a -> b -> EmacsM EmacsValue+funcall2 fname ev0 ev1 =+  join $ funcall <$> mkSymbol fname+                   <*> sequence [toEv ev0, toEv ev1]++funcall3 :: (ToEmacsValue a, ToEmacsValue b, ToEmacsValue c)+         => Text -> a -> b -> c -> EmacsM EmacsValue+funcall3 fname ev0 ev1 ev2 =+  join $ funcall <$> mkSymbol fname+                   <*> sequence [toEv ev0, toEv ev1, toEv ev2]++-- haskell-emacs の haskell 内 lisp の記述言語をパクる。+--+-- Lisp 型を利用して EmacsValue を生成する場合は自明だが、逆に+-- EmacsValue より Lisp を得るのはそうではない。+--+-- うーん、微妙。やはりモジュールにまとめるべきかな?+--+-- data Lisp = Symbol  Text+--           | String  Text+--           | Integer Int+--           | List    [Lisp]+--           | Ev      EmacsValue+--           | EvM     (EmacsM EmacsValue)+--           | Function  EFunction+--           | FunctionM  (EmacsM EFunction)++-- lispToEmacsValue :: Lisp -> EmacsM EmacsValue+-- lispToEmacsValue (Symbol t)    = untag <$> mkESymbol t+-- lispToEmacsValue (String t)    = untag <$> mkEString t+-- lispToEmacsValue (Integer i)   = untag <$> mkEInteger i+-- lispToEmacsValue (List xs')    = do+--    xs <- (sequence (map lispToEmacsValue xs'))+--    mkEList xs+-- lispToEmacsValue (Ev ev)       = pure ev+-- lispToEmacsValue (Function f)  = pure (untag f)+-- lispToEmacsValue (EvM evm)     = evm+-- lispToEmacsValue (FunctionM f) = untag <$> f++-- funcall :: Lisp -> [Lisp] -> EmacsM EmacsValue+-- funcall f args =+--   join $ funcall' <$> (lispToEmacsValue f) <*> (sequence $ map lispToEmacsValue args)++-- funcall_ :: Lisp -> [Lisp] -> EmacsM ()+-- funcall_ f args =+--   void $ funcall f args++evalString :: Text -> EmacsM EmacsValue+evalString t =+  funcall1 "eval" =<< (car =<< funcall1 "read-from-string" t)++provide :: Text -> EmacsM ()+provide feature =+  void $ funcall1 "provide" (Symbol feature)++message :: Text -> EmacsM ()+message t =+  void $ funcall1 "message" t++mkCons :: EmacsValue -> EmacsValue -> EmacsM EmacsValue+mkCons = funcall2 "cons"++car :: EmacsValue -> EmacsM EmacsValue+car = funcall1 "car"++cdr :: EmacsValue -> EmacsM EmacsValue+cdr = funcall1 "cdr"
+ src/Emacs/Function.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE ForeignFunctionInterface,OverloadedStrings,DataKinds,TypeFamilies,KindSignatures,FlexibleInstances,UndecidableInstances #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GADTs #-}+module Emacs.Function where++import Prelude()+import Protolude+import Emacs.Core++-- | 関数の設定+-- 一番 low level なのが setFunction+setFunction :: Text -> EmacsValue -> EmacsM ()+setFunction name f = do+  void $ funcall2 "fset" (Symbol name) f++-- | より elisp に近い形で記述したいのであればこちら+defun :: Text -> Doc -> Arity -> ([EmacsValue] -> EmacsM EmacsValue) -> EmacsM ()+defun name (Doc doc) (Arity arity) f =+  setFunction name =<< mkFunction f arity arity doc++defun' :: Callable f => Text -> f -> EmacsM ()+defun' name f =+  setFunction name =<< mkFunctionFromCallable f++-- | Haskell <- EmacsValue+-- これは mkEFunction のために?+class FromEmacsValue h where+  fromEv :: EmacsValue -> EmacsM (Maybe h)++instance Num b => FromEmacsValue b where+  fromEv = extractIntegerMaybe++-- hack+-- 多相的な関数は駄目らしい(具体的な関数ならokらしい)+class Callable a where+    call :: a -> [EmacsValue] -> EmacsM (Either Text EmacsValue)+    arity :: a -> Int++instance {-# OVERLAPPING #-} ToEmacsValue a => Callable a where+    call a [] = Right <$> toEv a+    call _ _  = pure $ Left "Too many arguments"+    arity _ = 0++instance {-# OVERLAPPING #-} ToEmacsValue a => Callable (IO a) where+    call a [] = do+      v <- liftIO a+      Right <$> toEv v+    call _ _  = pure $ Left "Too many arguments"+    arity _ = 0++instance {-# OVERLAPPING #-} ToEmacsValue a => Callable (EmacsM a) where+    call am [] = do+      a <- am+      Right <$> toEv a+    call _ _  = pure $ Left "Too many arguments"+    arity _ = 0++instance {-# OVERLAPPING #-} (FromEmacsValue a, Callable b) => Callable (a -> b) where++  call f (e:es) = do+    av' <- fromEv e+    case av' of+      Just av -> call (f av) es+      Nothing -> pure $ Left ""+  call _ [] = pure $ Left "Too less arguments"+  arity f = arity (f undefined) + 1++-- |+-- 多層的な関数は怒られるはず。+--+mkFunctionFromCallable :: Callable f => f -> EmacsM EmacsValue+mkFunctionFromCallable f = do+  let a = arity f+  mkFunction func a a ""+  where+    func :: [EmacsValue] -> EmacsM EmacsValue+    func es = do+      res <- call f es+      case res of+        Right ev -> return ev+        Left _   -> undefined
+ src/Emacs/Internal.hs view
@@ -0,0 +1,482 @@+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+module Emacs.Internal (+    module Emacs.Type,+    initState,+    initCtx,+    getEnv,+    runEmacsM,+    getEmacsEnvFromRT,+    -- Type relaties one..+    typeOf,+    isTypeOf,+    -- emacs -> haskell+    extractInteger,+    extractIntegerMaybe,+    --+    eq,+    isNotNil,+    isNil,+    -- mk+    mkFunction,+    mkInteger,+    mkString,+    mkSymbol,+    mkList,+    mkNil,+    --+    funcall,+    errorHandle+    ) where++{-+emacs-modue.h の関数。+型は EmacsValue しか扱わない。基本、このレベルの関数は使うべきではない。+-}++import Prelude()+import Protolude hiding (mkInteger)+import Control.Exception (displayException)+import Data.IORef+import Emacs.Type+import qualified Data.List as List+import qualified Data.Map as Map+import Foreign.C.Types+import Foreign.C.String+import Foreign.StablePtr+import Foreign.Storable+import Foreign.Marshal.Array+import Foreign.Marshal.Alloc+import GHC.Ptr+import Control.Monad.Log+import qualified Turtle as T+import Formatting (sformat)+import Formatting.Clock (timeSpecs)+import System.Clock (getTime,Clock(..))++initState :: MonadIO m => m PState+initState = do+  mapRef <- liftIO $ newIORef mempty+  return $ PState mapRef++initCtx :: MonadIO m => EmacsEnv -> m Ctx+initCtx env = do+  pstate <- initState+  pstatep <- liftIO $ newStablePtr pstate+  return $ Ctx pstatep pstate env++getPStateStablePtr :: EmacsM (StablePtr PState)+getPStateStablePtr =+  pstateStablePtr <$> ask++getPState :: EmacsM PState+getPState =+  pstate <$> ask++getEnv :: EmacsM EmacsEnv+getEnv =+  emacsEnv <$> ask++runEmacsM :: MonadIO m => Ctx -> EmacsM a -> m a+runEmacsM ctx action = do+  let logHandler t = T.append "/tmp/emacshaskell.log" (pure t)+  liftIO $ do+    startT <- getTime Monotonic+    r <- runReaderT (runLoggingT action logHandler) ctx+    endT <- getTime Monotonic+    map <- readIORef . symbolMap $ pstate ctx+    let time = sformat timeSpecs startT endT+        cacheSize = Map.size map+        log = "emacsM run time: " <> time <> ", symbol cache size: " <> (show cacheSize)+    logHandler log+    return r++foreign import ccall _get_emacs_env_from_rt+  :: Ptr ()+  -> IO EmacsEnv++getEmacsEnvFromRT :: Ptr () -> IO EmacsEnv+getEmacsEnvFromRT =+  _get_emacs_env_from_rt++-- TODO: シンボル のキャッシュしないと毎回シンボルを取得するのは遅い気+-- がする。+foreign import ccall _type_of+  :: EmacsEnv+  -> EmacsValue+  -> IO EmacsValue++typeOf :: EmacsValue -> EmacsM EmacsType+typeOf ev = do+  env <- getEnv+  typeP <- checkExitStatus $ _type_of env ev+  types <- forM emacsTypes $ \t -> do+             q <- mkSymbol (emacsTypeSymbolName t)+             b <- eq q typeP+             return (b, t)+  case List.find fst types of+    Just (_, t) -> return t+    Nothing     -> error "no type"++isTypeOf :: EmacsType -> EmacsValue -> EmacsM Bool+isTypeOf ty ev = do+  t <- typeOf ev+  return $ t == ty++-- | EmacsVAlue -> Haskell の変換+--+-- ただし可能なのは限られている。例えば バッファとかは Haskell 側で対+-- 応する型を持たないので EBuffer(もしくは EmacsValue)のままで扱う必要+-- がある。++-- 引数が integer じゃない場合多分 signal が投げられる+foreign import ccall _extract_integer+  :: EmacsEnv+  -> EmacsValue+  -> IO CIntMax++extractInteger :: Num b => EmacsValue -> EmacsM b+extractInteger ev = do+  env <- getEnv+  i <- checkExitStatus $ _extract_integer env ev+  return (fromIntegral i)++extractIntegerMaybe :: Num b => EmacsValue -> EmacsM (Maybe b)+extractIntegerMaybe v = do+  b <- isTypeOf EInteger v+  if b+    then (Just <$> extractInteger v)+    else return Nothing++-- | どうなんだ?+-- eq は bool 返すのだが、haskell では CBool は用意していないので int にして返している。+-- module_eq は珍しく MODULE_FUNCTION_BEGIN を使っていない。+foreign import ccall _eq+  :: EmacsEnv+  -> EmacsValue+  -> EmacsValue+  -> IO CInt++eq :: EmacsValue -> EmacsValue -> EmacsM Bool+eq ev0 ev1 = do+  env <- getEnv+  r <- liftIO $ _eq env ev0 ev1+  return (r == 1)++foreign import ccall _is_not_nil+  :: EmacsEnv+  -> EmacsValue+  -> IO CInt++isNotNil :: EmacsValue -> EmacsM Bool+isNotNil ev = do+  env <- getEnv+  r <- liftIO $ _is_not_nil env ev+  return (r == 1)++isNil :: EmacsValue -> EmacsM Bool+isNil = (fmap . fmap) not isNotNil++-- TODO: castFunPtrToPtr はまずいかな?inline-c 自体にFunPtrを作る機能+-- が備わっている?$fun:(...) 何故か使えない。。+-- TODO: arity と doc は Arity と Doc 型にするべきかな。++foreign import ccall _make_function+  :: EmacsEnv+  -> CPtrdiff+  -> CPtrdiff+  -> FunPtr EFunctionStub+  -> CString+  -> StablePtr a+  -> IO EmacsValue++-- TODO: ??? これ StablePtr の効果も兼ねている?+foreign import ccall "wrapper" wrapEFunctionStub+  :: EFunctionStub+  -> IO (FunPtr EFunctionStub)++mkFunction :: ([EmacsValue] -> EmacsM EmacsValue) -> Int -> Int -> Text -> EmacsM EmacsValue+mkFunction f minArity' maxArity' doc' = do+  let minArity = fromIntegral minArity' :: CPtrdiff+      maxArity = fromIntegral maxArity' :: CPtrdiff+  datap <- getPStateStablePtr+  stubp <- liftIO (wrapEFunctionStub stub)+  env <- getEnv+  checkExitStatus . withCString (toS doc') $ \doc ->+    _make_function env minArity maxArity stubp doc datap+  where+    stub :: EFunctionStub+    stub env nargs args pstatep = errorHandle env $ do+      pstate <- deRefStablePtr pstatep+      es <- fmap EmacsValue <$> peekArray (fromIntegral nargs) args+      runEmacsM (Ctx pstatep pstate env) (f es)++-- | Haskell で投げられた例外の対応+--+-- Emacs -> Haskell から呼ばれるところに設置する必要がある。例外が補足+-- できないと恐らく emacs がクラッシュする。非同期例外については考える+-- 必要はない。+--+-- 二つの場合を対処する必要がある(多段)+--+--  1. Haskell 側で例外が発生した+--  2. Haskell から呼び出した emacs 関数の中で signal(or throw)された+--+-- 2. の場合、emacsから返ってきた時に non local exit かどうか確認し、+-- もしそうであれば haskellの例外を投げる。そして haskell -> emacsに戻+-- る場所で haskellの例外は補足する。その場合、non-local-exit は既に設+-- 定されているので、+--+-- _non_local_exit_signal で haskellエラーであることを設定する。ただし+-- これが簡単にはいかず、+--+--   * IO モナドの中で実現する必要がある+--   * emacs関数を呼び出す際に例外が発生しうるものを呼び出せない+--+-- catch する順番重要+errorHandle :: EmacsEnv -> IO EmacsValue -> IO EmacsValue+errorHandle env action =+  action `catch` emacsExceptionHandler+         `catch` haskellExceptionHandler+  where+    -- handler の中で例外が発生した場合は諦め?+    -- ほんとは ctx はいらなくて、env だけで 対応したいところ+    -- とりあえずの対応+    --+    -- TODO: ハンドラ中に EmacsException が投げられたときは無視しない+    -- といけな?+    haskellExceptionHandler :: SomeException -> IO EmacsValue+    haskellExceptionHandler e = do+      let log t = T.append "/tmp/emacshaskell.log" (pure t)+      log $ "haskellExceptionHandler: " <> (show e)+      ctx <- initCtx env+      runEmacsM ctx $ do+        funcallExit <- nonLocalExitCheck+        -- TODO: これが不味い。既に funcall-exit が signal/throw に設+        -- 定されている可能性があるため、mkNil で更に EmacsException+        -- 例外が飛んでしまう。+        nil <- mkNil+        when (funcallExit == EmacsFuncallExitReturn) $ do+          mes <- mkString (toS $ displayException e)+          arg <- mkList [mes]+          sym <- mkSymbol "haskell-error"+          nonLocalExitSignal sym arg -- これ以降 emacs関数を呼んでは駄目+        return nil++    emacsExceptionHandler :: EmacsException -> IO EmacsValue+    emacsExceptionHandler e@(EmacsException funcallExit a0 a1) = do+      let log t = T.append "/tmp/emacshaskell.log" (pure t)+      log $ "emacsExceptionHandler: " <> (show e)+      let setter = case funcallExit of+                     EmacsFuncallExitSignal -> _non_local_exit_signal+                     EmacsFuncallExitThrow -> _non_local_exit_throw+      setter env a0 a1+      return a0++-- emacsモジュール関数の呼び出し後に signal/throwされていないかチェッ+-- クする。されている場合は EmacsException を投げる。+--+-- emacs-module.c の module_* 関数で 先頭にMODULE_FUNCTION_BEGIN が書+-- かれているものが実行の後にチェックが必要。+--+-- TODO: 理想的には チェック必要な import ccal は IONeedCheck a みたい+-- な型を返すようにして、checkExitStatus しないと IO(や EmacsM)に直せ+-- ないようにするのがいいのかな?ただちょっと面倒。+checkExitStatus :: IO a -> EmacsM a+checkExitStatus action = do+  v <- liftIO action+  funcallExit <- nonLocalExitCheck+  when (funcallExit /= EmacsFuncallExitReturn) $ do+    (_,a0,a1) <- nonLocalExitGet+    nonLocalExitClear+    liftIO . throwIO $ EmacsException funcallExit a0 a1+  return v++--   emacs_value (*make_integer) (emacs_env *env, intmax_t value);+foreign import ccall _make_integer+  :: EmacsEnv+  -> CIntMax+  -> IO EmacsValue++mkInteger :: Integral n => n -> EmacsM EmacsValue+mkInteger i' = do+  let i = fromIntegral i' :: CIntMax+  env <- getEnv+  checkExitStatus $ _make_integer env i++-- Create emacs symbol+foreign import ccall _make_string+  :: EmacsEnv+  -> CString+  -> CPtrdiff+  -> IO EmacsValue++mkString :: Text -> EmacsM EmacsValue+mkString str = do+  env <- getEnv+  checkExitStatus . withCStringLen (toS str) $ \(cstr,len) ->+    _make_string env cstr (fromIntegral len)++-- | Symbol+-- https://www.gnu.org/software/emacs/manual/html_node/elisp/Creating-Symbols.html+--+-- mkSymbol という名前にしたのは不味い気がしてきた。elispには intern+-- と make-symbol があり意味が違う。intern はシンボルを obarray に登録+-- する(既に登録されていればそれを返す)。make-symbol は全く新しいシン+-- ボルを作成し、obarray には登録しない。+--+-- :foo のようなのは keyword symbol と呼ばれており、自分自身に評価され+-- る。実態としてはただ単に : で前置されたシンボルである。++foreign import ccall _intern+  :: EmacsEnv+  -> CString+  -> IO EmacsValue++mkSymbol :: Text -> EmacsM EmacsValue+mkSymbol str = do+  s' <- lookupCache+  case s' of+    Just gev ->+      return (castGlobalToEmacsValue gev)+    Nothing ->+      storeToCache =<< create+  where+    lookupCache = do+      mapRef <- symbolMap <$> getPState+      map <- liftIO $ readIORef mapRef+      return $ Map.lookup str map++    -- TODO: 現在は全部入れているけど、これはまずい+    storeToCache ev = do+      mapRef <- symbolMap <$> getPState+      gev <- mkGlobalRef ev+      liftIO $ modifyIORef mapRef (Map.insert str gev)+      return (castGlobalToEmacsValue gev)++    create = do+      env <- getEnv+      checkExitStatus . withCString (toS str) $ \cstr -> _intern env cstr++-- 単一の値しかないので引数は不要。どうやって取得するだろ?+-- nil という定数が nil を持っている。+-- (symbol-value 'nil) でいけるかな。(eval 'nil) でもいいかも+--+-- TODO: キャッシュするべきだよね(キャッシュする場合は emacs_value を+-- emacs側でGCされないように global_ref を作る必要があるのかな?+mkNil :: EmacsM EmacsValue+mkNil = do+  q0 <- mkSymbol "symbol-value"+  q1 <- mkSymbol "nil"+  funcall q0 [q1]++-- そもそも list という型は emacs側には存在しない。+-- listp という関数があるが、これは cons もしくは nil かどうかを判定している。+--+-- 以下のナイーブな実装だと大量に emacs 側の関数呼び出しが必要になり、遅い。+--+-- mkList :: [EmacsValue] -> EmacsM EmacsValue+-- mkList evs = do+--   nil <- mkNil+--   foldrM mkCons nil evs++mkList :: [EmacsValue] -> EmacsM EmacsValue+mkList evs = do+  listQ <- mkSymbol "list"+  funcall listQ evs++foreign import ccall _make_global_ref+  :: EmacsEnv+  -> EmacsValue+  -> IO GlobalEmacsValue++mkGlobalRef :: EmacsValue -> EmacsM GlobalEmacsValue+mkGlobalRef ev = do+  env <- getEnv+  checkExitStatus $ _make_global_ref env ev++-- | 例外ハンドリング++foreign import ccall _non_local_exit_check+ :: EmacsEnv+ -> IO CInt++nonLocalExitCheck :: EmacsM EmacsFuncallExit+nonLocalExitCheck = do+  env <- getEnv+  toEnum . fromIntegral <$> liftIO (_non_local_exit_check env)++--+-- Emacs に戻ったときに signal を投げる。+foreign import ccall _non_local_exit_signal+  :: EmacsEnv+  -> EmacsValue+  -> EmacsValue+  -> IO ()++nonLocalExitSignal :: EmacsValue -> EmacsValue -> EmacsM ()+nonLocalExitSignal sym val = do+  env <- getEnv+  liftIO $ _non_local_exit_signal env sym val++foreign import ccall _non_local_exit_throw+  :: EmacsEnv+  -> EmacsValue+  -> EmacsValue+  -> IO ()++nonLocalExitThrow :: EmacsValue -> EmacsValue -> EmacsM ()+nonLocalExitThrow sym val = do+  env <- getEnv+  liftIO $ _non_local_exit_throw env sym val++foreign import ccall _non_local_exit_clear+  :: EmacsEnv+  -> IO ()++nonLocalExitClear :: EmacsM ()+nonLocalExitClear = do+  env <- getEnv+  liftIO $ _non_local_exit_clear env++-- 第二引数、第三引数に書き込まれることに注意。+foreign import ccall _non_local_exit_get+  :: EmacsEnv+  -> Ptr EmacsValue+  -> Ptr EmacsValue+  -> IO CInt++nonLocalExitGet :: EmacsM (EmacsFuncallExit,EmacsValue,EmacsValue)+nonLocalExitGet = do+  env <- getEnv+  liftIO $ do+    a0' <- malloc+    a1' <- malloc+    fe  <- _non_local_exit_get env a0' a1'+    a0  <- peek a0'+    a1  <- peek a1'+    free a0'+    free a1'+    return (toEnum (fromIntegral fe), a0, a1)++-- | Emacs の+-- 呼び出しは symbol に限らないのか。関数の直接起動とか。++foreign import ccall _funcall+  :: EmacsEnv+  -> EmacsValue+  -> CPtrdiff+  -> Ptr EmacsValue+  -> IO EmacsValue++funcall :: EmacsValue -> [EmacsValue] -> EmacsM EmacsValue+funcall func args = do+  env <- getEnv+  checkExitStatus . withArray args $ \carr ->+    _funcall env func argsLen carr+  where+    argsLen = fromIntegral (length args) :: CPtrdiff
+ src/Emacs/Type.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+module Emacs.Type where++import Prelude(Show(..))+import Protolude hiding (show)+import Data.IORef+import GHC.Ptr+import Foreign.C.Types+import Foreign.StablePtr+import Control.Monad.Log+import Control.Monad.Reader+import Data.Data hiding(typeOf)++data PState = PState+  { symbolMap :: IORef (Map Text GlobalEmacsValue)+  }++data Ctx = Ctx+  { pstateStablePtr :: StablePtr PState+  , pstate :: PState+  , emacsEnv :: EmacsEnv+  }++type EmacsM =+  LoggingT Text (ReaderT Ctx IO)+++-- | nil について+--+-- emacs 内部では nil は文字列で表現できないシンボルとして定義されている。+-- globals.h+--+-- #define Qnil builtin_lisp_symbol (0)+--+-- type-of では取得できない。nil は型ではない?かな。多分 type_of に渡+-- すとエラーになる。+--+-- haskell では不便なので ENil という型を導入する。+data EmacsType = ESymbol+               | EInteger+               | EFunction+               | EString+               | ECons+               | ENil+  deriving (Show, Eq, Data)++-- これは emacs 側での正式な名前である必要がある。typeOf が依存してい+-- る。nil は若干嘘が入っている。nil という型は存在しない。+emacsTypeSymbolName :: EmacsType -> Text+emacsTypeSymbolName ESymbol   = "symbol"+emacsTypeSymbolName EInteger  = "integer"+emacsTypeSymbolName EFunction = "function"+emacsTypeSymbolName EString   = "string"+emacsTypeSymbolName ECons     = "cons"+emacsTypeSymbolName ENil      = "nil" -- lie++emacsTypes :: [EmacsType]+emacsTypes = fromConstr <$> dataTypeConstrs (dataTypeOf ESymbol)++-- Singlton Types テクニック+-- 値と型を結び付けている+-- 現在は tagAs のところでしか使っていないのでオーバーキルかな。。+-- export しないほうがいいかも。internal だけに留めるかな?+-- data SEmacsType (et :: EmacsType) where+--     SESymbol  :: SEmacsType 'ESymbol+--     SEInteger :: SEmacsType 'EInteger+--     SECons    :: SEmacsType 'ECons+--     SENil     :: SEmacsType 'ENil++-- emacsTypeValue :: SEmacsType b -> EmacsType+-- emacsTypeValue SESymbol = ESymbol+-- emacsTypeValue SEInteger = EInteger+++newtype EmacsEnv   = EmacsEnv (Ptr ())+  deriving (Storable)+newtype EmacsValue = EmacsValue (Ptr ())+  deriving (Storable)+newtype GlobalEmacsValue = GlobalEmacsValue (Ptr ())+  deriving (Storable)++castGlobalToEmacsValue :: GlobalEmacsValue -> EmacsValue+castGlobalToEmacsValue (GlobalEmacsValue p) =+  EmacsValue p++-- type ESymbol       = Tagged 'ESymbol EmacsValue+-- type EInteger      = Tagged 'EInteger EmacsValue+-- type EFunction     = Tagged 'EFunction EmacsValue+-- type EString       = Tagged 'EString EmacsValue+-- type ECons         = Tagged 'ECons EmacsValue+-- type ENil          = Tagged 'ENil EmacsValue++-- | Emacs の値に対する Haskell の型+-- 数値や文字列は素直なんだけど、他+-- Nil は空 [] でいいのかな?+newtype Symbol = Symbol Text+data Nil       = Nil+data Cons      = Cons EmacsValue EmacsValue+newtype List   = List [EmacsValue]++-- | 例外機構++data EmacsFuncallExit+  = EmacsFuncallExitReturn+  | EmacsFuncallExitSignal+  | EmacsFuncallExitThrow+  deriving (Show,Eq,Enum)++data EmacsException+  = EmacsException EmacsFuncallExit EmacsValue EmacsValue++instance Show EmacsException where+  show (EmacsException funcallExit _ _) =+    "EmacsException(" <> show funcallExit <> ")"++instance Exception EmacsException++-- | 関数定義のため必要な型++type EFunctionStub+  = EmacsEnv+  -> CPtrdiff+  -> Ptr (Ptr ())+  -> StablePtr PState+  -> IO EmacsValue++data InteractiveForm = InteractiveNoArgs++newtype Doc   = Doc Text   -- ドキュメント(関数など)+newtype Arity = Arity Int  -- 関数アリティ
+ src/Emacs/Variable.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+module Emacs.Variable+  ( setVariable+  , variableValue+  , isVariableBounded+  ) where++import Emacs.Core+import Data.Text++setVariable :: ToEmacsValue a => Text -> a -> EmacsM EmacsValue+setVariable name val =+  funcall2 "set" (Symbol name) val++isVariableBounded :: Text -> EmacsM Bool+isVariableBounded name =+  isNotNil =<< funcall1 "boundp" (Symbol name)++variableValue :: Text -> EmacsM (Maybe EmacsValue)+variableValue name = do+  bounded <- isNotNil =<< funcall1 "boundp" (Symbol name)+  if bounded+    then Just <$> funcall1 "symbol-value" (Symbol name)+    else pure Nothing