diff --git a/bower.json b/bower.json index 229881b..962110e 100644 --- a/bower.json +++ b/bower.json @@ -15,11 +15,14 @@ "dependencies": { "purescript-effect": "^2.0.0", "purescript-maybe": "^4.0.0", - "purescript-arraybuffer-types": "^2.0.0" + "purescript-arraybuffer-types": "^2.0.0", + "purescript-st": "^4.0.0", + "purescript-unsafe-coerce": "^4.0.0" }, "devDependencies": { "purescript-assert": "^4.0.0", "purescript-console": "^4.1.0", - "purescript-foldable-traversable": "^4.0.0" + "purescript-foldable-traversable": "^4.0.0", + "purescript-proxy": "^3.0.0" } } diff --git a/src/Node/Buffer.js b/src/Node/Buffer.js deleted file mode 100644 index e96639d..0000000 --- a/src/Node/Buffer.js +++ /dev/null @@ -1,176 +0,0 @@ -/* global exports */ -/* global Buffer */ -/* global require */ -"use strict"; - -exports.showImpl = require('util').inspect; - -exports.create = function (size) { - return function() { - return Buffer.alloc(size); - }; -}; - -exports.fromArray = function (octets) { - return function() { - return Buffer.from(octets); - }; -}; - -exports.fromStringImpl = function (str) { - return function (encoding) { - return function() { - return Buffer.from(str, encoding); - }; - }; -}; - -exports.fromArrayBuffer = function(ab) { - return function() { - return Buffer.from(ab); - }; -}; - -exports.toArrayBuffer = function(buff) { - return function() { - return buff.buffer.slice(buff.byteOffset, buff.byteOffset + buff.byteLength); - }; -}; - -exports.readImpl = function (ty) { - return function (offset) { - return function (buf) { - return function() { - return buf['read' + ty](offset); - }; - }; - }; -}; - -exports.readStringImpl = function (enc) { - return function (start) { - return function (end) { - return function (buff) { - return function() { - return buff.toString(enc, start, end); - }; - }; - }; - }; -}; - -exports.toStringImpl = function (enc) { - return function (buff) { - return function() { - return buff.toString(enc); - }; - }; -}; - -exports.writeImpl = function (ty) { - return function (value) { - return function (offset) { - return function (buf) { - return function() { - buf['write' + ty](value, offset); - return {}; - } - }; - }; - }; -}; - -exports.writeStringImpl = function (encoding) { - return function (offset) { - return function (length) { - return function (value) { - return function (buff) { - return function() { - return buff.write(value, offset, length, encoding); - } - }; - }; - }; - }; -}; - -exports.toArray = function (buff) { - return function() { - var json = buff.toJSON() - return json.data || json; - }; -}; - -exports.getAtOffsetImpl = function (just) { - return function (nothing) { - return function (offset) { - return function (buff) { - return function() { - var octet = buff[offset]; - return octet == null ? nothing - : just(octet); - }; - }; - }; - }; -}; - -exports.setAtOffset = function (value) { - return function (offset) { - return function (buff) { - return function() { - buff[offset] = value; - return {}; - }; - }; - }; -}; - -exports.size = function (buff) { - return function() { - return buff.length; - }; -}; - - - -exports.concat = function (buffs) { - return function() { - return Buffer.concat(buffs); - }; -}; - -exports["concat'"] = function (buffs) { - return function (totalLength) { - return function() { - return Buffer.concat(buffs, totalLength); - }; - }; -}; - -exports.copy = function (srcStart) { - return function (srcEnd) { - return function (src) { - return function (targStart) { - return function (targ) { - return function() { - return src.copy(targ, targStart, srcStart, srcEnd); - }; - }; - }; - }; - }; -}; - -exports.fill = function (octet) { - return function (start) { - return function (end) { - return function (buf) { - return function() { - buf.fill(octet, start, end); - return {}; - }; - }; - }; - }; -}; diff --git a/src/Node/Buffer.purs b/src/Node/Buffer.purs index d90b9ab..612afd3 100644 --- a/src/Node/Buffer.purs +++ b/src/Node/Buffer.purs @@ -1,164 +1,40 @@ +-- | Mutable buffers and associated operations. module Node.Buffer - ( Octet() - , Offset() - , Buffer() - , BufferValueType(..) - , create - , fromArray - , fromString - , fromArrayBuffer - , toArrayBuffer - , read - , readString - , toString - , write - , writeString - , toArray - , getAtOffset - , setAtOffset - , size - , concat - , concat' - , copy - , fill + ( Buffer + , module TypesExports + , module Class ) where -import Prelude - import Effect (Effect) -import Data.ArrayBuffer.Types (ArrayBuffer) -import Data.Maybe (Maybe(..)) -import Node.Encoding (Encoding, encodingToNode) - --- | Type synonym indicating the value should be an octet (0-255). If the value --- | provided is outside this range it will be used as modulo 256. -type Octet = Int +import Node.Buffer.Class (class MutableBuffer) +import Node.Buffer.Class (class MutableBuffer, concat, concat', copy, create, fill, freeze, fromArray, fromArrayBuffer, fromString, getAtOffset, read, readString, setAtOffset, size, slice, thaw, toArray, toArrayBuffer, toString, unsafeFreeze, unsafeThaw, write, writeString) as Class +import Node.Buffer.Internal as Internal +import Node.Buffer.Types (BufferValueType(..), Octet, Offset) as TypesExports --- | Type synonym indicating the value refers to an offset in a buffer. -type Offset = Int - --- | An instance of Node's Buffer class. +-- | A reference to a mutable buffer for use with `Effect` foreign import data Buffer :: Type -instance showBuffer :: Show Buffer where - show = showImpl - -foreign import showImpl :: Buffer -> String - --- | Enumeration of the numeric types that can be written to a buffer. -data BufferValueType - = UInt8 - | UInt16LE - | UInt16BE - | UInt32LE - | UInt32BE - | Int8 - | Int16LE - | Int16BE - | Int32LE - | Int32BE - | FloatLE - | FloatBE - | DoubleLE - | DoubleBE - -instance showBufferValueType :: Show BufferValueType where - show UInt8 = "UInt8" - show UInt16LE = "UInt16LE" - show UInt16BE = "UInt16BE" - show UInt32LE = "UInt32LE" - show UInt32BE = "UInt32BE" - show Int8 = "Int8" - show Int16LE = "Int16LE" - show Int16BE = "Int16BE" - show Int32LE = "Int32LE" - show Int32BE = "Int32BE" - show FloatLE = "FloatLE" - show FloatBE = "FloatBE" - show DoubleLE = "DoubleLE" - show DoubleBE = "DoubleBE" - --- | Creates a new buffer of the specified size. -foreign import create :: Int -> Effect Buffer - --- | Creates a new buffer from an array of octets, sized to match the array. -foreign import fromArray :: Array Octet -> Effect Buffer - --- | Creates a buffer view from a JS ArrayByffer without copying data. --- --- Requires Node >= v5.10.0 -foreign import fromArrayBuffer :: ArrayBuffer -> Effect Buffer - --- | Creates a new buffer from a string with the specified encoding, sized to --- | match the string. -fromString :: String -> Encoding -> Effect Buffer -fromString str = fromStringImpl str <<< encodingToNode - -foreign import fromStringImpl :: String -> String -> Effect Buffer - -foreign import toArrayBuffer :: Buffer -> Effect ArrayBuffer - --- | Reads a numeric value from a buffer at the specified offset. -read :: BufferValueType -> Offset -> Buffer -> Effect Int -read = readImpl <<< show - -foreign import readImpl :: String -> Offset -> Buffer -> Effect Int - --- | Reads a section of a buffer as a string with the specified encoding. -readString :: Encoding -> Offset -> Offset -> Buffer -> Effect String -readString = readStringImpl <<< encodingToNode - -foreign import readStringImpl :: - String -> Offset -> Offset -> Buffer -> Effect String - --- | Reads the buffer as a string with the specified encoding. -toString :: Encoding -> Buffer -> Effect String -toString = toStringImpl <<< encodingToNode - -foreign import toStringImpl :: String -> Buffer -> Effect String - --- | Writes a numeric value to a buffer at the specified offset. -write :: BufferValueType -> Int -> Offset -> Buffer -> Effect Unit -write = writeImpl <<< show - -foreign import writeImpl :: String -> Int -> Offset -> Buffer -> Effect Unit - --- | Writes octets from a string to a buffer at the specified offset. Multi-byte --- | characters will not be written to the buffer if there is not enough capacity --- | to write them fully. The number of bytes written is returned. -writeString :: Encoding -> Offset -> Int -> String -> Buffer -> Effect Int -writeString = writeStringImpl <<< encodingToNode - -foreign import writeStringImpl :: - String -> Offset -> Int -> String -> Buffer -> Effect Int - --- | Creates an array of octets from a buffer's contents. -foreign import toArray :: Buffer -> Effect (Array Octet) - --- | Reads an octet from a buffer at the specified offset. -getAtOffset :: Offset -> Buffer -> Effect (Maybe Octet) -getAtOffset = getAtOffsetImpl Just Nothing - -foreign import getAtOffsetImpl :: - (Octet -> Maybe Octet) -> Maybe Octet -> Offset -> Buffer -> Effect (Maybe Octet) - --- | Writes an octet in the buffer at the specified offset. -foreign import setAtOffset :: Octet -> Offset -> Buffer -> Effect Unit - --- | Returns the size of a buffer. -foreign import size :: Buffer -> Effect Int - --- | Concatenates a list of buffers. -foreign import concat :: Array Buffer -> Effect Buffer - --- | Concatenates a list of buffers, combining them into a new buffer of the --- | specified length. -foreign import concat' :: Array Buffer -> Int -> Effect Buffer - --- | Copies a section of a source buffer into a target buffer at the specified --- | offset, and returns the number of octets copied. -foreign import copy :: Offset -> Offset -> Buffer -> Offset -> Buffer -> Effect Int - --- | Fills a range in a buffer with the specified octet. -foreign import fill :: - Octet -> Offset -> Offset -> Buffer -> Effect Unit +instance mutableBufferEffect :: MutableBuffer Buffer Effect where + create = Internal.create + freeze = Internal.copyAll + unsafeFreeze = Internal.unsafeFreeze + thaw = Internal.copyAll + unsafeThaw = Internal.unsafeThaw + fromArray = Internal.fromArray + fromString = Internal.fromString + fromArrayBuffer = Internal.fromArrayBuffer + toArrayBuffer = Internal.toArrayBuffer + read = Internal.read + readString = Internal.readString + toString = Internal.toString + write = Internal.write + writeString = Internal.writeString + toArray = Internal.toArray + getAtOffset = Internal.getAtOffset + setAtOffset = Internal.setAtOffset + slice = Internal.slice + size = Internal.size + concat = Internal.concat + concat' = Internal.concat' + copy = Internal.copy + fill = Internal.fill diff --git a/src/Node/Buffer/Class.purs b/src/Node/Buffer/Class.purs new file mode 100644 index 0000000..1bf2702 --- /dev/null +++ b/src/Node/Buffer/Class.purs @@ -0,0 +1,115 @@ +module Node.Buffer.Class + ( class MutableBuffer + , create + , freeze + , unsafeFreeze + , thaw + , unsafeThaw + , fromArray + , fromString + , fromArrayBuffer + , toArrayBuffer + , read + , readString + , toString + , write + , writeString + , toArray + , getAtOffset + , setAtOffset + , slice + , size + , concat + , concat' + , copy + , fill + ) where + +import Prelude + +import Data.ArrayBuffer.Types (ArrayBuffer) +import Data.Maybe (Maybe) +import Node.Buffer.Immutable (ImmutableBuffer) +import Node.Buffer.Types (BufferValueType, Octet, Offset) +import Node.Encoding (Encoding) + +-- | A type class for mutable buffers `buf` where operations on those buffers are +-- | represented by a particular monadic effect type `m`. +class Monad m <= MutableBuffer buf m | buf -> m where + + -- | Creates a new buffer of the specified size. + create :: Int -> m buf + + -- | Creates an immutable copy of a mutable buffer. + freeze :: buf -> m ImmutableBuffer + + -- | O(1). Convert a mutable buffer to an immutable buffer, without copying. The + -- | mutable buffer must not be mutated afterwards. + unsafeFreeze :: buf -> m ImmutableBuffer + + -- | Creates a mutable copy of an immutable buffer. + thaw :: ImmutableBuffer -> m buf + + -- | O(1) Convert an immutable buffer to a mutable buffer, without copying. The + -- | input buffer must not be used afterward. + unsafeThaw :: ImmutableBuffer -> m buf + + -- | Creates a new buffer from an array of octets, sized to match the array. + fromArray :: Array Octet -> m buf + + -- | Creates a new buffer from a string with the specified encoding, sized to + -- | match the string. + fromString :: String -> Encoding -> m buf + + -- | Creates a buffer view from a JS ArrayByffer without copying data. + fromArrayBuffer :: ArrayBuffer -> m buf + + -- | Copies the data in the buffer to a new JS ArrayBuffer + toArrayBuffer :: buf -> m ArrayBuffer + + -- | Reads a numeric value from a buffer at the specified offset. + read :: BufferValueType -> Offset -> buf -> m Int + + -- | Reads a section of a buffer as a string with the specified encoding. + readString :: Encoding -> Offset -> Offset -> buf -> m String + + -- | Reads the buffer as a string with the specified encoding. + toString :: Encoding -> buf -> m String + + -- | Writes a numeric value to a buffer at the specified offset. + write :: BufferValueType -> Int -> Offset -> buf -> m Unit + + -- | Writes octets from a string to a buffer at the specified offset. Multi-byte + -- | characters will not be written to the buffer if there is not enough capacity + -- | to write them fully. The number of bytes written is returned. + writeString :: Encoding -> Offset -> Int -> String -> buf -> m Int + + -- | Creates an array of octets from a buffer's contents. + toArray :: buf -> m (Array Octet) + + -- | Reads an octet from a buffer at the specified offset. + getAtOffset :: Offset -> buf -> m (Maybe Octet) + + -- | Writes an octet in the buffer at the specified offset. + setAtOffset :: Octet -> Offset -> buf -> m Unit + + -- | Creates a new buffer slice that acts like a window on the original buffer. + -- | Writing to the slice buffer updates the original buffer and vice-versa. + slice :: Offset -> Offset -> buf -> buf + + -- | Returns the size of a buffer. + size :: buf -> m Int + + -- | Concatenates a list of buffers. + concat :: Array buf -> m buf + + -- | Concatenates a list of buffers, combining them into a new buffer of the + -- | specified length. + concat' :: Array buf -> Int -> m buf + + -- | Copies a section of a source buffer into a target buffer at the specified + -- | offset, and returns the number of octets copied. + copy :: Offset -> Offset -> buf -> Offset -> buf -> m Int + + -- | Fills a range in a buffer with the specified octet. + fill :: Octet -> Offset -> Offset -> buf -> m Unit diff --git a/src/Node/Buffer/Immutable.js b/src/Node/Buffer/Immutable.js new file mode 100644 index 0000000..3809ae9 --- /dev/null +++ b/src/Node/Buffer/Immutable.js @@ -0,0 +1,103 @@ +/* global exports */ +/* global Buffer */ +/* global require */ +"use strict"; + +exports.showImpl = require('util').inspect; + +exports.eqImpl = function(a) { + return function(b) { + return a.equals(b); + } +}; + +exports.compareImpl = function(a) { + return function (b) { + return a.compare(b); + }; +} + +exports.create = function (size) { + return Buffer.alloc(size); +}; + +exports.fromArray = function (octets) { + return Buffer.from(octets); +}; + +exports.size = function (buff) { + return buff.length; +}; + +exports.toArray = function (buff) { + var json = buff.toJSON() + return json.data || json; +}; + +exports.toArrayBuffer = function(buff) { + return buff.buffer.slice(buff.byteOffset, buff.byteOffset + buff.byteLength); +}; + +exports.fromArrayBuffer = function(ab) { + return Buffer.from(ab); +}; + +exports.fromStringImpl = function (str) { + return function (encoding) { + return Buffer.from(str, encoding); + }; +}; + +exports.readImpl = function (ty) { + return function (offset) { + return function (buf) { + return buf['read' + ty](offset); + }; + }; +}; + +exports.readStringImpl = function (enc) { + return function (start) { + return function (end) { + return function (buff) { + return buff.toString(enc, start, end); + }; + }; + }; +}; + +exports.getAtOffsetImpl = function (just) { + return function (nothing) { + return function (offset) { + return function (buff) { + var octet = buff[offset]; + return octet == null ? nothing + : just(octet); + }; + }; + }; +}; + +exports.toStringImpl = function (enc) { + return function (buff) { + return buff.toString(enc); + }; +}; + +exports.slice = function (start) { + return function (end) { + return function (buff) { + return buff.slice(start, end); + }; + }; +}; + +exports.concat = function (buffs) { + return Buffer.concat(buffs); +}; + +exports["concat'"] = function (buffs) { + return function (totalLength) { + return Buffer.concat(buffs, totalLength); + }; +}; diff --git a/src/Node/Buffer/Immutable.purs b/src/Node/Buffer/Immutable.purs new file mode 100644 index 0000000..5994e1c --- /dev/null +++ b/src/Node/Buffer/Immutable.purs @@ -0,0 +1,109 @@ +-- | Immutable buffers and associated operations. +module Node.Buffer.Immutable + ( ImmutableBuffer + , create + , fromArray + , fromString + , fromArrayBuffer + , read + , readString + , toString + , toArray + , toArrayBuffer + , getAtOffset + , concat + , concat' + , slice + , size + ) where + +import Prelude + +import Data.ArrayBuffer.Types (ArrayBuffer) +import Data.Maybe (Maybe(..)) +import Node.Buffer.Types (BufferValueType, Octet, Offset) +import Node.Encoding (Encoding, encodingToNode) + +-- | An immutable buffer that exists independently of any memory region or effect. +foreign import data ImmutableBuffer :: Type + +instance showBuffer :: Show ImmutableBuffer where + show = showImpl + +foreign import showImpl :: ImmutableBuffer -> String + +instance eqBuffer :: Eq ImmutableBuffer where + eq = eqImpl + +foreign import eqImpl :: ImmutableBuffer -> ImmutableBuffer -> Boolean + +instance ordBuffer :: Ord ImmutableBuffer where + compare a b = + case compareImpl a b of + x | x < 0 -> LT + x | x > 0 -> GT + otherwise -> EQ + +foreign import compareImpl :: ImmutableBuffer -> ImmutableBuffer -> Int + +-- | Creates a new buffer of the specified size. +foreign import create :: Int -> ImmutableBuffer + +-- | Creates a new buffer from an array of octets, sized to match the array. +foreign import fromArray :: Array Octet -> ImmutableBuffer + +-- | Creates a buffer view from a JS ArrayByffer without copying data. +-- +-- Requires Node >= v5.10.0 +foreign import fromArrayBuffer :: ArrayBuffer -> ImmutableBuffer + +-- | Creates a new buffer from a string with the specified encoding, sized to match the string. +fromString :: String -> Encoding -> ImmutableBuffer +fromString str = fromStringImpl str <<< encodingToNode + +foreign import fromStringImpl :: String -> String -> ImmutableBuffer + +-- | Reads a numeric value from a buffer at the specified offset. +read :: BufferValueType -> Offset -> ImmutableBuffer -> Int +read = readImpl <<< show + +foreign import readImpl :: String -> Offset -> ImmutableBuffer -> Int + +-- | Reads a section of a buffer as a string with the specified encoding. +readString :: Encoding -> Offset -> Offset -> ImmutableBuffer -> String +readString = readStringImpl <<< encodingToNode + +foreign import readStringImpl :: + String -> Offset -> Offset -> ImmutableBuffer -> String + +-- | Reads the buffer as a string with the specified encoding. +toString :: Encoding -> ImmutableBuffer -> String +toString = toStringImpl <<< encodingToNode + +foreign import toStringImpl :: String -> ImmutableBuffer -> String + +-- | Creates an array of octets from a buffer's contents. +foreign import toArray :: ImmutableBuffer -> Array Octet + +-- | Creates an `ArrayBuffer` by copying a buffer's contents. +foreign import toArrayBuffer :: ImmutableBuffer -> ArrayBuffer + +-- | Reads an octet from a buffer at the specified offset. +getAtOffset :: Offset -> ImmutableBuffer -> Maybe Octet +getAtOffset = getAtOffsetImpl Just Nothing + +foreign import getAtOffsetImpl :: + (Octet -> Maybe Octet) -> Maybe Octet -> Offset -> ImmutableBuffer -> Maybe Octet + +-- | Concatenates a list of buffers. +foreign import concat :: Array ImmutableBuffer -> ImmutableBuffer + +-- | Concatenates a list of buffers, combining them into a new buffer of the +-- | specified length. +foreign import concat' :: Array ImmutableBuffer -> Int -> ImmutableBuffer + +-- | Creates a new buffer slice that shares the memory of the original buffer. +foreign import slice :: Offset -> Offset -> ImmutableBuffer -> ImmutableBuffer + +-- | Returns the size of a buffer. +foreign import size :: ImmutableBuffer -> Int diff --git a/src/Node/Buffer/Internal.js b/src/Node/Buffer/Internal.js new file mode 100644 index 0000000..7cd3cd9 --- /dev/null +++ b/src/Node/Buffer/Internal.js @@ -0,0 +1,74 @@ +/* global exports */ +/* global Buffer */ +"use strict"; + +exports.copyAll = function(a) { + return function() { + return Buffer.from(a); + }; +}; + +exports.writeInternal = function (ty) { + return function (value) { + return function (offset) { + return function (buf) { + return function() { + buf['write' + ty](value, offset); + return {}; + } + }; + }; + }; +}; + +exports.writeStringInternal = function (encoding) { + return function (offset) { + return function (length) { + return function (value) { + return function (buff) { + return function() { + return buff.write(value, offset, length, encoding); + } + }; + }; + }; + }; +}; + +exports.setAtOffset = function (value) { + return function (offset) { + return function (buff) { + return function() { + buff[offset] = value; + return {}; + }; + }; + }; +}; + +exports.copy = function (srcStart) { + return function (srcEnd) { + return function (src) { + return function (targStart) { + return function (targ) { + return function() { + return src.copy(targ, targStart, srcStart, srcEnd); + }; + }; + }; + }; + }; +}; + +exports.fill = function (octet) { + return function (start) { + return function (end) { + return function (buf) { + return function() { + buf.fill(octet, start, end); + return {}; + }; + }; + }; + }; +}; diff --git a/src/Node/Buffer/Internal.purs b/src/Node/Buffer/Internal.purs new file mode 100644 index 0000000..6ee6662 --- /dev/null +++ b/src/Node/Buffer/Internal.purs @@ -0,0 +1,109 @@ +-- | Functions and types to support the other modules. Not for public use. +module Node.Buffer.Internal + ( unsafeFreeze + , unsafeThaw + , usingFromImmutable + , usingToImmutable + , create + , copyAll + , fromArray + , fromString + , fromArrayBuffer + , toArrayBuffer + , read + , readString + , toString + , write + , writeString + , toArray + , getAtOffset + , setAtOffset + , slice + , size + , concat + , concat' + , copy + , fill ) where + +import Prelude + +import Data.ArrayBuffer.Types (ArrayBuffer) +import Data.Maybe (Maybe) +import Node.Buffer.Immutable (ImmutableBuffer) +import Node.Buffer.Immutable as Immutable +import Node.Buffer.Types (BufferValueType, Octet, Offset) +import Node.Encoding (Encoding, encodingToNode) +import Unsafe.Coerce (unsafeCoerce) + +unsafeFreeze :: forall buf m. Monad m => buf -> m ImmutableBuffer +unsafeFreeze = pure <<< unsafeCoerce + +unsafeThaw :: forall buf m. Monad m => ImmutableBuffer -> m buf +unsafeThaw = pure <<< unsafeCoerce + +usingFromImmutable :: forall buf m a. Monad m => (ImmutableBuffer -> a) -> buf -> m a +usingFromImmutable f buf = f <$> unsafeFreeze buf + +usingToImmutable :: forall buf m a. Monad m => (a -> ImmutableBuffer) -> a -> m buf +usingToImmutable f x = unsafeThaw $ f x + +create :: forall buf m. Monad m => Int -> m buf +create = usingToImmutable Immutable.create + +foreign import copyAll :: forall a buf m. a -> m buf + +fromArray :: forall buf m. Monad m => Array Octet -> m buf +fromArray = usingToImmutable Immutable.fromArray + +fromString :: forall buf m. Monad m => String -> Encoding -> m buf +fromString s = usingToImmutable $ Immutable.fromString s + +fromArrayBuffer :: forall buf m. Monad m => ArrayBuffer -> m buf +fromArrayBuffer = usingToImmutable Immutable.fromArrayBuffer + +toArrayBuffer :: forall buf m. Monad m => buf -> m ArrayBuffer +toArrayBuffer = usingFromImmutable Immutable.toArrayBuffer + +read :: forall buf m. Monad m => BufferValueType -> Offset -> buf -> m Int +read t o = usingFromImmutable $ Immutable.read t o + +readString :: forall buf m. Monad m => Encoding -> Offset -> Offset -> buf -> m String +readString m o o' = usingFromImmutable $ Immutable.readString m o o' + +toString :: forall buf m. Monad m => Encoding -> buf -> m String +toString m = usingFromImmutable $ Immutable.toString m + +write :: forall buf m. Monad m => BufferValueType -> Int -> Offset -> buf -> m Unit +write = writeInternal <<< show + +foreign import writeInternal :: forall buf m. String -> Int -> Offset -> buf -> m Unit + +writeString :: forall buf m. Monad m => Encoding -> Offset -> Int -> String -> buf -> m Int +writeString = writeStringInternal <<< encodingToNode + +foreign import writeStringInternal :: + forall buf m. String -> Offset -> Int -> String -> buf -> m Int + +toArray :: forall buf m. Monad m => buf -> m (Array Octet) +toArray = usingFromImmutable Immutable.toArray + +getAtOffset :: forall buf m. Monad m => Offset -> buf -> m (Maybe Octet) +getAtOffset o = usingFromImmutable $ Immutable.getAtOffset o + +foreign import setAtOffset :: forall buf m. Octet -> Offset -> buf -> m Unit + +slice :: forall buf. Offset -> Offset -> buf -> buf +slice = unsafeCoerce Immutable.slice + +size :: forall buf m. Monad m => buf -> m Int +size = usingFromImmutable Immutable.size + +concat :: forall buf m. Array buf -> m buf +concat arrs = unsafeCoerce \_ -> Immutable.concat (unsafeCoerce arrs) + +concat' :: forall buf m. Monad m => Array buf -> Int -> m buf +concat' arrs n = unsafeCoerce \_ -> Immutable.concat' (unsafeCoerce arrs) n + +foreign import copy :: forall buf m. Offset -> Offset -> buf -> Offset -> buf -> m Int + +foreign import fill :: forall buf m. Octet -> Offset -> Offset -> buf -> m Unit diff --git a/src/Node/Buffer/ST.purs b/src/Node/Buffer/ST.purs new file mode 100644 index 0000000..ab13182 --- /dev/null +++ b/src/Node/Buffer/ST.purs @@ -0,0 +1,47 @@ +module Node.Buffer.ST + ( STBuffer + , run + ) where + +import Prelude + +import Control.Monad.ST (ST, kind Region) +import Control.Monad.ST as ST +import Node.Buffer.Class (class MutableBuffer, unsafeFreeze) +import Node.Buffer.Immutable (ImmutableBuffer) +import Node.Buffer.Internal as Internal + +-- | A reference to a mutable buffer for use with `ST` +-- | +-- | The type parameter represents the memory region which the buffer belongs to. +foreign import data STBuffer :: Region -> Type + +-- | Runs an effect creating an `STBuffer` then freezes the buffer and returns +-- | it, without unneccessary copying. +run :: (forall h. ST h (STBuffer h)) -> ImmutableBuffer +run st = ST.run (st >>= unsafeFreeze) + +instance mutableBufferST :: MutableBuffer (STBuffer h) (ST h) where + create = Internal.create + freeze = Internal.copyAll + unsafeFreeze = Internal.unsafeFreeze + thaw = Internal.copyAll + unsafeThaw = Internal.unsafeThaw + fromArray = Internal.fromArray + fromString = Internal.fromString + fromArrayBuffer = Internal.fromArrayBuffer + toArrayBuffer = Internal.toArrayBuffer + read = Internal.read + readString = Internal.readString + toString = Internal.toString + write = Internal.write + writeString = Internal.writeString + toArray = Internal.toArray + getAtOffset = Internal.getAtOffset + setAtOffset = Internal.setAtOffset + slice = Internal.slice + size = Internal.size + concat = Internal.concat + concat' = Internal.concat' + copy = Internal.copy + fill = Internal.fill diff --git a/src/Node/Buffer/Types.purs b/src/Node/Buffer/Types.purs new file mode 100644 index 0000000..c6b2489 --- /dev/null +++ b/src/Node/Buffer/Types.purs @@ -0,0 +1,47 @@ +module Node.Buffer.Types + ( Octet + , Offset + , BufferValueType(..) + ) where + +import Prelude + +-- | Type synonym indicating the value should be an octet (0-255). If the value +-- | provided is outside this range it will be used as modulo 256. +type Octet = Int + +-- | Type synonym indicating the value refers to an offset in a buffer. +type Offset = Int + +-- | Enumeration of the numeric types that can be written to a buffer. +data BufferValueType + = UInt8 + | UInt16LE + | UInt16BE + | UInt32LE + | UInt32BE + | Int8 + | Int16LE + | Int16BE + | Int32LE + | Int32BE + | FloatLE + | FloatBE + | DoubleLE + | DoubleBE + +instance showBufferValueType :: Show BufferValueType where + show UInt8 = "UInt8" + show UInt16LE = "UInt16LE" + show UInt16BE = "UInt16BE" + show UInt32LE = "UInt32LE" + show UInt32BE = "UInt32BE" + show Int8 = "Int8" + show Int16LE = "Int16LE" + show Int16BE = "Int16BE" + show Int32LE = "Int32LE" + show Int32BE = "Int32BE" + show FloatLE = "FloatLE" + show FloatBE = "FloatBE" + show DoubleLE = "DoubleLE" + show DoubleBE = "DoubleBE" diff --git a/src/Node/Buffer/Unsafe.js b/src/Node/Buffer/Unsafe.js deleted file mode 100644 index 139a70e..0000000 --- a/src/Node/Buffer/Unsafe.js +++ /dev/null @@ -1,11 +0,0 @@ -/* global exports */ -/* global Buffer */ -"use strict"; - -exports.slice = function (start) { - return function (end) { - return function (buff) { - return buff.slice(start, end); - }; - }; -}; diff --git a/src/Node/Buffer/Unsafe.purs b/src/Node/Buffer/Unsafe.purs deleted file mode 100644 index 930c214..0000000 --- a/src/Node/Buffer/Unsafe.purs +++ /dev/null @@ -1,10 +0,0 @@ -module Node.Buffer.Unsafe where - -import Node.Buffer - --- | Creates a new buffer slice that acts like a window on the original buffer. --- | Writing to the slice buffer updates the original buffer and vice-versa. --- --- | This is considered unsafe as writing to a slice can result in action at a --- | distance. -foreign import slice :: Offset -> Offset -> Buffer -> Buffer diff --git a/test/Main.purs b/test/Main.purs deleted file mode 100644 index cc4d548..0000000 --- a/test/Main.purs +++ /dev/null @@ -1,137 +0,0 @@ -module Test.Main where - -import Prelude -import Effect (Effect) -import Effect.Console (log) -import Data.Maybe (Maybe(..)) -import Data.Traversable (traverse) -import Node.Buffer (BufferValueType(..), toArray, concat', fromArray, fill, copy, readString, fromString, toString, read, write, create, getAtOffset) -import Node.Encoding (Encoding(..)) -import Test.Assert (assert') - -main :: Effect Unit -main = do - log "Testing..." - - log "Reading and writing" - testReadWrite - - log "fromArray" - testFromArray - - log "toArray" - testToArray - - log "fromString" - testFromString - - log "toString" - testToString - - log "readString" - testReadString - - log "copy" - testCopy - - log "fill" - testFill - - log "concat'" - testConcat' - - log "getAtOffset" - testGetAtOffset - -testReadWrite :: Effect Unit -testReadWrite = do - buf <- create 1 - let val = 42 - write UInt8 val 0 buf - readVal <- read UInt8 0 buf - - assertEq val readVal - -testFromArray :: Effect Unit -testFromArray = do - buf <- fromArray [1,2,3,4,5] - readVal <- read UInt8 2 buf - - assertEq 3 readVal - -testToArray :: Effect Unit -testToArray = do - let val = [1,2,67,3,3,7,8,3,4,237] - - buf <- fromArray val - valOut <- toArray buf - - assertEq val valOut - -testFromString :: Effect Unit -testFromString = do - let str = "hello, world" - - buf <- fromString str ASCII - val <- read UInt8 6 buf - - assertEq val 32 -- ASCII space - -testToString :: Effect Unit -testToString = do - let str = "hello, world" - - buf <- fromString str ASCII - strOut <- toString ASCII buf - - assertEq str strOut - -testReadString :: Effect Unit -testReadString = do - let str = "hello, world" - - buf <- fromString str ASCII - strOut <- readString ASCII 7 12 buf - - assertEq "world" strOut - -testCopy :: Effect Unit -testCopy = do - buf1 <- fromArray [1,2,3,4,5] - buf2 <- fromArray [10,9,8,7,6] - - copied <- copy 0 3 buf1 2 buf2 - out <- toArray buf2 - - assertEq copied 3 - assertEq out [10,9,1,2,3] - -testFill :: Effect Unit -testFill = do - buf <- fromArray [1,1,1,1,1] - fill 42 2 4 buf - out <- toArray buf - - assertEq [1,1,42,42,1] out - -testConcat' :: Effect Unit -testConcat' = do - bufs <- traverse fromArray $ map (\x -> [x, x+1, x+2]) [0,3,6,9,12] - buf <- concat' bufs 15 - out <- toArray buf - - assertEq [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14] out - -testGetAtOffset :: Effect Unit -testGetAtOffset = do - buf <- fromArray [1, 2, 3, 4] - assertEq (Just 2) =<< getAtOffset 1 buf - assertEq Nothing =<< getAtOffset 4 buf - assertEq Nothing =<< getAtOffset (-1) buf - -assertEq :: forall a. Eq a => Show a => a -> a -> Effect Unit -assertEq x y = - if x == y - then pure unit - else let msg = show x <> " and " <> show y <> " were not equal." - in assert' msg false diff --git a/test/Test/Main.purs b/test/Test/Main.purs new file mode 100644 index 0000000..7cdb68d --- /dev/null +++ b/test/Test/Main.purs @@ -0,0 +1,8 @@ +module Test.Main where + +import Prelude +import Effect (Effect) +import Test.Node.Buffer as Buffer + +main :: Effect Unit +main = Buffer.test diff --git a/test/Test/Node/Buffer.purs b/test/Test/Node/Buffer.purs new file mode 100644 index 0000000..85a537a --- /dev/null +++ b/test/Test/Node/Buffer.purs @@ -0,0 +1,20 @@ +module Test.Node.Buffer (test) where + +import Prelude + +import Effect (Effect) +import Effect.Console (log) +import Node.Buffer (Buffer) +import Test.Node.Buffer.Class (testMutableBuffer) +import Test.Node.Buffer.Immutable as Immutable +import Test.Node.Buffer.ST (test) as ST +import Type.Proxy (Proxy(..)) + +test :: Effect Unit +test = do + + log "Testing Node.Buffer ..." + testMutableBuffer (Proxy :: Proxy Buffer) identity + + ST.test + Immutable.test diff --git a/test/Test/Node/Buffer/Class.purs b/test/Test/Node/Buffer/Class.purs new file mode 100644 index 0000000..633179d --- /dev/null +++ b/test/Test/Node/Buffer/Class.purs @@ -0,0 +1,198 @@ +module Test.Node.Buffer.Class (testMutableBuffer) where + +import Prelude + +import Data.ArrayBuffer.Types (ArrayBuffer) +import Data.Maybe (Maybe(..)) +import Data.Traversable (traverse) +import Effect (Effect) +import Effect.Console (log) +import Node.Buffer (class MutableBuffer, BufferValueType(..), concat', copy, create, fill, freeze, fromArray, fromArrayBuffer, fromString, getAtOffset, read, readString, setAtOffset, slice, thaw, toArray, toArrayBuffer, toString, write) +import Node.Buffer.Immutable as Immutable +import Node.Encoding (Encoding(..)) +import Test.Assert (assertEqual) +import Type.Proxy (Proxy) + +testMutableBuffer :: forall buf m. MutableBuffer buf m => + Proxy buf -> (forall a. m a -> Effect a) -> Effect Unit +testMutableBuffer _ run = do + + log " - create" + testCreate + + log " - freeze" + testFreeze + + log " - thaw" + testThaw + + log " - Reading and writing" + testReadWrite + + log " - fromArray" + testFromArray + + log " - toArray" + testToArray + + log " - fromString" + testFromString + + log " - (to/from)ArrayBuffer" + testToFromArrayBuffer + + log " - toString" + testToString + + log " - readString" + testReadString + + log " - slice" + testSlice + + log " - copy" + testCopy + + log " - fill" + testFill + + log " - concat'" + testConcat' + + log " - getAtOffset" + testGetAtOffset + + where + testCreate :: Effect Unit + testCreate = do + buf <- run ((create 3 :: m buf) >>= toArray) + assertEqual {expected: [0, 0, 0], actual: buf} + + testFreeze :: Effect Unit + testFreeze = do + buf <- Immutable.toArray <$> run ((fromArray [1, 2, 3] :: m buf) >>= freeze) + assertEqual {expected: [1, 2, 3], actual: buf} + + testThaw :: Effect Unit + testThaw = do + buf <- run ((thaw (Immutable.fromArray [1, 2, 3]) :: m buf) >>= toArray) + assertEqual {expected: [1, 2, 3], actual: buf} + + testReadWrite :: Effect Unit + testReadWrite = do + let val = 42 + readVal <- run do + buf <- create 1 :: m buf + write UInt8 val 0 buf + read UInt8 0 buf + + assertEqual {expected: val, actual: readVal} + + testFromArray :: Effect Unit + testFromArray = do + readVal <- run do + buf <- fromArray [1,2,3,4,5] :: m buf + read UInt8 2 buf + + assertEqual {expected: 3, actual: readVal} + + testToArray :: Effect Unit + testToArray = do + let val = [1,2,67,3,3,7,8,3,4,237] + valOut <- run do + buf <- fromArray val :: m buf + toArray buf + + assertEqual {expected: val, actual: valOut} + + testFromString :: Effect Unit + testFromString = do + let str = "hello, world" + val <- run do + buf <- fromString str ASCII :: m buf + read UInt8 6 buf + + assertEqual {expected: 32, actual: val} -- ASCII space + + testToFromArrayBuffer :: Effect Unit + testToFromArrayBuffer = do + buf <- run $ + fromArray [1, 2, 3] + >>= (toArrayBuffer :: buf -> m ArrayBuffer) + >>= (fromArrayBuffer :: ArrayBuffer -> m buf) + >>= toArray + assertEqual {expected: [1, 2, 3], actual: buf} + + testToString :: Effect Unit + testToString = do + let str = "hello, world" + strOut <- run do + buf <- fromString str ASCII :: m buf + toString ASCII buf + + assertEqual {expected: str, actual: strOut} + + testReadString :: Effect Unit + testReadString = do + let str = "hello, world" + strOut <- run do + buf <- fromString str ASCII :: m buf + readString ASCII 7 12 buf + + assertEqual {expected: "world", actual: strOut} + + testSlice :: Effect Unit + testSlice = do + {bufArr, bufSliceArr} <- run do + buf <- fromArray [1, 2, 3, 4] :: m buf + let bufSlice = slice 1 3 buf + setAtOffset 42 1 bufSlice + bufArr <- toArray buf + bufSliceArr <- toArray bufSlice + pure {bufArr, bufSliceArr} + + assertEqual {expected: [1, 2, 42, 4], actual: bufArr} + assertEqual {expected: [2, 42], actual: bufSliceArr} + + testCopy :: Effect Unit + testCopy = do + {copied, out} <- run do + buf1 <- fromArray [1,2,3,4,5] :: m buf + buf2 <- fromArray [10,9,8,7,6] + copied <- copy 0 3 buf1 2 buf2 + out <- toArray buf2 + pure {copied, out} + + assertEqual {expected: 3, actual: copied} + assertEqual {expected: [10,9,1,2,3], actual: out} + + testFill :: Effect Unit + testFill = do + out <- run do + buf <- fromArray [1,1,1,1,1] :: m buf + fill 42 2 4 buf + toArray buf + + assertEqual {expected: [1,1,42,42,1], actual: out} + + testConcat' :: Effect Unit + testConcat' = do + out <- run do + bufs <- traverse (fromArray :: Array Int -> m buf) $ map (\x -> [x, x+1, x+2]) [0,3,6,9,12] + buf <- concat' bufs 15 + toArray buf + + assertEqual {expected: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14], actual: out} + + testGetAtOffset :: Effect Unit + testGetAtOffset = do + {o1, o4, om1} <- run do + buf <- fromArray [1, 2, 3, 4] :: m buf + o1 <- getAtOffset 1 buf + o4 <- getAtOffset 4 buf + om1 <- getAtOffset (-1) buf + pure {o1, o4, om1} + + assertEqual {expected: Just 2, actual: o1} + assertEqual {expected: Nothing, actual: o4} + assertEqual {expected: Nothing, actual: om1} diff --git a/test/Test/Node/Buffer/Immutable.purs b/test/Test/Node/Buffer/Immutable.purs new file mode 100644 index 0000000..4468d42 --- /dev/null +++ b/test/Test/Node/Buffer/Immutable.purs @@ -0,0 +1,130 @@ +module Test.Node.Buffer.Immutable (test) where + +import Prelude + +import Data.Maybe (Maybe(..)) +import Effect (Effect) +import Effect.Class.Console (log) +import Node.Buffer.Immutable as Immutable +import Node.Buffer.Immutable (ImmutableBuffer) +import Node.Buffer.Types (BufferValueType(..)) +import Node.Encoding (Encoding(..)) +import Test.Assert (assertEqual, assertTrue) + +test :: Effect Unit +test = do + log "Testing Node.Buffer.Immutable ..." + + log " - show" + testShow + + log " - eq" + testEq + + log " - compare" + testCompare + + log " - create" + testCreate + + log " - fromString" + testFromString + + log " - toString" + testToString + + log " - toArray" + testToArray + + log " - readString" + testReadString + + log " - getAtOffset" + testGetAtOffset + + log " - (to/from)ArrayBuffer" + testToFromArrayBuffer + + log " - concat'" + testConcat' + + log " - slice" + testSlice + + log " - size" + testSize + +buffer123 :: ImmutableBuffer +buffer123 = Immutable.fromArray [1, 2, 3] + +testShow :: Effect Unit +testShow = do + assertEqual {expected: "", actual: show buffer123} + +testEq :: Effect Unit +testEq = do + assertTrue $ buffer123 == buffer123 + assertTrue $ buffer123 == Immutable.fromArray [1, 2, 3] + assertTrue $ buffer123 /= Immutable.fromArray [1, 2, 4] + assertTrue $ buffer123 /= Immutable.fromArray [1, 2] + +testCompare :: Effect Unit +testCompare = do + assertEqual {expected: EQ, actual: compare buffer123 buffer123} + assertEqual {expected: LT, actual: compare buffer123 $ Immutable.fromArray [3, 2, 1]} + assertEqual {expected: GT, actual: compare buffer123 $ Immutable.fromArray [0, 1, 2]} + +testCreate :: Effect Unit +testCreate = do + assertEqual {expected: Immutable.fromArray [], actual: Immutable.create 0} + assertEqual {expected: Immutable.fromArray [0, 0, 0], actual: Immutable.create 3} + +testFromString :: Effect Unit +testFromString = do + let buf = Immutable.fromString "hello, world" ASCII + assertEqual {expected: 32, actual: Immutable.read UInt8 6 buf} + +testToString :: Effect Unit +testToString = do + let str = "hello, world" + str' = Immutable.toString ASCII $ Immutable.fromString str ASCII + assertEqual {expected: str, actual: str'} + +testToArray :: Effect Unit +testToArray = do + assertEqual {expected: [1, 2, 3], actual: Immutable.toArray buffer123} + +testReadString :: Effect Unit +testReadString = do + let str = "hello, world" + str' = Immutable.readString ASCII 7 12 $ Immutable.fromString str ASCII + assertEqual {expected: "world", actual: str'} + +testGetAtOffset :: Effect Unit +testGetAtOffset = do + assertEqual {expected: Just 2, actual: Immutable.getAtOffset 1 buffer123} + assertEqual {expected: Nothing, actual: Immutable.getAtOffset 99 buffer123} + assertEqual {expected: Nothing, actual: Immutable.getAtOffset (-1) buffer123} + +testToFromArrayBuffer :: Effect Unit +testToFromArrayBuffer = do + assertEqual {expected: buffer123, actual: Immutable.fromArrayBuffer $ Immutable.toArrayBuffer buffer123} + +testConcat' :: Effect Unit +testConcat' = do + let bufs = map (\x -> Immutable.fromArray [x, x+1, x+2]) [0,3,6,9,12] + buf = Immutable.concat' bufs 15 + out = Immutable.toArray buf + + assertEqual {expected: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14], actual: out} + +testSlice :: Effect Unit +testSlice = do + assertEqual {expected: buffer123, actual: Immutable.slice 0 3 buffer123} + assertEqual {expected: buffer123, actual: Immutable.slice 0 4 buffer123} + assertEqual {expected: Immutable.fromArray [2], actual: Immutable.slice 1 2 buffer123} + +testSize :: Effect Unit +testSize = do + assertEqual {expected: 0, actual: Immutable.size $ Immutable.fromArray []} + assertEqual {expected: 3, actual: Immutable.size buffer123} diff --git a/test/Test/Node/Buffer/ST.purs b/test/Test/Node/Buffer/ST.purs new file mode 100644 index 0000000..783f835 --- /dev/null +++ b/test/Test/Node/Buffer/ST.purs @@ -0,0 +1,26 @@ +module Test.Node.Buffer.ST (test) where + +import Prelude + +import Control.Monad.ST (run) as ST +import Effect (Effect) +import Effect.Console (log) +import Node.Buffer.Class (create) +import Node.Buffer.Immutable as Immutable +import Node.Buffer.ST (STBuffer, run) +import Test.Assert (assertEqual) +import Test.Node.Buffer.Class (testMutableBuffer) +import Type.Proxy (Proxy(..)) +import Unsafe.Coerce (unsafeCoerce) + +test :: Effect Unit +test = do + log "Testing Node.Buffer.ST ..." + testMutableBuffer (Proxy :: Proxy (STBuffer _)) unsafeCoerce + log " - run" + testRun + +testRun :: Effect Unit +testRun = do + let buf = Immutable.toArray $ run (create 3) + assertEqual {expected: [0, 0, 0], actual: buf}