在编译时使用类型提供程序加载和存储文件
Loading and storing a file at compile time with a type provider
我想在编译时加载一个(二进制)文件并将其存储在 Bytes
类型的顶层变量中:
module FileProvider
import Data.Bits
import Data.Bytes
import Data.Buffer
%default total
export
loadImage : String -> IO (Provider Bytes)
loadImage fileName = do
Right file <- openFile fileName Read
| Left err => pure (Error $ show err)
Just buf <- newBuffer size
| Nothing => pure (Error "allocation failed")
readBufferFromFile file buf size
Provide . dropPrefix 2 . pack <$> bufferData buf
where
size : Int
size = 256 * 256 + 2
它似乎在 运行 时间正常工作:
module Main
import FileProvider
import Data.Bits
import Data.Bytes
main : IO ()
main = do
Provide mem <- loadImage "ram.mem"
| Error err => putStrLn err
printLn $ length mem
但是,如果我在编译时尝试 运行 它,它会失败并显示一条提到 FFI 的神秘消息:
module Main
import FileProvider
import Data.Bits
import Data.Bytes
%language TypeProviders
%provide (mem : Bytes) with loadImage "ram.mem"
main : IO ()
main = do
printLn $ length mem
$ idris -p bytes -o SOMain SOMain.idr
Symbol "idris_newBuffer" not found
idris: user error (Could not call foreign function "idris_newBuffer" with args [65538])
这是怎么回事,我如何在编译时加载文件的内容?
idris_newbuffer
是 Data.Buffer
使用的 C 函数。来自 docs to type providers:
If we want to call our foreign functions from interpreted code (such as the REPL or a type provider), we need to dynamically link a library containing the symbols we need.
所以每个使用 FFI 的函数都需要链接一个动态库。那将是 Data.Buffer
和 Data.ByteArray
。让我们关注第一个,看看会出现什么问题:
所以 Data.Buffer
需要 %dynamic "idris_buffer.so"
(而不仅仅是它目前拥有的 %include C "idris_buffer.h"
)。您可以复制 idris/rts/idris_buffer.(h|c)
并删除需要其他 rts-stuff 的功能。编译共享库 运行:
cc -o idris_buffer.so -fPIC -shared idris_buffer.c
使用修改后的Data.Buffer
,你仍然会得到一个错误:
Could not use <<handle {handle: ram.mem}>> as FFI arg.
FFI 调用在 Data.Buffer.readBufferFromFile
。 File
参数引起了麻烦。那是因为 Idris 看到 openFile
被使用(另一个 C 函数)并且
transforms it in a Haskell call. On the one hand that's nice, because during compilation we're interpreting Idris code, and that make following functions like readLine
C/JS/Node/… agnostic. But in this case it's unfortunate, because the Haskell backend doesn't support the returned file handle for FFI calls。所以我们可以编写另一个 fopen : String -> String -> IO Ptr
函数来做同样的事情但有另一个名字所以 Ptr
将保留 Ptr
,因为它们可以在 FFI 调用中使用。
完成后,由于内置函数出现另一个错误:
Could not use prim__registerPtr (<<ptr 0x00000000009e2bf0>>) (65546) as FFI arg.
Data.Buffer
在其后端使用 ManagedPtr
。是的,这在 FFI 调用中也不支持。所以您需要将它们更改为 Ptr
。我猜,这两个 都可以 得到编译器的支持。
最后,一切都应该适用于有效的 %provide (mem : Buffer)
。但是,不,因为:
Can't convert pointers back to TT after execution.
尽管 Idris 现在可以在编译时读取文件,但它无法使 Buffer
或任何其他具有 Pnt
的内容可供 运行 访问 - 这是非常合理的.在 运行 时间,只有程序编译时的指针只是随机的事情。因此,您要么需要将数据转换为提供程序中的结果,要么使用像 Provider (List Bits8)
.
这样的中间格式
I made a short example 让 List Bits8
可以在 main
中访问。 Buffer
基本上是 Data.Buffer
,包含 _openFile
和 Pnt
而不是 ManagedPtr
。我希望这能以某种方式帮助你,但也许一些编译器人员可以提供更多背景知识。
我想在编译时加载一个(二进制)文件并将其存储在 Bytes
类型的顶层变量中:
module FileProvider
import Data.Bits
import Data.Bytes
import Data.Buffer
%default total
export
loadImage : String -> IO (Provider Bytes)
loadImage fileName = do
Right file <- openFile fileName Read
| Left err => pure (Error $ show err)
Just buf <- newBuffer size
| Nothing => pure (Error "allocation failed")
readBufferFromFile file buf size
Provide . dropPrefix 2 . pack <$> bufferData buf
where
size : Int
size = 256 * 256 + 2
它似乎在 运行 时间正常工作:
module Main
import FileProvider
import Data.Bits
import Data.Bytes
main : IO ()
main = do
Provide mem <- loadImage "ram.mem"
| Error err => putStrLn err
printLn $ length mem
但是,如果我在编译时尝试 运行 它,它会失败并显示一条提到 FFI 的神秘消息:
module Main
import FileProvider
import Data.Bits
import Data.Bytes
%language TypeProviders
%provide (mem : Bytes) with loadImage "ram.mem"
main : IO ()
main = do
printLn $ length mem
$ idris -p bytes -o SOMain SOMain.idr Symbol "idris_newBuffer" not found idris: user error (Could not call foreign function "idris_newBuffer" with args [65538])
这是怎么回事,我如何在编译时加载文件的内容?
idris_newbuffer
是 Data.Buffer
使用的 C 函数。来自 docs to type providers:
If we want to call our foreign functions from interpreted code (such as the REPL or a type provider), we need to dynamically link a library containing the symbols we need.
所以每个使用 FFI 的函数都需要链接一个动态库。那将是 Data.Buffer
和 Data.ByteArray
。让我们关注第一个,看看会出现什么问题:
所以 Data.Buffer
需要 %dynamic "idris_buffer.so"
(而不仅仅是它目前拥有的 %include C "idris_buffer.h"
)。您可以复制 idris/rts/idris_buffer.(h|c)
并删除需要其他 rts-stuff 的功能。编译共享库 运行:
cc -o idris_buffer.so -fPIC -shared idris_buffer.c
使用修改后的Data.Buffer
,你仍然会得到一个错误:
Could not use <<handle {handle: ram.mem}>> as FFI arg.
FFI 调用在 Data.Buffer.readBufferFromFile
。 File
参数引起了麻烦。那是因为 Idris 看到 openFile
被使用(另一个 C 函数)并且
transforms it in a Haskell call. On the one hand that's nice, because during compilation we're interpreting Idris code, and that make following functions like readLine
C/JS/Node/… agnostic. But in this case it's unfortunate, because the Haskell backend doesn't support the returned file handle for FFI calls。所以我们可以编写另一个 fopen : String -> String -> IO Ptr
函数来做同样的事情但有另一个名字所以 Ptr
将保留 Ptr
,因为它们可以在 FFI 调用中使用。
完成后,由于内置函数出现另一个错误:
Could not use prim__registerPtr (<<ptr 0x00000000009e2bf0>>) (65546) as FFI arg.
Data.Buffer
在其后端使用 ManagedPtr
。是的,这在 FFI 调用中也不支持。所以您需要将它们更改为 Ptr
。我猜,这两个 都可以 得到编译器的支持。
最后,一切都应该适用于有效的 %provide (mem : Buffer)
。但是,不,因为:
Can't convert pointers back to TT after execution.
尽管 Idris 现在可以在编译时读取文件,但它无法使 Buffer
或任何其他具有 Pnt
的内容可供 运行 访问 - 这是非常合理的.在 运行 时间,只有程序编译时的指针只是随机的事情。因此,您要么需要将数据转换为提供程序中的结果,要么使用像 Provider (List Bits8)
.
I made a short example 让 List Bits8
可以在 main
中访问。 Buffer
基本上是 Data.Buffer
,包含 _openFile
和 Pnt
而不是 ManagedPtr
。我希望这能以某种方式帮助你,但也许一些编译器人员可以提供更多背景知识。