⅄ trunk → topic/merge4

This commit is contained in:
Mitchell Rosen 2024-05-13 10:36:03 -04:00
commit e18bbd40f9
65 changed files with 2112 additions and 1022 deletions

3
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,3 @@
# Require approval from ucm team when editing repository workflows
# This helps prevent users from sneaking in malicious changes to CI workflows.
/.github/ @unisonweb/ucm

View File

@ -0,0 +1,20 @@
name: Contributor signed CONTRIBUTORS.markdown
on:
pull_request:
jobs:
check-contributor:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: CONTRIBUTORS.markdown
- name: Look for @${{github.event.pull_request.user.login}} in CONTRIBUTORS.markdown
shell: bash
run: |
echo "If this step fails, make sure you've added yourself to CONTRIBUTORS.markdown"
echo "to acknowledge Unison's MIT license."
egrep '\* .* \(@${{github.event.pull_request.user.login}}\)' \
CONTRIBUTORS.markdown

View File

@ -54,13 +54,13 @@ jobs:
with:
name: jit-source
path: ${{ env.jit_src }}
- name: cache/restore jit binaries
id: cache-jit-binaries
uses: actions/cache/restore@v4
with:
path: ${{ env.jit_dist }}
key: jit-dist_${{matrix.os}}.racket_${{env.racket_version}}.jit-src_${{hashFiles(format('{0}/**.rkt',env.jit_src),format('{0}/**.ss',env.jit_src))}}.yaml_${{hashFiles('**/ci-build-jit-binary.yaml')}}
key: jit-dist_${{matrix.os}}.racket_${{env.racket_version}}.jit-src_${{hashFiles(format('{0}/**/*.rkt',env.jit_src),format('{0}/**/*.ss',env.jit_src))}}.yaml_${{hashFiles('**/ci-build-jit-binary.yaml')}}
- name: cache racket dependencies
if: steps.cache-jit-binaries.outputs.cache-hit != 'true'
@ -81,8 +81,18 @@ jobs:
if: runner.os == 'macOS' && steps.cache-jit-binaries.outputs.cache-hit != 'true'
run: |
brew install libb2
racket_lib_dir="$(dirname "$(readlink -f "$(which raco)")")/../lib"
ln -s "$(brew --prefix)"/lib/libb2.*.dylib "$racket_lib_dir/"
brew_lib_dir=$(brew --prefix)/lib
racket_lib_dir=$(dirname $(dirname $(readlink -f $(which raco))))/lib
# link libb2 if not already present/cached
for dll in $brew_lib_dir/libb2.*.dylib; do
file=$(basename "$dll")
if [ ! -e "$racket_lib_dir/$file" ]; then
ln -s "$brew_lib_dir/$file" "$racket_lib_dir/$file"
else
echo "$racket_lib_dir/$file" already exists.
fi
done
- name: build jit binary
if: steps.cache-jit-binaries.outputs.cache-hit != 'true'
@ -104,7 +114,7 @@ jobs:
uses: actions/cache/save@v4
with:
path: ${{ env.jit_dist }}
key: jit-dist_${{matrix.os}}.racket_${{env.racket_version}}.jit-src_${{hashFiles(format('{0}/**.rkt',env.jit_src),format('{0}/**.ss',env.jit_src))}}.yaml_${{hashFiles('**/ci-build-jit-binary.yaml')}}
key: jit-dist_${{matrix.os}}.racket_${{env.racket_version}}.jit-src_${{hashFiles(format('{0}/**/*.rkt',env.jit_src),format('{0}/**/*.ss',env.jit_src))}}.yaml_${{hashFiles('**/ci-build-jit-binary.yaml')}}
- name: save jit binary
uses: actions/upload-artifact@v4

View File

@ -9,7 +9,7 @@ At a high level, the CI process is:
Some version numbers that are used during CI:
- `ormolu_version: "0.5.0.1"`
- `racket_version: "8.7"`
- `jit_version: "@unison/internal/releases/0.0.15"`
- `jit_version: "@unison/internal/releases/0.0.16"`
Some cached directories:
- `ucm_local_bin` a temp path for caching a built `ucm`

View File

@ -20,7 +20,7 @@ on:
env:
ormolu_version: 0.5.2.0
ucm_local_bin: ucm-local-bin
jit_version: "@unison/internal/releases/0.0.15"
jit_version: "@unison/internal/releases/0.0.16"
jit_src_scheme: unison-jit-src/scheme-libs/racket
jit_dist: unison-jit-dist
jit_generator_os: ubuntu-20.04

View File

@ -82,3 +82,7 @@ The format for this list: name, GitHub handle
* Kyle Goetz (@kylegoetz)
* Ethan Morgan (@sixfourtwelve)
* Johan Winther (@JohanWinther)
* Greg Pfeil (@sellout)
* Upendra Upadhyay (@upendra1997)
* Dan Doel (@dolio)
* Eric Torreborre (@etorreborre)

View File

@ -15,7 +15,7 @@ be listed here, please [file a ticket](https://github.com/unisonweb/unison/issue
This file was generated using [unisonweb/credits-generator](http://github.com/unisonweb/credits-generator).
### Listing
### Listing
These are listed in alphabetical order.
| Package name | License |
@ -109,6 +109,7 @@ These are listed in alphabetical order.
| [network-bsd-2.8.1.0](https://hackage.haskell.org/package/network-bsd-2.8.1.0) | [BSD3](https://hackage.haskell.org/package/network-bsd-2.8.1.0/src/LICENSE) |
| [network-info-0.2.0.10](https://hackage.haskell.org/package/network-info-0.2.0.10) | [BSD3](https://hackage.haskell.org/package/network-info-0.2.0.10/src/LICENSE) |
| [network-simple-0.4.5](https://hackage.haskell.org/package/network-simple-0.4.5) | [BSD3](https://hackage.haskell.org/package/network-simple-0.4.5/src/LICENSE) |
| [network-udp-0.0.0](https://hackage.haskell.org/package/network-udp-0.0.0) | [BSD3](https://hackage.haskell.org/package/network-udp-0.0.0/src/LICENSE) |
| [nonempty-containers-0.3.3.0](https://hackage.haskell.org/package/nonempty-containers-0.3.3.0) | [BSD3](https://hackage.haskell.org/package/nonempty-containers-0.3.3.0/src/LICENSE) |
| [nonempty-vector-0.2.0.2](https://hackage.haskell.org/package/nonempty-vector-0.2.0.2) | [BSD3](https://hackage.haskell.org/package/nonempty-vector-0.2.0.2/src/LICENSE) |
| [parallel-3.2.2.0](https://hackage.haskell.org/package/parallel-3.2.2.0) | [BSD3](https://hackage.haskell.org/package/parallel-3.2.2.0/src/LICENSE) |

View File

@ -42,7 +42,7 @@ Some tests are executables instead:
* `stack exec transcripts` runs the transcripts-related integration tests, found in `unison-src/transcripts`. You can add more tests to this directory.
* `stack exec transcripts -- prefix-of-filename` runs only transcript tests with a matching filename prefix.
* `stack exec integration-tests` runs the additional integration tests for cli. These tests are not triggered by `tests` or `trancscripts`.
* `stack exec integration-tests` runs the additional integration tests for cli. These tests are not triggered by `tests` or `transcripts`.
* `stack exec unison -- transcript unison-src/transcripts-round-trip/main.md` runs the pretty-printing round trip tests
* `stack exec unison -- transcript unison-src/transcripts-manual/benchmarks.md` runs the benchmark suite. Output goes in unison-src/transcripts-manual/benchmarks/output.txt.

View File

@ -4,9 +4,12 @@
* [Overview](#overview)
* [Installation and setup](#installation-and-setup)
* [Settings](#settings)
* [NeoVim](#neovim)
* [VSCode](#vscode)
* [Helix Editor](#helix-editor)
* [Emacs](#emacs)
* [other editors](#other-editors)
* [Configuration](#configuration)
## Overview
@ -31,7 +34,7 @@ Note for Windows users: Due to an outstanding issue with GHC's IO manager on Win
Enabling the LSP on windows can cause UCM to hang on exit and may require the process to be killed by the operating system or via Ctrl-C.
Note that this doesn't pose any risk of codebase corruption or cause any known issues, it's simply an annoyance.
If you accept this annoyance, you can enable the LSP server on Windows by exporting the `UNISON_LSP_ENABLED=true` environment variable.
If you accept this annoyance, you can enable the LSP server on Windows by exporting the `UNISON_LSP_ENABLED=true` environment variable.
You can set this persistently in powershell using:
@ -41,17 +44,6 @@ You can set this persistently in powershell using:
See [this issue](https://github.com/unisonweb/unison/issues/3487) for more details.
### Settings
Supported settings and their defaults. See information for your language server client about where to provide these.
```json
{
// A suggestion for the formatter about how wide (in columns) to print definitions.
"formattingWidth": 80
}
```
### NeoVim
Before configuring the LSP, install the Vim plugin for filetype detection and syntax highlighting.
@ -193,6 +185,18 @@ language-servers = [ "ucm" ]
or follow the instructions for Unison in "[How to install the default language servers](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers#unison)" wiki page.
### Emacs
In Emacs 29 (or earlier, if you install the [Eglot](https://elpa.gnu.org/packages/eglot.html) package), add the following to your init file:
```elisp
(push '((unison-ts-mode unisonlang-mode) "127.0.0.1" 5757)
eglot-server-programs)
```
This requires having either [unison-ts-mode](https://github.com/fmguerreiro/unison-ts-mode) or [unisonlang-mode](https://melpa.org/#/unisonlang-mode) installed. unison-ts-mode is newer, supported, and more complete, but isnt in [MELPA](https://melpa.org/) yet and requires a couple commands to set up [tree-sitter-unison](https://github.com/kylegoetz/tree-sitter-unison).
You can then use `M-x eglot` in a Unison scratch file buffer. You can also [configure Eglot to start automatically](https://www.gnu.org/software/emacs/manual/html_node/eglot/Starting-Eglot.html).
### Other Editors
@ -205,11 +209,14 @@ Note that some editors require passing the command and arguments as separate par
Supported settings and their defaults. See information for your language server client about where to provide these.
* `formattingWidth`: A suggestion for the formatter about how wide (in columns) to print definitions.
* `maxCompletions`: The number of completions the server should collect and send based on a single query. Increasing this limit will provide more completion results, but at the cost of being slower to respond.
If explicitly set to `null` the server will return ALL completions available.
```json
{
// The number of completions the server should collect and send based on a single query.
// Increasing this limit will provide more completion results, but at the cost of being slower to respond.
// If explicitly set to `null` the server will return ALL completions available.
"formattingWidth": 80,
"maxCompletions": 100
}
```

View File

@ -173,7 +173,7 @@ defaultColors :: ST.Element r -> Maybe Color
defaultColors = \case
ST.NumericLiteral -> Nothing
ST.TextLiteral -> Nothing
ST.BytesLiteral -> Just HiBlack
ST.BytesLiteral -> Just HiPurple
ST.CharLiteral -> Nothing
ST.BooleanLiteral -> Nothing
ST.Blank -> Nothing
@ -182,21 +182,21 @@ defaultColors = \case
ST.TermReference _ -> Nothing
ST.Op _ -> Nothing
ST.Unit -> Nothing
ST.AbilityBraces -> Just HiBlack
ST.ControlKeyword -> Just Bold
ST.LinkKeyword -> Just HiBlack
ST.TypeOperator -> Just HiBlack
ST.AbilityBraces -> Just HiPurple
ST.ControlKeyword -> Just HiCyan
ST.LinkKeyword -> Just HiPurple
ST.TypeOperator -> Just HiPurple
ST.BindingEquals -> Nothing
ST.TypeAscriptionColon -> Just Blue
ST.DataTypeKeyword -> Nothing
ST.DataTypeParams -> Nothing
ST.DataTypeModifier -> Nothing
ST.UseKeyword -> Just HiBlack
ST.UsePrefix -> Just HiBlack
ST.UseSuffix -> Just HiBlack
ST.HashQualifier _ -> Just HiBlack
ST.UseKeyword -> Just HiPurple
ST.UsePrefix -> Just HiPurple
ST.UseSuffix -> Just HiPurple
ST.HashQualifier _ -> Just HiPurple
ST.DelayForceChar -> Just Yellow
ST.DelimiterChar -> Nothing
ST.Parenthesis -> Nothing
ST.DocDelimiter -> Just Green
ST.DocKeyword -> Just Bold
ST.DocKeyword -> Just HiCyan

View File

@ -23,6 +23,8 @@ dependencies:
- NanoID
- aeson
- ansi-terminal
- asn1-encoding
- asn1-types
- async
- atomic-primops
- base
@ -60,6 +62,7 @@ dependencies:
- http-media
- http-types
- IntervalMap
- iproute
- lens
- lucid
- megaparsec
@ -73,6 +76,7 @@ dependencies:
- natural-transformation
- network
- network-simple
- network-udp
- network-uri
- nonempty-containers
- open-browser
@ -158,6 +162,7 @@ tests:
- easytest
- filemanip
- split
- hex-text
- unison-parser-typechecker
when:
- condition: false

View File

@ -246,7 +246,10 @@ builtinTypesSrc =
B' "MutableArray" CT.Data,
B' "ImmutableByteArray" CT.Data,
B' "MutableByteArray" CT.Data,
B' "Char.Class" CT.Data
B' "Char.Class" CT.Data,
B' "UDPSocket" CT.Data,
B' "ListenSocket" CT.Data,
B' "ClientSockAddr" CT.Data
]
-- rename these to "builtin" later, when builtin means intrinsic as opposed to
@ -772,6 +775,10 @@ cryptoBuiltins =
[ B "crypto.Ed25519.sign.impl" $
bytes --> bytes --> bytes --> eithert failure bytes,
B "crypto.Ed25519.verify.impl" $
bytes --> bytes --> bytes --> eithert failure boolean,
B "crypto.Rsa.sign.impl" $
bytes --> bytes --> eithert failure bytes,
B "crypto.Rsa.verify.impl" $
bytes --> bytes --> bytes --> eithert failure boolean
]
@ -815,6 +822,17 @@ ioBuiltins =
("IO.serverSocket.impl.v3", optionalt text --> text --> iof socket),
("IO.listen.impl.v3", socket --> iof unit),
("IO.clientSocket.impl.v3", text --> text --> iof socket),
("IO.UDP.clientSocket.impl.v1", text --> text --> iof udpSocket),
("IO.UDP.ClientSockAddr.toText.v1", udpClientSockAddr --> text),
("IO.UDP.UDPSocket.toText.impl.v1", udpSocket --> text),
("IO.UDP.UDPSocket.close.impl.v1", udpSocket --> iof unit),
("IO.UDP.serverSocket.impl.v1", text --> text --> iof udpListenSocket),
("IO.UDP.ListenSocket.recvFrom.impl.v1", udpListenSocket --> iof (tuple [bytes, udpClientSockAddr])),
("IO.UDP.ListenSocket.sendTo.impl.v1", udpListenSocket --> bytes --> udpClientSockAddr --> iof unit),
("IO.UDP.ListenSocket.toText.impl.v1", udpListenSocket --> text),
("IO.UDP.ListenSocket.close.impl.v1", udpListenSocket --> iof unit),
("IO.UDP.UDPSocket.recv.impl.v1", udpSocket --> iof bytes),
("IO.UDP.UDPSocket.send.impl.v1", udpSocket --> bytes --> iof unit),
("IO.closeSocket.impl.v3", socket --> iof unit),
("IO.socketPort.impl.v3", socket --> iof nat),
("IO.socketAccept.impl.v3", socket --> iof socket),
@ -1055,6 +1073,12 @@ handle = Type.fileHandle ()
phandle = Type.processHandle ()
unit = DD.unitType ()
udpSocket, udpListenSocket, udpClientSockAddr :: Type
udpSocket = Type.udpSocket ()
udpListenSocket = Type.udpListenSocket ()
udpClientSockAddr = Type.udpClientSockAddr ()
tls, tlsClientConfig, tlsServerConfig, tlsSignedCert, tlsPrivateKey, tlsVersion, tlsCipher :: Type
tls = Type.ref () Type.tlsRef
tlsClientConfig = Type.ref () Type.tlsClientConfigRef

View File

@ -356,6 +356,9 @@ builtinConstraintTree =
flip Type.ref Type.filePathRef,
Type.threadId,
Type.socket,
Type.udpSocket,
Type.udpListenSocket,
Type.udpClientSockAddr,
Type.processHandle,
Type.ibytearrayType,
flip Type.ref Type.charClassRef,

View File

@ -39,6 +39,7 @@ import Crypto.Error (CryptoError (..), CryptoFailable (..))
import Crypto.Hash qualified as Hash
import Crypto.MAC.HMAC qualified as HMAC
import Crypto.PubKey.Ed25519 qualified as Ed25519
import Crypto.PubKey.RSA.PKCS15 qualified as RSA
import Crypto.Random (getRandomBytes)
import Data.Bits (shiftL, shiftR, (.|.))
import Data.ByteArray qualified as BA
@ -52,6 +53,7 @@ import Data.IORef as SYS
readIORef,
writeIORef,
)
import Data.IP (IP)
import Data.Map qualified as Map
import Data.PEM (PEM, pemContent, pemParseLBS)
import Data.Set (insert)
@ -81,9 +83,23 @@ import Network.Simple.TCP as SYS
import Network.Socket as SYS
( Socket,
accept,
socketPort,
socketPort, PortNumber,
)
import Network.TLS as TLS
import Network.UDP as UDP
( UDPSocket (..),
ClientSockAddr,
ListenSocket,
clientSocket,
close,
recv,
recvFrom,
send,
sendTo,
serverSocket,
stop,
)
import Network.TLS.Extra.Cipher as Cipher
import System.Clock (Clock (..), getTime, nsec, sec)
import System.Directory as SYS
@ -138,6 +154,7 @@ import System.Process as SYS
)
import System.X509 qualified as X
import Unison.ABT.Normalized hiding (TTm)
import Unison.Runtime.Crypto.Rsa as Rsa
import Unison.Builtin qualified as Ty (builtinTypes)
import Unison.Builtin.Decls qualified as Ty
import Unison.Prelude hiding (Text, some)
@ -1544,6 +1561,22 @@ outIoFailBool stack1 stack2 stack3 extra fail result =
)
]
outIoFailTup :: forall v . (Var v) => v -> v -> v -> v -> v -> v -> v -> v -> ANormal v
outIoFailTup stack1 stack2 stack3 stack4 stack5 extra fail result =
TMatch result . MatchSum $
mapFromList
[ failureCase stack1 stack2 stack3 extra fail,
( 1,
([BX, BX],
TAbss [stack1, stack2]
. TLetD stack3 BX (TCon Ty.unitRef 0 [])
. TLetD stack4 BX (TCon Ty.pairRef 0 [stack2, stack3])
. TLetD stack5 BX (TCon Ty.pairRef 0 [stack1, stack4])
$ right stack5
)
)
]
outIoFailG ::
(Var v) =>
v ->
@ -1767,6 +1800,14 @@ boxToEFBox =
where
(arg, result, stack1, stack2, stack3, any, fail) = fresh
-- a -> Either Failure (b, c)
boxToEFTup :: ForeignOp
boxToEFTup =
inBx arg result $
outIoFailTup stack1 stack2 stack3 stack4 stack5 extra fail result
where
(arg, result, stack1, stack2, stack3, stack4, stack5, extra, fail) = fresh
-- a -> Either Failure (Maybe b)
boxToEFMBox :: ForeignOp
boxToEFMBox =
@ -1858,6 +1899,14 @@ boxBoxToEF0 =
where
(arg1, arg2, result, stack1, stack2, stack3, fail, unit) = fresh
-- a -> b -> c -> Either Failure ()
boxBoxBoxToEF0 :: ForeignOp
boxBoxBoxToEF0 =
inBxBxBx arg1 arg2 arg3 result $
outIoFailUnit stack1 stack2 stack3 fail unit result
where
(arg1, arg2, arg3, result, stack1, stack2, stack3, fail, unit) = fresh
-- a -> Either Failure Nat
boxToEFNat :: ForeignOp
boxToEFNat =
@ -2290,8 +2339,64 @@ mkForeignTlsE f = mkForeign $ \a -> fmap flatten (tryIO2 (tryIO1 (f a)))
flatten (Right (Right (Left e))) = Left e
flatten (Right (Right (Right a))) = Right a
declareUdpForeigns :: FDecl Symbol ()
declareUdpForeigns = do
declareForeign Tracked "IO.UDP.clientSocket.impl.v1" boxBoxToEFBox
. mkForeignIOF
$ \(host :: Util.Text.Text, port :: Util.Text.Text) ->
let hostStr = Util.Text.toString host
portStr = Util.Text.toString port
in UDP.clientSocket hostStr portStr True
declareForeign Tracked "IO.UDP.UDPSocket.recv.impl.v1" boxToEFBox
. mkForeignIOF
$ \(sock :: UDPSocket) -> Bytes.fromArray <$> UDP.recv sock
declareForeign Tracked "IO.UDP.UDPSocket.send.impl.v1" boxBoxToEF0
. mkForeignIOF
$ \(sock :: UDPSocket, bytes :: Bytes.Bytes) ->
UDP.send sock (Bytes.toArray bytes)
declareForeign Tracked "IO.UDP.UDPSocket.close.impl.v1" boxToEF0
. mkForeignIOF
$ \(sock :: UDPSocket) -> UDP.close sock
declareForeign Tracked "IO.UDP.ListenSocket.close.impl.v1" boxToEF0
. mkForeignIOF
$ \(sock :: ListenSocket) -> UDP.stop sock
declareForeign Tracked "IO.UDP.UDPSocket.toText.impl.v1" boxDirect
. mkForeign
$ \(sock :: UDPSocket) -> pure $ show sock
declareForeign Tracked "IO.UDP.serverSocket.impl.v1" boxBoxToEFBox
. mkForeignIOF
$ \(ip :: Util.Text.Text, port :: Util.Text.Text) ->
let maybeIp = readMaybe $ Util.Text.toString ip :: Maybe IP
maybePort = readMaybe $ Util.Text.toString port :: Maybe PortNumber
in case (maybeIp, maybePort) of
(Nothing, _) -> fail "Invalid IP Address"
(_, Nothing) -> fail "Invalid Port Number"
(Just ip, Just pt) -> UDP.serverSocket (ip, pt)
declareForeign Tracked "IO.UDP.ListenSocket.toText.impl.v1" boxDirect
. mkForeign
$ \(sock :: ListenSocket) -> pure $ show sock
declareForeign Tracked "IO.UDP.ListenSocket.recvFrom.impl.v1" boxToEFTup .
mkForeignIOF $ fmap (first Bytes.fromArray) <$> UDP.recvFrom
declareForeign Tracked "IO.UDP.ClientSockAddr.toText.v1" boxDirect
. mkForeign
$ \(sock :: ClientSockAddr) -> pure $ show sock
declareForeign Tracked "IO.UDP.ListenSocket.sendTo.impl.v1" boxBoxBoxToEF0 .
mkForeignIOF $ \(socket :: ListenSocket, bytes :: Bytes.Bytes, addr :: ClientSockAddr) ->
UDP.sendTo socket (Bytes.toArray bytes) addr
declareForeigns :: FDecl Symbol ()
declareForeigns = do
declareUdpForeigns
declareForeign Tracked "IO.openFile.impl.v3" boxIomrToEFBox $
mkForeignIOF $ \(fnameText :: Util.Text.Text, n :: Int) ->
let fname = Util.Text.toString fnameText
@ -2830,6 +2935,14 @@ declareForeigns = do
. mkForeign
$ pure . verifyEd25519Wrapper
declareForeign Untracked "crypto.Rsa.sign.impl" boxBoxToEFBox
. mkForeign
$ pure . signRsaWrapper
declareForeign Untracked "crypto.Rsa.verify.impl" boxBoxBoxToEFBool
. mkForeign
$ pure . verifyRsaWrapper
let catchAll :: (MonadCatch m, MonadIO m, NFData a) => m a -> m (Either Util.Text.Text a)
catchAll e = do
e <- Exception.tryAnyDeep e
@ -3471,6 +3584,31 @@ verifyEd25519Wrapper (public0, msg0, sig0) = case validated of
"ed25519: Secret key structure invalid"
errMsg _ = "ed25519: unexpected error"
signRsaWrapper ::
(Bytes.Bytes, Bytes.Bytes) -> Either Failure Bytes.Bytes
signRsaWrapper (secret0, msg0) = case validated of
Left err ->
Left (Failure Ty.cryptoFailureRef err unitValue)
Right secret ->
case RSA.sign Nothing (Just Hash.SHA256) secret msg of
Left err -> Left (Failure Ty.cryptoFailureRef (Rsa.rsaErrorToText err) unitValue)
Right signature -> Right $ Bytes.fromByteString signature
where
msg = Bytes.toArray msg0 :: ByteString
validated = Rsa.parseRsaPrivateKey (Bytes.toArray secret0 :: ByteString)
verifyRsaWrapper ::
(Bytes.Bytes, Bytes.Bytes, Bytes.Bytes) -> Either Failure Bool
verifyRsaWrapper (public0, msg0, sig0) = case validated of
Left err ->
Left $ Failure Ty.cryptoFailureRef err unitValue
Right public ->
Right $ RSA.verify (Just Hash.SHA256) public msg sig
where
msg = Bytes.toArray msg0 :: ByteString
sig = Bytes.toArray sig0 :: ByteString
validated = Rsa.parseRsaPublicKey (Bytes.toArray public0 :: ByteString)
typeReferences :: [(Reference, Word64)]
typeReferences = zip rs [1 ..]
where

View File

@ -0,0 +1,127 @@
module Unison.Runtime.Crypto.Rsa (
parseRsaPublicKey,
parseRsaPrivateKey,
rsaErrorToText,
) where
import Crypto.Number.Basic qualified as Crypto
import Crypto.PubKey.RSA qualified as RSA
import Data.ASN1.BinaryEncoding qualified as ASN1
import Data.ASN1.BitArray qualified as ASN1
import Data.ASN1.Encoding qualified as ASN1
import Data.ASN1.Error qualified as ASN1
import Data.ASN1.Types qualified as ASN1
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BSL
import Unison.Util.Text (Text)
import Unison.Util.Text qualified as Util.Text
-- | Parse a RSA public key from a ByteString
-- The input bytestring is a hex-encoded string of the DER file for the public key.
-- It can be generated with those commands:
-- # generate a RSA key of a given size
-- openssl genrsa -out private_key.pem <size>
-- # output the DER format as a hex string
-- openssl rsa -in private_key.pem -outform DER -pubout | xxd -p
parseRsaPublicKey :: BS.ByteString -> Either Text RSA.PublicKey
parseRsaPublicKey bs = case ASN1.decodeASN1 ASN1.DER (BSL.fromStrict bs) of
Left err -> Left $ "rsa: cannot decode as an ASN.1 structure. " <> asn1ErrorToText err
Right asn1 ->
case asn1 of
[ ASN1.Start ASN1.Sequence,
ASN1.Start ASN1.Sequence,
ASN1.OID _,
ASN1.Null,
ASN1.End ASN1.Sequence,
ASN1.BitString (ASN1.BitArray _ bits),
ASN1.End ASN1.Sequence
] -> case ASN1.decodeASN1 ASN1.DER (BSL.fromStrict bits) of
Left err -> Left $ "rsa: cannot decode as an ASN.1 inner structure. " <> asn1ErrorToText err
Right asn1 -> case asn1 of
[ASN1.Start ASN1.Sequence, ASN1.IntVal n, ASN1.IntVal e, ASN1.End ASN1.Sequence] ->
Right
RSA.PublicKey
{ public_size = Crypto.numBytes n,
public_n = n,
public_e = e
}
other -> Left ("rsa: unexpected ASN.1 inner structure for a RSA public key" <> Util.Text.pack (show other))
other -> Left ("rsa: unexpected ASN.1 outer structure for a RSA public key" <> Util.Text.pack (show other))
-- | Parse a RSA private key from a ByteString
-- The input bytestring is a hex-encoded string of the DER file for the private key.
-- It can be generated with those commands:
-- # generate a RSA key of a given size
-- openssl genrsa -out private_key.pem <size>
-- # output the DER format as a hex string
-- openssl rsa -in private_key.pem -outform DER | xxd -p
parseRsaPrivateKey :: BS.ByteString -> Either Text RSA.PrivateKey
parseRsaPrivateKey bs = case ASN1.decodeASN1 ASN1.DER (BSL.fromStrict bs) of
Left err -> Left $ "Error decoding ASN.1: " <> asn1ErrorToText err
Right asn1 ->
case asn1 of
[ ASN1.Start ASN1.Sequence,
ASN1.IntVal 0,
ASN1.Start ASN1.Sequence,
ASN1.OID _,
ASN1.Null,
ASN1.End ASN1.Sequence,
ASN1.OctetString bits,
ASN1.End ASN1.Sequence
] ->
case ASN1.decodeASN1 ASN1.DER (BSL.fromStrict bits) of
Left err -> Left $ "Error decoding inner ASN.1: " <> asn1ErrorToText err
Right asn1 ->
case asn1 of
[ ASN1.Start ASN1.Sequence,
ASN1.IntVal _,
ASN1.IntVal n,
ASN1.IntVal e,
ASN1.IntVal d,
ASN1.IntVal p,
ASN1.IntVal q,
ASN1.IntVal dP,
ASN1.IntVal dQ,
ASN1.IntVal qinv,
ASN1.End ASN1.Sequence
] ->
Right
RSA.PrivateKey
{ private_pub = RSA.PublicKey {public_size = Crypto.numBytes n, public_n = n, public_e = e},
private_d = d,
private_p = p,
private_q = q,
private_dP = dP,
private_dQ = dQ,
private_qinv = qinv
}
other -> Left ("rsa: unexpected inner ASN.1 structure for a RSA private key" <> Util.Text.pack (show other))
other -> Left ("rsa: unexpected outer ASN.1 structure for a RSA private key" <> Util.Text.pack (show other))
-- | Display an ASN1 Error
asn1ErrorToText :: ASN1.ASN1Error -> Text
asn1ErrorToText = \case
ASN1.StreamUnexpectedEOC -> "Unexpected EOC in the stream"
ASN1.StreamInfinitePrimitive -> "Invalid primitive with infinite length in a stream"
ASN1.StreamConstructionWrongSize -> "A construction goes over the size specified in the header"
ASN1.StreamUnexpectedSituation s -> "An unexpected situation has come up parsing an ASN1 event stream: " <> Util.Text.pack s
ASN1.ParsingHeaderFail s -> "Parsing an invalid header: " <> Util.Text.pack s
ASN1.ParsingPartial -> "Parsing is not finished, the key is not complete"
ASN1.TypeNotImplemented s -> "Decoding of a type that is not implemented: " <> Util.Text.pack s
ASN1.TypeDecodingFailed s -> "Decoding of a known type failed: " <> Util.Text.pack s
ASN1.TypePrimitiveInvalid s -> "Invalid primitive type: " <> Util.Text.pack s
ASN1.PolicyFailed s1 s2 -> "Policy failed. Policy name: " <> Util.Text.pack s1 <> ", reason:" <> Util.Text.pack s2
-- | Display a RSA Error
rsaErrorToText :: RSA.Error -> Text
rsaErrorToText = \case
RSA.MessageSizeIncorrect ->
"rsa: The message to decrypt is not of the correct size (need to be == private_size)"
RSA.MessageTooLong ->
"rsa: The message to encrypt is too long"
RSA.MessageNotRecognized ->
"rsa: The message decrypted doesn't have a PKCS15 structure (0 2 .. 0 msg)"
RSA.SignatureTooLong ->
"rsa: The message's digest is too long"
RSA.InvalidParameters ->
"rsa: Some parameters lead to breaking assumptions"

View File

@ -27,6 +27,7 @@ import Data.Primitive (ByteArray, MutableArray, MutableByteArray)
import Data.Tagged (Tagged (..))
import Data.X509 qualified as X509
import Network.Socket (Socket)
import Network.UDP (ListenSocket, UDPSocket, ClientSockAddr)
import Network.TLS qualified as TLS (ClientParams, Context, ServerParams)
import System.Clock (TimeSpec)
import System.IO (Handle)
@ -81,6 +82,10 @@ socketEq :: Socket -> Socket -> Bool
socketEq l r = l == r
{-# NOINLINE socketEq #-}
udpSocketEq :: UDPSocket -> UDPSocket -> Bool
udpSocketEq l r = l == r
{-# NOINLINE udpSocketEq #-}
refEq :: IORef () -> IORef () -> Bool
refEq l r = l == r
{-# NOINLINE refEq #-}
@ -157,6 +162,7 @@ ref2eq r
-- Ditto
| r == Ty.tvarRef = Just $ promote tvarEq
| r == Ty.socketRef = Just $ promote socketEq
| r == Ty.udpSocketRef = Just $ promote udpSocketEq
| r == Ty.refRef = Just $ promote refEq
| r == Ty.threadIdRef = Just $ promote tidEq
| r == Ty.marrayRef = Just $ promote marrEq
@ -230,6 +236,12 @@ instance BuiltinForeign Referent where foreignRef = Tagged Ty.termLinkRef
instance BuiltinForeign Socket where foreignRef = Tagged Ty.socketRef
instance BuiltinForeign ListenSocket where foreignRef = Tagged Ty.udpListenSocketRef
instance BuiltinForeign ClientSockAddr where foreignRef = Tagged Ty.udpClientSockAddrRef
instance BuiltinForeign UDPSocket where foreignRef = Tagged Ty.udpSocketRef
instance BuiltinForeign ThreadId where foreignRef = Tagged Ty.threadIdRef
instance BuiltinForeign TLS.ClientParams where foreignRef = Tagged Ty.tlsClientConfigRef

View File

@ -26,6 +26,7 @@ import Data.Time.Clock.POSIX (POSIXTime)
import Data.Word (Word16, Word32, Word64, Word8)
import GHC.IO.Exception (IOErrorType (..), IOException (..))
import Network.Socket (Socket)
import Network.UDP (UDPSocket)
import System.IO (BufferMode (..), Handle, IOMode, SeekMode)
import Unison.Builtin.Decls qualified as Ty
import Unison.Reference (Reference)
@ -139,6 +140,10 @@ instance ForeignConvention Socket where
readForeign = readForeignBuiltin
writeForeign = writeForeignBuiltin
instance ForeignConvention UDPSocket where
readForeign = readForeignBuiltin
writeForeign = writeForeignBuiltin
instance ForeignConvention ThreadId where
readForeign = readForeignBuiltin
writeForeign = writeForeignBuiltin

View File

@ -55,6 +55,7 @@ import System.Directory
createDirectoryIfMissing,
getXdgDirectory,
)
import System.Environment (getArgs)
import System.Exit (ExitCode (..))
import System.FilePath ((<.>), (</>))
import System.Process
@ -869,6 +870,8 @@ nativeEvalInContext executable ppe ctx serv port codes base = do
ensureRuntimeExists executable
let cc = ccache ctx
crs <- readTVarIO $ combRefs cc
-- Seems a bit weird, but apparently this is how we do it
args <- getArgs
let bytes = serializeValue . compileValue base $ codes
decodeResult (Error msg) = pure . Left $ text msg
@ -884,8 +887,14 @@ nativeEvalInContext executable ppe ctx serv port codes base = do
(errs, dv) -> pure $ Right (listErrors errs, dv)
comm mv (sock, _) = do
send sock . runPutS . putWord32be . fromIntegral $ BS.length bytes
let encodeNum = runPutS . putWord32be . fromIntegral
send sock . encodeNum $ BS.length bytes
send sock bytes
send sock . encodeNum $ length args
for_ args $ \arg -> do
let bs = encodeUtf8 $ pack arg
send sock . encodeNum $ BS.length bs
send sock bs
UnliftIO.putMVar mv =<< receiveAll sock
callout _ _ _ ph = do

View File

@ -1950,8 +1950,10 @@ reserveIds :: Word64 -> TVar Word64 -> IO Word64
reserveIds n free = atomically . stateTVar free $ \i -> (i, i + n)
updateMap :: (Semigroup s) => s -> TVar s -> STM s
updateMap new r = stateTVar r $ \old ->
let total = new <> old in (total, total)
updateMap new0 r = do
new <- evaluateSTM new0
stateTVar r $ \old ->
let total = new <> old in (total, total)
refLookup :: String -> M.Map Reference Word64 -> Reference -> Word64
refLookup s m r
@ -2080,6 +2082,11 @@ checkValueSandboxing cc allowed0 v = do
where
allowed = S.fromList allowed0
-- Just evaluating to force exceptions. Shouldn't actually be that
-- unsafe.
evaluateSTM :: a -> STM a
evaluateSTM x = unsafeIOToSTM (evaluate x)
cacheAdd0 ::
S.Set Reference ->
[(Reference, SuperGroup Symbol)] ->

View File

@ -0,0 +1,48 @@
module Unison.Test.Runtime.Crypto.Rsa where
import Crypto.PubKey.RSA qualified as RSA
import Data.Maybe (fromJust)
import EasyTest
import Text.Hex
import Unison.Runtime.Crypto.Rsa
test :: Test ()
test =
scope "parsing" $
tests
[ scope "parseRsaPublicKey" parseRsaPublicKeyTest,
scope "parseRsaPrivateKey" parseRsaPrivateKeyTest
]
parseRsaPublicKeyTest :: Test ()
parseRsaPublicKeyTest = do
let publicKey = fromJust $ decodeHex "30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001"
let actual = parseRsaPublicKey publicKey
let expected =
RSA.PublicKey
{ public_size = 128,
public_n = 117316082691067466889305872575557202673362950667744445659499028356561021937142613205104589546643406309814005581397307365793352915031830083408196867291689544964758311244905648512755140288413724266536406258908443053617981341387254220659107167969619543916073994027510270571746462643891169516098953507692950006037,
public_e = 65537
}
expectEqual actual (Right expected)
parseRsaPrivateKeyTest :: Test ()
parseRsaPrivateKeyTest = do
let privateKey = fromJust $ decodeHex "30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65"
let actual = parseRsaPrivateKey privateKey
let expected =
RSA.PrivateKey
{ private_pub =
RSA.PublicKey
{ public_size = 128,
public_n = 117316082691067466889305872575557202673362950667744445659499028356561021937142613205104589546643406309814005581397307365793352915031830083408196867291689544964758311244905648512755140288413724266536406258908443053617981341387254220659107167969619543916073994027510270571746462643891169516098953507692950006037,
public_e = 65537
},
private_d = 87679616801061623139678211462583995973938243841750319557622746050821908471598979773246073219465960975647341309221073776399960619667883322633274192544886774496262613234964971623744931197514942326521327825606791139576216469817618072158660015124292686556025876602526093941289386692302798356532230087066424907681,
private_p = 11233609214744923027767175501352593646202568021007351512424743595719525825944483790453654486119375677127184086533073126720964060366977171672432803562630589,
private_q = 10443311712951670023099443962737058583295522901049380734330015511797675780053495867511334370071427510893202629294375157939437054042246322949533759718949433,
private_dP = 3176031022781156885141187342486873181111240716865972140527001145690023864823311109042460960576558461960260523664057127500690343997127119244373520564139069,
private_dQ = 6941120510619372179626602981107825119089517097926514417911731475020140673258620725588998791918173107511741662411060736754565186643059761376912904765212297,
private_qinv = 5130749483925715543854508655089227892147425255568362503702389513480166321367311031864242660308321705497233758877799126086240198385610964125158868020698725
}
expectEqual actual (Right expected)

View File

@ -142,6 +142,7 @@ library
Unison.Runtime.ANF.Serialize
Unison.Runtime.Array
Unison.Runtime.Builtin
Unison.Runtime.Crypto.Rsa
Unison.Runtime.Debug
Unison.Runtime.Decompile
Unison.Runtime.Exception
@ -232,6 +233,8 @@ library
, NanoID
, aeson
, ansi-terminal
, asn1-encoding
, asn1-types
, async
, atomic-primops
, base
@ -268,6 +271,7 @@ library
, http-client
, http-media
, http-types
, iproute
, lens
, lucid
, megaparsec
@ -281,6 +285,7 @@ library
, natural-transformation
, network
, network-simple
, network-udp
, network-uri
, nonempty-containers
, open-browser
@ -370,6 +375,7 @@ test-suite parser-typechecker-tests
Unison.Test.DataDeclaration
Unison.Test.MCode
Unison.Test.Referent
Unison.Test.Runtime.Crypto.Rsa
Unison.Test.Syntax.FileParser
Unison.Test.Syntax.TermParser
Unison.Test.Syntax.TypePrinter
@ -424,6 +430,8 @@ test-suite parser-typechecker-tests
, NanoID
, aeson
, ansi-terminal
, asn1-encoding
, asn1-types
, async
, atomic-primops
, base
@ -460,9 +468,11 @@ test-suite parser-typechecker-tests
, hashable
, hashtables
, haskeline
, hex-text
, http-client
, http-media
, http-types
, iproute
, lens
, lucid
, megaparsec
@ -476,6 +486,7 @@ test-suite parser-typechecker-tests
, natural-transformation
, network
, network-simple
, network-udp
, network-uri
, nonempty-containers
, open-browser

View File

@ -30,18 +30,32 @@
unison/data-info
unison/chunked-seq
unison/primops
unison/builtin
unison/primops-generated
unison/builtin-generated)
(define (grab-num port)
(integer-bytes->integer (read-bytes 4 port) #f #t 0 4))
; Gets bytes using the expected input format. The format is simple:
;
; - 4 bytes indicating how many bytes follow
; - the actual payload, with size matching the above
(define (grab-bytes port)
(let* ([size-bytes (read-bytes 4 port)]
[size (integer-bytes->integer size-bytes #f #t 0 4)])
(let ([size (grab-num port)])
(read-bytes size port)))
; Gets args sent after the code payload. Format is:
;
; - 4 bytes indicating how many arguments
; - for each argument
; - 4 bytes indicating length of argument
; - utf-8 bytes of that length
(define (grab-args port)
(let ([n (grab-num port)])
(for/list ([i (range n)])
(bytes->string/utf-8 (grab-bytes port)))))
; Reads and decodes the input. First uses `grab-bytes` to read the
; payload, then uses unison functions to deserialize the `Value` that
; is expected.
@ -113,13 +127,15 @@
; input. Then uses the dynamic loading machinery to add the code to
; the runtime. Finally executes a specified main reference.
(define (do-evaluate in out)
(let-values ([(code main-ref) (decode-input in)])
(let-values ([(code main-ref) (decode-input in)]
[(args) (list->vector (grab-args in))])
(add-runtime-code 'unison-main code)
(with-handlers
([exn:bug? (lambda (e) (encode-error e out))])
(handle [ref-exception:typelink] (eval-exn-handler out)
((termlink->proc main-ref))))))
(parameterize ([current-command-line-arguments args])
(handle [ref-exception:typelink] (eval-exn-handler out)
((termlink->proc main-ref)))))))
; Uses racket pretty printing machinery to instead generate a file
; containing the given code, and which executes the main definition on

View File

@ -47,6 +47,10 @@
builtin-tls.signedcert:typelink
builtin-tls.version:typelink
builtin-udpsocket:typelink
builtin-listensocket:typelink
builtin-clientsockaddr:typelink
bytevector
bytes
control
@ -607,5 +611,5 @@
(define (exn:bug->exception b)
(raise-unison-exception
ref-runtimefailure:typelink
(exn:bug-msg b)
(string->chunked-string (exn:bug-msg b))
(exn:bug-val b)))

View File

@ -0,0 +1,4 @@
#lang racket/base
(require unison/udp)
(provide (all-from-out))

View File

@ -37,7 +37,6 @@
bytevector
bytevector-append
directory-contents
current-microseconds
decode-value
@ -227,10 +226,6 @@
(define (current-microseconds)
(fl->fx (* 1000 (current-inexact-milliseconds))))
(define (directory-contents path-str)
(define (extract path) (string->chunked-string (path->string path)))
(map extract (directory-list (chunked-string->string path-str))))
(define (list-head l n)
(let rec ([c l] [m n])
(cond
@ -476,19 +471,17 @@
(next (fx1- i)))))))
(define (write-exn:bug ex port mode)
(when mode
(write-string "<exn:bug " port))
(when mode (write-string "<exn:bug " port))
(let ([recur (case mode
[(#t) write]
[(#f) display]
[else (lambda (v port) (print v port mode))])])
(recur (chunked-string->string (exn:bug-msg ex)) port)
(recur (exn:bug-msg ex) port)
(if mode (write-string " " port) (newline port))
(write-string (describe-value (exn:bug-val ex)) port))
(when mode
(write-string ">")))
(when mode (write-string ">" port)))
(struct exn:bug (msg val)
#:constructor-name make-exn:bug

View File

@ -80,6 +80,9 @@
builtin-timespec:typelink
builtin-threadid:typelink
builtin-value:typelink
builtin-udpsocket:typelink
builtin-listensocket:typelink
builtin-clientsockaddr:typelink
builtin-crypto.hashalgorithm:typelink
builtin-char.class:typelink
@ -440,6 +443,9 @@
(define builtin-timespec:typelink (unison-typelink-builtin "TimeSpec"))
(define builtin-threadid:typelink (unison-typelink-builtin "ThreadId"))
(define builtin-value:typelink (unison-typelink-builtin "Value"))
(define builtin-udpsocket:typelink (unison-typelink-builtin "UDPSocket"))
(define builtin-listensocket:typelink (unison-typelink-builtin "ListenSocket"))
(define builtin-clientsockaddr:typelink (unison-typelink-builtin "ClientSockAddr"))
(define builtin-crypto.hashalgorithm:typelink
(unison-typelink-builtin "crypto.HashAlgorithm"))

View File

@ -14,6 +14,7 @@
(provide
unison-FOp-IO.stdHandle
unison-FOp-IO.openFile.impl.v3
(prefix-out
builtin-IO.
(combine-out
@ -100,13 +101,23 @@
ref-unit-unit)
(ref-either-right char))))
(define-unison (getSomeBytes.impl.v1 handle bytes)
(let* ([buffer (make-bytes bytes)]
(define-unison (getSomeBytes.impl.v1 handle nbytes)
(let* ([buffer (make-bytes nbytes)]
[line (read-bytes-avail! buffer handle)])
(if (eof-object? line)
(ref-either-right (bytes->chunked-bytes #""))
(ref-either-right (bytes->chunked-bytes buffer))
)))
(cond
[(eof-object? line)
(ref-either-right (bytes->chunked-bytes #""))]
[(procedure? line)
(Exception
ref-iofailure:typelink
"getSomeBytes.impl: special value returned"
ref-unit-unit)]
[else
(ref-either-right
(bytes->chunked-bytes
(if (< line nbytes)
(subbytes buffer 0 line)
buffer)))])))
(define-unison (getBuffering.impl.v3 handle)
(case (file-stream-buffer-mode handle)
@ -194,6 +205,15 @@
(ref-either-right
(string->chunked-string (bytes->string/utf-8 value))))))
(define (unison-FOp-IO.openFile.impl.v3 fn0 mode)
(define fn (chunked-string->string fn0))
(right (case mode
[(0) (open-input-file fn)]
[(1) (open-output-file fn #:exists 'truncate)]
[(2) (open-output-file fn #:exists 'append)]
[else (open-input-output-file fn #:exists 'can-update)])))
;; From https://github.com/sorawee/shlex/blob/5de06500e8c831cfc8dffb99d57a76decc02c569/main.rkt (MIT License)
;; with is a port of https://github.com/python/cpython/blob/bf2f76ec0976c09de79c8827764f30e3b6fba776/Lib/shlex.py#L325
(define unsafe-pattern #rx"[^a-zA-Z0-9_@%+=:,./-]")

View File

@ -5,6 +5,10 @@
unison/data-info
racket/file
racket/flonum
(only-in racket
date-dst?
date-time-zone-offset
date*-time-zone-name)
(only-in unison/boot data-case define-unison)
(only-in
rnrs/arithmetic/flonums-6
@ -12,6 +16,7 @@
(require racket/file)
(provide
builtin-Clock.internals.systemTimeZone.v1
(prefix-out
unison-FOp-Clock.internals.
(combine-out
@ -35,6 +40,7 @@
renameFile.impl.v3
createDirectory.impl.v3
removeDirectory.impl.v3
directoryContents.impl.v3
setCurrentDirectory.impl.v3
renameDirectory.impl.v3
isDirectory.impl.v3
@ -42,6 +48,13 @@
systemTimeMicroseconds.impl.v3
createTempDirectory.impl.v3)))
(define (failure-result ty msg vl)
(ref-either-left
(ref-failure-failure
ty
(string->chunked-string msg)
(unison-any-any vl))))
(define (getFileSize.impl.v3 path)
(with-handlers
[[exn:fail:filesystem?
@ -81,6 +94,24 @@
(current-directory (chunked-string->string path))
(ref-either-right none))
(define-unison (directoryContents.impl.v3 path)
(with-handlers
[[exn:fail:filesystem?
(lambda (e)
(failure-result
ref-iofailure:typelink
(exception->string e)
ref-unit-unit))]]
(let* ([dirps (directory-list (chunked-string->string path))]
[dirss (map path->string dirps)])
(ref-either-right
(vector->chunked-list
(list->vector
(map
string->chunked-string
(list* "." ".." dirss))))))))
(define-unison (createTempDirectory.impl.v3 prefix)
(ref-either-right
(string->chunked-string
@ -117,6 +148,14 @@
(define-unison (systemTimeMicroseconds.impl.v3 unit)
(ref-either-right (inexact->exact (* 1000 (current-inexact-milliseconds)))))
(define-unison (builtin-Clock.internals.systemTimeZone.v1 secs)
(let* ([d (seconds->date secs)])
(list->unison-tuple
(list
(date-time-zone-offset d)
(if (date-dst? d) 1 0)
(date*-time-zone-name d)))))
(define (threadCPUTime.v1)
(right
(integer->time

View File

@ -0,0 +1,31 @@
#lang racket/base
(require racket/exn
unison/data ; exception
unison/data-info ; ref-*
unison/chunked-seq
unison/core) ; exception->string, chunked-string
(provide handle-errors)
(define (handle-errors fn)
(with-handlers
[[exn:fail:network?
(lambda (e)
(exception
ref-iofailure:typelink
(exception->string e)
ref-unit-unit))]
[exn:fail:contract?
(lambda (e)
(exception
ref-miscfailure:typelink
(exception->string e)
ref-unit-unit))]
[(lambda _ #t)
(lambda (e)
(exception
ref-miscfailure:typelink
(string->chunked-string
(format "Unknown exception ~a" (exn->string e)))
ref-unit-unit))]]
(fn)))

View File

@ -221,10 +221,11 @@
[(unison-termlink-builtin name)
(string-append "builtin-" name)]
[(unison-termlink-derived bs i)
(let ([hs (bytevector->base32-string bs #:alphabet 'hex)]
[po (if (= i 0) "" (string-append "." (number->string i)))])
(let* ([hs (bytevector->base32-string bs #:alphabet 'hex)]
[tm (string-trim hs "=" #:repeat? #t)]
[po (if (= i 0) "" (string-append "." (number->string i)))])
(string->symbol
(string-append "ref-" (substring hs 0 8) po)))]))
(string-append "ref-" tm po)))]))
(define (ref-bytes r)
(sum-case (decode-ref r)
@ -304,8 +305,24 @@
(match v
[(unison-data _ t (list rf rt bs0))
#:when (= t ref-value-data:tag)
(let ([bs (map reify-value (chunked-list->list bs0))])
(make-data (reference->typelink rf) rt bs))]
(let ([bs (map reify-value (chunked-list->list bs0))]
[tl (reference->typelink rf)])
(cond
[(eqv? tl builtin-boolean:typelink)
(cond
[(not (null? bs))
(raise
(make-exn:bug
"reify-value: boolean with arguments"
bs0))]
[(= rt 0) #f]
[(= rt 1) #t]
[else
(raise
(make-exn:bug
"reify-value: unknown boolean tag"
rt))])]
[else (make-data tl rt bs)]))]
[(unison-data _ t (list gr bs0))
#:when (= t ref-value-partial:tag)
(let ([bs (map reify-value (chunked-list->list bs0))]
@ -316,11 +333,18 @@
(reify-vlit vl)]
[(unison-data _ t (list bs0 k))
#:when (= t ref-value-cont:tag)
(raise "reify-value: unimplemented cont case")]
(raise
(make-exn:bug
"reify-value: unimplemented cont case"
ref-unit-unit))]
[(unison-data r t fs)
(raise "reify-value: unimplemented data case")]
(raise
(make-exn:bug
"reify-value: unrecognized tag"
ref-unit-unit))]
[else
(raise (format "reify-value: unknown tag"))]))
(raise
(make-exn:bug "reify-value: unrecognized value" v))]))
(define (reflect-typelink tl)
(match tl
@ -354,6 +378,11 @@
(define (reflect-value v)
(match v
[(? boolean?)
(ref-value-data
(reflect-typelink builtin-boolean:typelink)
(if v 1 0) ; boolean pseudo-data tags
empty-chunked-list)]
[(? exact-nonnegative-integer?)
(ref-value-vlit (ref-vlit-pos v))]
[(? exact-integer?)

View File

@ -186,6 +186,29 @@
builtin-TypeLink.toReference
builtin-TypeLink.toReference:termlink
builtin-IO.UDP.clientSocket.impl.v1
builtin-IO.UDP.clientSocket.impl.v1:termlink
builtin-IO.UDP.UDPSocket.recv.impl.v1
builtin-IO.UDP.UDPSocket.recv.impl.v1:termlink
builtin-IO.UDP.UDPSocket.send.impl.v1
builtin-IO.UDP.UDPSocket.send.impl.v1:termlink
builtin-IO.UDP.UDPSocket.close.impl.v1
builtin-IO.UDP.UDPSocket.close.impl.v1:termlink
builtin-IO.UDP.ListenSocket.close.impl.v1
builtin-IO.UDP.ListenSocket.close.impl.v1:termlink
builtin-IO.UDP.UDPSocket.toText.impl.v1
builtin-IO.UDP.UDPSocket.toText.impl.v1:termlink
builtin-IO.UDP.serverSocket.impl.v1
builtin-IO.UDP.serverSocket.impl.v1:termlink
builtin-IO.UDP.ListenSocket.toText.impl.v1
builtin-IO.UDP.ListenSocket.toText.impl.v1:termlink
builtin-IO.UDP.ListenSocket.recvFrom.impl.v1
builtin-IO.UDP.ListenSocket.recvFrom.impl.v1:termlink
builtin-IO.UDP.ClientSockAddr.toText.v1
builtin-IO.UDP.ClientSockAddr.toText.v1:termlink
builtin-IO.UDP.ListenSocket.sendTo.impl.v1
builtin-IO.UDP.ListenSocket.sendTo.impl.v1:termlink
unison-FOp-internal.dataTag
unison-FOp-Char.toText
; unison-FOp-Code.dependencies
@ -314,13 +337,16 @@
unison-FOp-Clock.internals.processCPUTime.v1
unison-FOp-Clock.internals.realtime.v1
unison-FOp-Clock.internals.monotonic.v1
builtin-Clock.internals.systemTimeZone.v1
builtin-Clock.internals.systemTimeZone.v1:termlink
; unison-FOp-Value.serialize
unison-FOp-IO.stdHandle
unison-FOp-IO.getArgs.impl.v1
unison-FOp-IO.directoryContents.impl.v3
builtin-IO.directoryContents.impl.v3
builtin-IO.directoryContents.impl.v3:termlink
unison-FOp-IO.systemTimeMicroseconds.v1
unison-FOp-ImmutableArray.copyTo!
@ -645,6 +671,7 @@
(unison murmurhash)
(unison tls)
(unison tcp)
(unison udp)
(unison gzip)
(unison zlib)
(unison concurrent)
@ -732,6 +759,7 @@
(define-builtin-link IO.getEnv.impl.v1)
(define-builtin-link IO.getChar.impl.v1)
(define-builtin-link IO.getCurrentDirectory.impl.v3)
(define-builtin-link IO.directoryContents.impl.v3)
(define-builtin-link IO.removeDirectory.impl.v3)
(define-builtin-link IO.renameFile.impl.v3)
(define-builtin-link IO.createTempDirectory.impl.v3)
@ -758,6 +786,7 @@
(define-builtin-link Char.Class.is)
(define-builtin-link Scope.bytearrayOf)
(define-builtin-link unsafe.coerceAbilities)
(define-builtin-link Clock.internals.systemTimeZone.v1)
(begin-encourage-inline
(define-unison (builtin-Value.toBuiltin v) (unison-quote v))
@ -902,11 +931,13 @@
(define (unison-POp-LEQT s t) (bool (chunked-string<? s t)))
(define (unison-POp-EQLU x y) (bool (universal=? x y)))
(define (unison-POp-EROR fnm x)
(let-values ([(p g) (open-string-output-port)])
(put-string p (chunked-string->string fnm))
(let-values
([(p g) (open-string-output-port)]
[(snm) (chunked-string->string fnm)])
(put-string p snm)
(put-string p ": ")
(display (describe-value x) p)
(raise (make-exn:bug fnm x))))
(raise (make-exn:bug snm x))))
(define (unison-POp-FTOT f)
(define base (number->string f))
(define dotted
@ -1095,11 +1126,6 @@
(define (unison-FOp-IO.getArgs.impl.v1)
(sum 1 (cdr (command-line))))
(define (unison-FOp-IO.directoryContents.impl.v3 path)
(reify-exn
(lambda ()
(sum 1 (directory-contents path)))))
(define unison-FOp-IO.systemTimeMicroseconds.v1 current-microseconds)
;; TODO should we convert Bytes -> Text directly without the intermediate conversions?
@ -1129,13 +1155,6 @@
(close-output-port h))
(right none))
(define (unison-FOp-IO.openFile.impl.v3 fn mode)
(right (case mode
[(0) (open-file-input-port (chunked-string->string fn))]
[(1) (open-file-output-port (chunked-string->string fn))]
[(2) (open-file-output-port (chunked-string->string fn) 'no-truncate)]
[else (open-file-input/output-port (chunked-string->string fn))])))
(define (unison-FOp-Text.repeat n t)
(let loop ([cnt 0]
[acc empty-chunked-string])
@ -1470,6 +1489,7 @@
(declare-builtin-link builtin-IO.getArgs.impl.v1)
(declare-builtin-link builtin-IO.getEnv.impl.v1)
(declare-builtin-link builtin-IO.getChar.impl.v1)
(declare-builtin-link builtin-IO.directoryContents.impl.v3)
(declare-builtin-link builtin-IO.getCurrentDirectory.impl.v3)
(declare-builtin-link builtin-IO.removeDirectory.impl.v3)
(declare-builtin-link builtin-IO.renameFile.impl.v3)
@ -1495,4 +1515,5 @@
(declare-builtin-link builtin-Char.Class.is)
(declare-builtin-link builtin-Pattern.many.corrected)
(declare-builtin-link builtin-unsafe.coerceAbilities)
(declare-builtin-link builtin-Clock.internals.systemTimeZone.v1)
)

View File

@ -103,6 +103,17 @@
(sandbox-builtin "IO.getFileSize.impl.v3")
(sandbox-builtin "IO.serverSocket.impl.v3")
(sandbox-builtin "Socket.toText")
(sandbox-builtin "UDP.clientSocket.impl.v1")
(sandbox-builtin "UDP.serverSocket.impl.v1")
(sandbox-builtin "UDP.UDPSocket.close.impl.v1")
(sandbox-builtin "UDP.UDPSocket.recv.impl.v1")
(sandbox-builtin "UDP.UDPSocket.send.impl.v1")
(sandbox-builtin "UDP.ListenSocket.close.impl.v1")
(sandbox-builtin "UDP.UDPSocket.toText.impl.v1")
(sandbox-builtin "UDP.ListenSocket.toText.impl.v1")
(sandbox-builtin "UDP.ListenSocket.recvFrom.impl.v1")
(sandbox-builtin "UDP.ClientSockAddr.toText.v1")
(sandbox-builtin "UDP.ListenSocket.sendTo.impl.v1")
(sandbox-builtin "Handle.toText")
(sandbox-builtin "ThreadId.toText")
(sandbox-builtin "IO.socketPort.impl.v3")

View File

@ -6,6 +6,7 @@
unison/data
unison/data-info
unison/chunked-seq
unison/network-utils
unison/core)
(provide
@ -25,29 +26,6 @@
(struct socket-pair (input output))
(define (handle-errors fn)
(with-handlers
[[exn:fail:network?
(lambda (e)
(exception
ref-iofailure:typelink
(exception->string e)
ref-unit-unit))]
[exn:fail:contract?
(lambda (e)
(exception
ref-miscfailure:typelink
(exception->string e)
ref-unit-unit))]
[(lambda _ #t)
(lambda (e)
(exception
ref-miscfailure:typelink
(chunked-string->string
(format "Unknown exception ~a" (exn->string e)))
ref-unit-unit))]]
(fn)))
(define (closeSocket.impl.v3 socket)
(handle-errors
(lambda ()

View File

@ -0,0 +1,179 @@
; UDP primitives!
#lang racket/base
(require racket/udp
racket/format
(only-in unison/boot define-unison)
unison/data
unison/data-info
unison/chunked-seq
(only-in unison/boot sum-case)
unison/network-utils
unison/core)
(provide
(prefix-out
builtin-IO.UDP.
(combine-out
clientSocket.impl.v1
clientSocket.impl.v1:termlink
UDPSocket.recv.impl.v1
UDPSocket.recv.impl.v1:termlink
UDPSocket.send.impl.v1
UDPSocket.send.impl.v1:termlink
UDPSocket.close.impl.v1
UDPSocket.close.impl.v1:termlink
ListenSocket.close.impl.v1
ListenSocket.close.impl.v1:termlink
UDPSocket.toText.impl.v1
UDPSocket.toText.impl.v1:termlink
serverSocket.impl.v1
serverSocket.impl.v1:termlink
ListenSocket.toText.impl.v1
ListenSocket.toText.impl.v1:termlink
ListenSocket.recvFrom.impl.v1
ListenSocket.recvFrom.impl.v1:termlink
ClientSockAddr.toText.v1
ClientSockAddr.toText.v1:termlink
ListenSocket.sendTo.impl.v1
ListenSocket.sendTo.impl.v1:termlink)))
(struct client-sock-addr (host port))
; Haskell's Network.UDP choice of buffer size is 2048, so mirror that here
(define buffer-size 2048)
(define ; a -> Either Failure a
(wrap-in-either a)
(sum-case a
(0 (type msg meta)
(ref-either-left (ref-failure-failure type msg (unison-any-any meta))))
(1 (data)
(ref-either-right data))))
(define
(format-socket socket)
(let*-values ([(local-hn local-port remote-hn remote-port) (udp-addresses socket #t)]
[(rv) (~a "<socket local=" local-hn ":" local-port " remote=" remote-hn ":" remote-port ">")])
(string->chunked-string rv)))
(define (close-socket socket)
(let ([rv (handle-errors (lambda() (begin
(udp-close socket)
(right ref-unit-unit))))])
(wrap-in-either rv)))
;; define termlink builtins
(define clientSocket.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.clientSocket.impl.v1"))
(define UDPSocket.recv.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.UDPSocket.recv.impl.v1"))
(define UDPSocket.send.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.UDPSocket.send.impl.v1"))
(define UDPSocket.close.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.UDPSocket.close.impl.v1"))
(define ListenSocket.close.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.ListenSocket.close.impl.v1"))
(define UDPSocket.toText.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.UDPSocket.toText.impl.v1"))
(define serverSocket.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.serverSocket.impl.v1"))
(define ListenSocket.toText.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.ListenSocket.toText.impl.v1"))
(define ListenSocket.recvFrom.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.ListenSocket.recvFrom.impl.v1"))
(define ClientSockAddr.toText.v1:termlink
(unison-termlink-builtin "IO.UDP.ClientSockAddr.toText.v1"))
(define ListenSocket.sendTo.impl.v1:termlink
(unison-termlink-builtin "IO.UDP.ListenSocket.sendTo.impl.v1"))
;; define builtins
(define-unison
(UDPSocket.recv.impl.v1 socket) ; socket -> Either Failure Bytes
(let
([rv (handle-errors (lambda()
(let*-values
([(buffer) (make-bytes buffer-size)]
[(len a b) (udp-receive! socket buffer)])
(right (bytes->chunked-bytes (subbytes buffer 0 len))))))])
(wrap-in-either rv)))
(define-unison
(ListenSocket.close.impl.v1 socket) ; socket -> Either Failure ()
(close-socket socket))
(define-unison
(serverSocket.impl.v1 ip port) ; string string -> Either Failure socket
(let
([result (handle-errors (lambda()
(let* ([iip (chunked-string->string ip)]
[pport (string->number (chunked-string->string port))]
[sock (udp-open-socket iip pport)])
(begin
(udp-bind! sock iip pport)
(right sock)))))])
(wrap-in-either result)))
(define-unison
(ListenSocket.recvFrom.impl.v1 socket) ; socket -> Either Failure (Bytes, ClientSockAddr)
(let ([result (handle-errors (lambda()
(if (not (udp? socket))
(raise-argument-error 'socket "a UDP socket" socket)
(let*-values
([(buffer) (make-bytes buffer-size)]
[(len host port) (udp-receive! socket buffer)]
[(csa) (client-sock-addr host port)]
[(bs) (subbytes buffer 0 len)]
[(chunked) (bytes->chunked-bytes bs)])
(right (ref-tuple-pair chunked (ref-tuple-pair csa ref-unit-unit)))))))])
(wrap-in-either result)))
(define-unison
(UDPSocket.send.impl.v1 socket data) ; socket -> Bytes -> Either Failure ()
(let
([result (handle-errors (lambda () (begin
(udp-send socket (chunked-bytes->bytes data))
(right ref-unit-unit))))])
(wrap-in-either result)))
(define-unison
(ListenSocket.sendTo.impl.v1 sock bytes addr) ; socket -> Bytes -> ClientSockAddr -> Either Failure ()
(let
([result (handle-errors (lambda()
(let* ([host (client-sock-addr-host addr)]
[port (client-sock-addr-port addr)]
[bytes (chunked-bytes->bytes bytes)])
(begin
(udp-send-to sock host port bytes)
(right ref-unit-unit)))))])
(wrap-in-either result)))
(define-unison
(UDPSocket.toText.impl.v1 socket) ; socket -> string
(format-socket socket))
(define-unison
(ClientSockAddr.toText.v1 addr) ; ClientSocketAddr -> string
(string->chunked-string (format "<client-sock-addr ~a ~a>" (client-sock-addr-host addr) (client-sock-addr-port addr))))
(define-unison
(ListenSocket.toText.impl.v1 socket) ; socket -> string
(format-socket socket))
(define-unison
(UDPSocket.close.impl.v1 socket) ; socket -> Either Failure ()
(let
([rv (handle-errors (lambda() (begin
(udp-close socket)
(right ref-unit-unit))))])
(wrap-in-either rv)))
(define-unison
(clientSocket.impl.v1 host port) ; string string -> Either Failure socket
(let ([rv (handle-errors (lambda() (let* ([pport (string->number (chunked-string->string port))]
[hhost (chunked-string->string host)]
[sock (udp-open-socket hhost pport)]
[_ (udp-bind! sock #f 0)]
[res (udp-connect! sock hhost pport)]) (right sock))))])
(wrap-in-either rv)))

View File

@ -22,7 +22,7 @@ getHash() {
if [[ -z "$name" ]]; then
name="${parts[i]}"
else
name="$name/${parts[i]}"
name="$name%2F${parts[i]}"
fi
done
fi

View File

@ -66,6 +66,7 @@ extra-deps:
- lsp-2.2.0.0@sha256:82fbf4b69d94d8d22543be71f89986b3e90050032d671fb3de3f8253ea1e5b6f,3550
- lsp-types-2.0.2.0@sha256:a9a51c3cea0726d91fe63fa0670935ee720f7b31bc3f3b33b2483fc538152677,29421
- row-types-1.0.1.2@sha256:4d4c7cb95d06a32b28ba977852d52a26b4c1f695ef083a6fd874ab6d79933b64,3071
- network-udp-0.0.0
ghc-options:
# All packages

View File

@ -82,6 +82,13 @@ packages:
size: 1060
original:
hackage: row-types-1.0.1.2@sha256:4d4c7cb95d06a32b28ba977852d52a26b4c1f695ef083a6fd874ab6d79933b64,3071
- completed:
hackage: network-udp-0.0.0@sha256:408d2d4fa1a25e49e95752ee124cca641993404bb133ae10fb81daef22d876ae,1075
pantry-tree:
sha256: ee19a66c9d420861c5cc1dfad3210e2a53cdc6088ff3dd90b44f7961f5caebee
size: 284
original:
hackage: network-udp-0.0.0
snapshots:
- completed:
sha256: 5a59b2a405b3aba3c00188453be172b85893cab8ebc352b1ef58b0eae5d248a2

View File

@ -51,6 +51,7 @@ dependencies:
- mtl
- network
- network-simple
- network-udp
- network-uri
- nonempty-containers
- open-browser

View File

@ -13,7 +13,9 @@ import Data.IORef
import Data.Text qualified as Text
import Data.Text.IO qualified as Text
import Ki qualified
import System.Console.Haskeline (Settings (autoAddHistory))
import System.Console.Haskeline qualified as Line
import System.Console.Haskeline.History qualified as Line
import System.IO (hGetEcho, hPutStrLn, hSetEcho, stderr, stdin)
import System.IO.Error (isDoesNotExistError)
import U.Codebase.HashTags (CausalHash)
@ -102,17 +104,27 @@ getUserInput codebase authHTTPClient currentPath numberedArgs =
ws -> do
liftIO (parseInput codebase currentPath numberedArgs IP.patternMap ws) >>= \case
Left msg -> do
-- We still add history that failed to parse so the user can easily reload
-- the input and fix it.
Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ l
liftIO $ putPrettyLn msg
go
Right Nothing -> do
-- Ctrl-c or some input cancel, re-run the prompt
go
Right (Just (expandedArgs, i)) -> do
let expandedArgsStr = unwords expandedArgs
when (expandedArgs /= ws) $ do
liftIO . putStrLn $ fullPrompt <> unwords expandedArgs
liftIO . putStrLn $ fullPrompt <> expandedArgsStr
Line.modifyHistory $ Line.addHistoryUnlessConsecutiveDupe $ unwords expandedArgs
pure i
settings :: Line.Settings IO
settings = Line.Settings tabComplete (Just ".unisonHistory") True
settings =
Line.Settings
{ complete = tabComplete,
historyFile = Just ".unisonHistory",
autoAddHistory = False
}
tabComplete = haskelineTabComplete IP.patternMap codebase authHTTPClient currentPath
main ::

View File

@ -217,6 +217,7 @@ library
, mtl
, network
, network-simple
, network-udp
, network-uri
, nonempty-containers
, open-browser
@ -358,6 +359,7 @@ executable transcripts
, mtl
, network
, network-simple
, network-udp
, network-uri
, nonempty-containers
, open-browser
@ -507,6 +509,7 @@ test-suite cli-tests
, mtl
, network
, network-simple
, network-udp
, network-uri
, nonempty-containers
, open-browser

View File

@ -15,6 +15,19 @@ import Unison.LabeledDependency qualified as LD
import Unison.Name qualified as Name
import Unison.Names.ResolutionResult qualified as Names
import Unison.Prelude
( Const (Const, getConst),
Generic,
Generic1,
Identity (runIdentity),
Map,
Set,
Text,
foldl',
join,
sortOn,
($>),
(<&>),
)
import Unison.Reference (TypeReference)
import Unison.Reference qualified as Reference
import Unison.Settings qualified as Settings
@ -269,6 +282,11 @@ filePathRef = Reference.Builtin "FilePath"
threadIdRef = Reference.Builtin "ThreadId"
socketRef = Reference.Builtin "Socket"
udpSocketRef, udpListenSocketRef, udpClientSockAddrRef :: TypeReference
udpSocketRef = Reference.Builtin "UDPSocket"
udpListenSocketRef = Reference.Builtin "ListenSocket"
udpClientSockAddrRef = Reference.Builtin "ClientSockAddr"
processHandleRef :: TypeReference
processHandleRef = Reference.Builtin "ProcessHandle"
@ -388,6 +406,15 @@ mbytearrayType a = ref a mbytearrayRef
socket :: (Ord v) => a -> Type v a
socket a = ref a socketRef
udpSocket :: (Ord v) => a -> Type v a
udpSocket a = ref a udpSocketRef
udpListenSocket :: (Ord v) => a -> Type v a
udpListenSocket a = ref a udpListenSocketRef
udpClientSockAddr :: (Ord v) => a -> Type v a
udpClientSockAddr a = ref a udpClientSockAddrRef
list :: (Ord v) => a -> Type v a
list a = ref a listRef

View File

@ -9,6 +9,7 @@ import Control.Monad.Writer.Class qualified as Writer
import Control.Monad.Writer.Lazy (runWriterT)
import Data.Char qualified as Char
import Data.Foldable
import Data.List (intersperse)
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe
@ -357,7 +358,8 @@ toHtml docNamesByRef document =
[d] ->
currentSectionLevelToHtml d
ds ->
span_ [class_ "span"] <$> renderSequence currentSectionLevelToHtml (mergeWords " " ds)
span_ [class_ "span"]
<$> (renderSequence currentSectionLevelToHtml (intersperse (Word " ") (mergeWords " " ds)))
BulletedList items ->
let itemToHtml i =
li_ [] <$> currentSectionLevelToHtml i

View File

@ -49,3 +49,18 @@ foo = do
()
```
This can also only be tested by separately running this test, because
it is exercising the protocol that ucm uses to talk to the jit during
an exception.
```ucm
runtime-tests/selected> run.native testBug
💔💥
I've encountered a call to builtin.bug with the following
value:
"testing"
```

View File

@ -34,3 +34,11 @@ foo = do
.> run.native foo
.> run.native foo
```
This can also only be tested by separately running this test, because
it is exercising the protocol that ucm uses to talk to the jit during
an exception.
```ucm:error
runtime-tests/selected> run.native testBug
```

View File

@ -0,0 +1,66 @@
UDP.client' h p = Either.toException (##IO.UDP.clientSocket.impl.v1 h p)
UDP.server' i p = Either.toException (##IO.UDP.serverSocket.impl.v1 i p)
closeClient' = ##IO.UDP.UDPSocket.close.impl.v1 >> Either.toException
send' s b = Either.toException (##IO.UDP.UDPSocket.send.impl.v1 s b)
closeServer' = ##IO.UDP.ListenSocket.close.impl.v1 >> Either.toException
sendTo' s b a = Either.toException (##IO.UDP.ListenSocket.sendTo.impl.v1 s b a)
recvFrom' = ##IO.UDP.ListenSocket.recvFrom.impl.v1 >> Either.toException
recv' = ##IO.UDP.UDPSocket.recv.impl.v1 >> Either.toException
badPort = "what"
badIp = "what"
goodPort = "8000"
goodIp = "127.0.0.1"
shouldFail fn =
result = catchAll fn
isLeft result
udp.tests = do
check "client rejects invalid port" do shouldFail do UDP.client' goodIp badPort
check "server rejects invalid port" do shouldFail do UDP.server' goodIp badPort
check "server rejects invalid IP address" do shouldFail do UDP.server' badIp goodPort
check "client: no send after close" do shouldFail do
socket = UDP.client' goodIp goodPort
closeClient' socket
send' socket (toUtf8 "hello") -- should fail here
check "server no send after close" do shouldFail do
ssocket = UDP.server' goodIp goodPort
csocket = UDP.client' goodIp goodPort
send' csocket (toUtf8 "hello")
(_, clientSockAddr) = recvFrom' ssocket
closeServer' ssocket
sendTo' ssocket (toUtf8 "hello") clientSockAddr -- should fail here
check "no receive after close" do shouldFail do
socket = UDP.client' goodIp goodPort
closeClient' socket
recv' socket
!testServerAndClient
testServerAndClient = do
fromServerMsg = "from server"
fromClientMsg = "from client"
setup = catchAll do
UDP.server' goodIp goodPort
match setup with
Left e ->
Tests.fail "Unable to open a socket for UDP server" (Debug.evalToText e)
Right ssocket ->
serve = do
(data, sockAddr) = recvFrom' ssocket
sendTo' ssocket (toUtf8 fromServerMsg) sockAddr
closeServer' ssocket
fromUtf8 data
serveResult = !Promise.new
_ = fork do Promise.write serveResult (catchAll serve)
data = catchAll do
csocket = UDP.client' goodIp goodPort
send' csocket (toUtf8 fromClientMsg)
d = recv' csocket
closeClient' csocket
fromUtf8 d
checkEqual "Server received data" (Promise.read serveResult) (Right fromClientMsg)
checkEqual "Client received data" data (Right fromServerMsg)

View File

@ -5,7 +5,7 @@ Next, we'll download the jit project and generate a few Racket files from it.
```ucm
.> project.create-empty jit-setup
jit-setup/main> pull @unison/internal/releases/0.0.15 lib.jit
jit-setup/main> pull @unison/internal/releases/0.0.16 lib.jit
```
```unison

View File

@ -20,9 +20,9 @@ Next, we'll download the jit project and generate a few Racket files from it.
🎉 🥳 Happy coding!
jit-setup/main> pull @unison/internal/releases/0.0.15 lib.jit
jit-setup/main> pull @unison/internal/releases/0.0.16 lib.jit
Downloaded 15060 entities.
Downloaded 15091 entities.

File diff suppressed because it is too large Load Diff

View File

@ -178,21 +178,21 @@ Stream.collect s =
handle !s with Stream.collect.handler
-- An ability that facilitates creating temoporary directories that can be
-- An ability that facilitates creating temoporary directories that can be
-- automatically cleaned up
structural ability TempDirs where
newTempDir: Text -> Text
removeDir: Text -> ()
-- A handler for TempDirs which cleans up temporary directories
-- This will be useful for IO tests which need to interact with
-- This will be useful for IO tests which need to interact with
-- the filesystem
autoCleaned.handler: '{io2.IO} (Request {TempDirs} r -> r)
autoCleaned.handler _ =
remover : [Text] -> {io2.IO} ()
remover = cases
a +: as -> match removeDirectory.impl a with
a +: as -> match removeDirectory.impl a with
Left (Failure _ e _) -> watch e ()
_ -> ()
remover as
@ -277,12 +277,12 @@ putBytes = compose2 reraise putBytes.impl
getLine = compose reraise getLine.impl
systemTime = compose reraise systemTime.impl
decodeCert = compose reraise decodeCert.impl
serverSocket = compose2 reraise serverSocket.impl
serverSocket = compose2 reraise IO.serverSocket.impl
listen = compose reraise listen.impl
handshake = compose reraise handshake.impl
send = compose2 reraise send.impl
handshake = compose reraise handshake.impl
send = compose2 reraise Tls.send.impl
closeSocket = compose reraise closeSocket.impl
clientSocket = compose2 reraise clientSocket.impl
clientSocket = compose2 reraise IO.clientSocket.impl
receive = compose reraise receive.impl
terminate = compose reraise terminate.impl
newServer = compose2 reraise newServer.impl
@ -451,4 +451,3 @@ saveTestCase name ver f i =
saveSelfContained (f, i) sfile
writeFile ofile (toUtf8 output)
writeFile hfile (Bytes.toBase32 (crypto.hash Sha3_512 (f, i)))

View File

@ -145,6 +145,11 @@ And here's the full API:
15. hashBytes : HashAlgorithm -> Bytes -> Bytes
16. hmac : HashAlgorithm -> Bytes -> a -> Bytes
17. hmacBytes : HashAlgorithm -> Bytes -> Bytes -> Bytes
18. Rsa.sign.impl : Bytes -> Bytes -> Either Failure Bytes
19. Rsa.verify.impl : Bytes
-> Bytes
-> Bytes
-> Either Failure Boolean
```

View File

@ -1,9 +1,9 @@
```unison:hide
serverSocket = compose2 reraise serverSocket.impl
serverSocket = compose2 reraise IO.serverSocket.impl
socketPort = compose reraise socketPort.impl
listen = compose reraise listen.impl
closeSocket = compose reraise closeSocket.impl
clientSocket = compose2 reraise clientSocket.impl
clientSocket = compose2 reraise IO.clientSocket.impl
socketSend = compose2 reraise socketSend.impl
socketReceive = compose2 reraise socketReceive.impl
socketAccept = compose reraise socketAccept.impl
@ -36,11 +36,11 @@ stored in `/etc/services` and queried with the `getent` tool:
# map number to name
$ getent services 22
ssh 22/tcp
# map name to number
$ getent services finger
finger 79/tcp
# get a list of all known names
$ getent services | head
tcpmux 1/tcp
@ -58,35 +58,35 @@ Below shows different examples of how we might specify the server coordinates.
``` unison
testExplicitHost : '{io2.IO} [Result]
testExplicitHost _ =
testExplicitHost _ =
test = 'let
sock = serverSocket (Some "127.0.0.1") "1028"
emit (Ok "successfully created socket")
port = socketPort sock
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU "should have bound to port 1028" 1028 port
expectU "should have bound to port 1028" 1028 port
runTest test
testDefaultHost : '{io2.IO} [Result]
testDefaultHost _ =
testDefaultHost _ =
test = 'let
sock = serverSocket None "1028"
emit (Ok "successfully created socket")
port = socketPort sock
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU "should have bound to port 1028" 1028 port
expectU "should have bound to port 1028" 1028 port
runTest test
testDefaultPort : '{io2.IO} [Result]
testDefaultPort _ =
testDefaultPort _ =
test = 'let
sock = serverSocket None "0"
emit (Ok "successfully created socket")
port = socketPort sock
putBytes (stdHandle StdOut) (toUtf8 (toText port))
check "port should be > 1024" (1024 < port)
check "port should be < 65536" (65536 > port)
@ -113,7 +113,7 @@ serverThread portVar toSend = 'let
socketSend sock' (toUtf8 toSend)
closeSocket sock'
match (toEither go) with
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
@ -130,24 +130,24 @@ clientThread portVar resultVar = 'let
_ -> ()
testTcpConnect : '{io2.IO}[Result]
testTcpConnect = 'let
testTcpConnect = 'let
test = 'let
portVar = !MVar.newEmpty
resultVar = !MVar.newEmpty
toSend = "12345"
void (forkComp (serverThread portVar toSend))
void (forkComp (clientThread portVar resultVar))
received = take resultVar
expectU "should have reaped what we've sown" toSend received
runTest test
```
```ucm
```ucm
.> add
.> io.test testTcpConnect

View File

@ -1,9 +1,9 @@
```unison
serverSocket = compose2 reraise serverSocket.impl
serverSocket = compose2 reraise IO.serverSocket.impl
socketPort = compose reraise socketPort.impl
listen = compose reraise listen.impl
closeSocket = compose reraise closeSocket.impl
clientSocket = compose2 reraise clientSocket.impl
clientSocket = compose2 reraise IO.clientSocket.impl
socketSend = compose2 reraise socketSend.impl
socketReceive = compose2 reraise socketReceive.impl
socketAccept = compose reraise socketAccept.impl
@ -34,11 +34,11 @@ stored in `/etc/services` and queried with the `getent` tool:
# map number to name
$ getent services 22
ssh 22/tcp
# map name to number
$ getent services finger
finger 79/tcp
# get a list of all known names
$ getent services | head
tcpmux 1/tcp
@ -56,35 +56,35 @@ Below shows different examples of how we might specify the server coordinates.
```unison
testExplicitHost : '{io2.IO} [Result]
testExplicitHost _ =
testExplicitHost _ =
test = 'let
sock = serverSocket (Some "127.0.0.1") "1028"
emit (Ok "successfully created socket")
port = socketPort sock
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU "should have bound to port 1028" 1028 port
expectU "should have bound to port 1028" 1028 port
runTest test
testDefaultHost : '{io2.IO} [Result]
testDefaultHost _ =
testDefaultHost _ =
test = 'let
sock = serverSocket None "1028"
emit (Ok "successfully created socket")
port = socketPort sock
putBytes (stdHandle StdOut) (toUtf8 (toText port))
expectU "should have bound to port 1028" 1028 port
expectU "should have bound to port 1028" 1028 port
runTest test
testDefaultPort : '{io2.IO} [Result]
testDefaultPort _ =
testDefaultPort _ =
test = 'let
sock = serverSocket None "0"
emit (Ok "successfully created socket")
port = socketPort sock
putBytes (stdHandle StdOut) (toUtf8 (toText port))
check "port should be > 1024" (1024 < port)
check "port should be < 65536" (65536 > port)
@ -143,7 +143,7 @@ serverThread portVar toSend = 'let
socketSend sock' (toUtf8 toSend)
closeSocket sock'
match (toEither go) with
match (toEither go) with
Left (Failure _ t _) -> watch t ()
_ -> ()
@ -160,22 +160,22 @@ clientThread portVar resultVar = 'let
_ -> ()
testTcpConnect : '{io2.IO}[Result]
testTcpConnect = 'let
testTcpConnect = 'let
test = 'let
portVar = !MVar.newEmpty
resultVar = !MVar.newEmpty
toSend = "12345"
void (forkComp (serverThread portVar toSend))
void (forkComp (clientThread portVar resultVar))
received = take resultVar
expectU "should have reaped what we've sown" toSend received
runTest test
```
```ucm

View File

@ -17,74 +17,77 @@ The `builtins.merge` command adds the known builtins to a `builtin` subnamespace
6. Bytes/ (34 terms)
7. Char (builtin type)
8. Char/ (22 terms, 1 type)
9. Code (builtin type)
10. Code/ (9 terms)
11. Debug/ (3 terms)
12. Doc (type)
13. Doc/ (6 terms)
14. Either (type)
15. Either/ (2 terms)
16. Exception (type)
17. Exception/ (1 term)
18. Float (builtin type)
19. Float/ (38 terms)
20. Handle/ (1 term)
21. ImmutableArray (builtin type)
22. ImmutableArray/ (3 terms)
23. ImmutableByteArray (builtin type)
24. ImmutableByteArray/ (8 terms)
25. Int (builtin type)
26. Int/ (31 terms)
27. IsPropagated (type)
28. IsPropagated/ (1 term)
29. IsTest (type)
30. IsTest/ (1 term)
31. Link (type)
32. Link/ (3 terms, 2 types)
33. List (builtin type)
34. List/ (10 terms)
35. MutableArray (builtin type)
36. MutableArray/ (6 terms)
37. MutableByteArray (builtin type)
38. MutableByteArray/ (14 terms)
39. Nat (builtin type)
40. Nat/ (28 terms)
41. Optional (type)
42. Optional/ (2 terms)
43. Pattern (builtin type)
44. Pattern/ (9 terms)
45. Ref (builtin type)
46. Ref/ (2 terms)
47. Request (builtin type)
48. RewriteCase (type)
49. RewriteCase/ (1 term)
50. RewriteSignature (type)
51. RewriteSignature/ (1 term)
52. RewriteTerm (type)
53. RewriteTerm/ (1 term)
54. Rewrites (type)
55. Rewrites/ (1 term)
56. Scope (builtin type)
57. Scope/ (6 terms)
58. SeqView (type)
59. SeqView/ (2 terms)
60. Socket/ (1 term)
61. Test/ (2 terms, 1 type)
62. Text (builtin type)
63. Text/ (34 terms)
64. ThreadId/ (1 term)
65. Tuple (type)
66. Tuple/ (1 term)
67. Unit (type)
68. Unit/ (1 term)
69. Universal/ (7 terms)
70. Value (builtin type)
71. Value/ (5 terms)
72. bug (a -> b)
73. crypto/ (15 terms, 2 types)
74. io2/ (135 terms, 32 types)
75. metadata/ (2 terms)
76. todo (a -> b)
77. unsafe/ (1 term)
9. ClientSockAddr (builtin type)
10. Code (builtin type)
11. Code/ (9 terms)
12. Debug/ (3 terms)
13. Doc (type)
14. Doc/ (6 terms)
15. Either (type)
16. Either/ (2 terms)
17. Exception (type)
18. Exception/ (1 term)
19. Float (builtin type)
20. Float/ (38 terms)
21. Handle/ (1 term)
22. ImmutableArray (builtin type)
23. ImmutableArray/ (3 terms)
24. ImmutableByteArray (builtin type)
25. ImmutableByteArray/ (8 terms)
26. Int (builtin type)
27. Int/ (31 terms)
28. IsPropagated (type)
29. IsPropagated/ (1 term)
30. IsTest (type)
31. IsTest/ (1 term)
32. Link (type)
33. Link/ (3 terms, 2 types)
34. List (builtin type)
35. List/ (10 terms)
36. ListenSocket (builtin type)
37. MutableArray (builtin type)
38. MutableArray/ (6 terms)
39. MutableByteArray (builtin type)
40. MutableByteArray/ (14 terms)
41. Nat (builtin type)
42. Nat/ (28 terms)
43. Optional (type)
44. Optional/ (2 terms)
45. Pattern (builtin type)
46. Pattern/ (9 terms)
47. Ref (builtin type)
48. Ref/ (2 terms)
49. Request (builtin type)
50. RewriteCase (type)
51. RewriteCase/ (1 term)
52. RewriteSignature (type)
53. RewriteSignature/ (1 term)
54. RewriteTerm (type)
55. RewriteTerm/ (1 term)
56. Rewrites (type)
57. Rewrites/ (1 term)
58. Scope (builtin type)
59. Scope/ (6 terms)
60. SeqView (type)
61. SeqView/ (2 terms)
62. Socket/ (1 term)
63. Test/ (2 terms, 1 type)
64. Text (builtin type)
65. Text/ (34 terms)
66. ThreadId/ (1 term)
67. Tuple (type)
68. Tuple/ (1 term)
69. UDPSocket (builtin type)
70. Unit (type)
71. Unit/ (1 term)
72. Universal/ (7 terms)
73. Value (builtin type)
74. Value/ (5 terms)
75. bug (a -> b)
76. crypto/ (17 terms, 2 types)
77. io2/ (146 terms, 32 types)
78. metadata/ (2 terms)
79. todo (a -> b)
80. unsafe/ (1 term)
```

View File

@ -23,7 +23,7 @@ Technically, the definitions all exist, but they have no names. `builtins.merge`
.foo> ls
1. builtin/ (456 terms, 71 types)
1. builtin/ (469 terms, 74 types)
```
And for a limited time, you can get even more builtin goodies:
@ -35,7 +35,7 @@ And for a limited time, you can get even more builtin goodies:
.foo> ls
1. builtin/ (630 terms, 89 types)
1. builtin/ (643 terms, 92 types)
```
More typically, you'd start out by pulling `base.

View File

@ -37,7 +37,7 @@ Exception.unsafeRun! e _ =
socketSend s bytes = reraise (socketSend.impl s bytes)
closeSocket s = reraise (closeSocket.impl s)
serverSocket host port = reraise (serverSocket.impl host port)
serverSocket host port = reraise (IO.serverSocket.impl host port)
hello : Text -> Text -> {IO, Exception} ()
hello host port =

View File

@ -33,7 +33,7 @@ Exception.unsafeRun! e _ =
socketSend s bytes = reraise (socketSend.impl s bytes)
closeSocket s = reraise (closeSocket.impl s)
serverSocket host port = reraise (serverSocket.impl host port)
serverSocket host port = reraise (IO.serverSocket.impl host port)
hello : Text -> Text -> {IO, Exception} ()
hello host port =

View File

@ -119,13 +119,13 @@ it's still in the `history` of the parent namespace and can be resurrected at an
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #mqis95ft23
⊙ 1. #6j9omad7mv
- Deletes:
feature1.y
⊙ 2. #5ro9c9692q
⊙ 2. #59u4sdgodu
+ Adds / updates:
@ -136,26 +136,26 @@ it's still in the `history` of the parent namespace and can be resurrected at an
Original name New name(s)
feature1.y master.y
⊙ 3. #da33td9rni
⊙ 3. #0je96at36h
+ Adds / updates:
feature1.y
⊙ 4. #ks6rftepdv
⊙ 4. #cnv4gjntbl
> Moves:
Original name New name
x master.x
⊙ 5. #dgcqc7jftr
⊙ 5. #tp0bn8ulih
+ Adds / updates:
x
□ 6. #ms344fdodl (start of history)
□ 6. #cujaete914 (start of history)
```
To resurrect an old version of a namespace, you can learn its hash via the `history` command, then use `fork #namespacehash .newname`.

View File

@ -80,7 +80,7 @@ Should be able to move the term, type, and namespace, including its types, terms
1. Bar (Nat)
2. Bar (type)
3. Bar/ (4 terms, 1 type)
4. builtin/ (456 terms, 71 types)
4. builtin/ (469 terms, 74 types)
.> ls Bar
@ -145,7 +145,7 @@ bonk = 5
.z> ls
1. builtin/ (456 terms, 71 types)
1. builtin/ (469 terms, 74 types)
2. zonk (Nat)
```
@ -188,7 +188,7 @@ bonk.zonk = 5
.a> ls
1. builtin/ (456 terms, 71 types)
1. builtin/ (469 terms, 74 types)
2. zonk/ (1 term)
.a> view zonk.zonk

View File

@ -277,7 +277,7 @@ I should be able to move the root into a sub-namespace
.> ls
1. root/ (1373 terms, 214 types)
1. root/ (1412 terms, 223 types)
.> history
@ -286,22 +286,22 @@ I should be able to move the root into a sub-namespace
□ 1. #vrn80pdffk (start of history)
□ 1. #o7cku9c0t9 (start of history)
```
```ucm
.> ls .root.at.path
1. existing/ (457 terms, 71 types)
2. happy/ (459 terms, 72 types)
3. history/ (457 terms, 71 types)
1. existing/ (470 terms, 74 types)
2. happy/ (472 terms, 75 types)
3. history/ (470 terms, 74 types)
.> history .root.at.path
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #g3ri07hi09
⊙ 1. #fv72cqfto4
- Deletes:
@ -312,7 +312,7 @@ I should be able to move the root into a sub-namespace
Original name New name
existing.a.termInA existing.b.termInA
⊙ 2. #ifjg1bj57v
⊙ 2. #12iqsb3l9g
+ Adds / updates:
@ -324,26 +324,26 @@ I should be able to move the root into a sub-namespace
happy.b.termInA existing.a.termInA
history.b.termInA existing.a.termInA
⊙ 3. #bdn8f7vhg1
⊙ 3. #r9jmgtco5u
+ Adds / updates:
existing.a.termInA existing.b.termInB
⊙ 4. #5dqmgnr0lt
⊙ 4. #1k6kae1vn4
> Moves:
Original name New name
history.a.termInA history.b.termInA
⊙ 5. #vd3d37rn3c
⊙ 5. #ua9re7leg7
- Deletes:
history.b.termInB
⊙ 6. #gi32sh566a
⊙ 6. #3k8ouql6cc
+ Adds / updates:
@ -354,13 +354,13 @@ I should be able to move the root into a sub-namespace
Original name New name(s)
happy.b.termInA history.a.termInA
⊙ 7. #u2bs53f2hl
⊙ 7. #fp2331i1ek
+ Adds / updates:
history.a.termInA history.b.termInB
⊙ 8. #48hsm89mgl
⊙ 8. #5sj5jefgcu
> Moves:
@ -370,7 +370,7 @@ I should be able to move the root into a sub-namespace
happy.a.T.T2 happy.b.T.T2
happy.a.termInA happy.b.termInA
⊙ 9. #pqd79g3q7l
⊙ 9. #ell48pttus
+ Adds / updates:
@ -380,7 +380,7 @@ I should be able to move the root into a sub-namespace
happy.a.T.T
⊙ 10. #allrjqq7ga
⊙ 10. #al8eguoh70
+ Adds / updates:
@ -392,7 +392,7 @@ I should be able to move the root into a sub-namespace
⊙ 11. #ohd0a9rim1
⊙ 11. #okceqk39nf
```
@ -414,26 +414,26 @@ I should be able to move a sub namespace _over_ the root.
.> ls
1. b/ (3 terms, 1 type)
2. builtin/ (456 terms, 71 types)
2. builtin/ (469 terms, 74 types)
.> history
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #lf3m1s2e7i
⊙ 1. #0rvi5q5une
+ Adds / updates:
b.T b.T.T1 b.T.T2 b.termInA
⊙ 2. #b1cg22v7s1
⊙ 2. #oaa8ltdusf
- Deletes:
a.T a.T.T1 a.T.T2 a.termInA
⊙ 3. #r83v608ifd
⊙ 3. #t1c91ou7ri
+ Adds / updates:
@ -443,13 +443,13 @@ I should be able to move a sub namespace _over_ the root.
a.T.T
⊙ 4. #pmm6a0f6fj
⊙ 4. #hovh08jep4
+ Adds / updates:
a.T a.T.T a.termInA
□ 5. #nmcjvlnbk1 (start of history)
□ 5. #4bigcpnl7t (start of history)
```
```ucm

View File

@ -63,17 +63,17 @@ y = 2
most recent, along with the command that got us there. Try:
`fork 2 .old`
`fork #mq4oqhiuuq .old` to make an old namespace
`fork #p611n6o5ve .old` to make an old namespace
accessible again,
`reset-root #mq4oqhiuuq` to reset the root namespace and
`reset-root #p611n6o5ve` to reset the root namespace and
its history to that of the
specified namespace.
When Root Hash Action
1. now #1n5tjujeu7 add
2. now #mq4oqhiuuq add
3. now #nmcjvlnbk1 builtins.merge
1. now #rmu2vgm86a add
2. now #p611n6o5ve add
3. now #4bigcpnl7t builtins.merge
4. #sg60bvjo91 history starts here
Tip: Use `diff.namespace 1 7` to compare namespaces between

View File

@ -28,13 +28,13 @@ a = 5
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #0nv4t3770d
⊙ 1. #d079vet1oj
+ Adds / updates:
a
□ 2. #nmcjvlnbk1 (start of history)
□ 2. #4bigcpnl7t (start of history)
.> reset 2
@ -47,7 +47,7 @@ a = 5
□ 1. #nmcjvlnbk1 (start of history)
□ 1. #4bigcpnl7t (start of history)
```
```unison
@ -83,13 +83,13 @@ foo.a = 5
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #3s91aop8k9
⊙ 1. #tfg7r9359n
+ Adds / updates:
foo.a
□ 2. #nmcjvlnbk1 (start of history)
□ 2. #4bigcpnl7t (start of history)
.> reset 1 foo

View File

@ -0,0 +1,37 @@
```ucm:hide
.> builtins.merge
```
```unison
up = 0xs0123456789abcdef
down = 0xsfedcba9876543210
-- | Generated with:
-- openssl genrsa -out private_key.pem 1024
-- openssl rsa -in private_key.pem -outform DER | xxd -p
secret = 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65
-- | Generated with:
-- openssl rsa -in private_key.pem -outform DER -pubout | xxd -p
publicKey = 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001
incorrectPublicKey = 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010002
message = up ++ down ++ up ++ down ++ down ++ up ++ down ++ up
signature = crypto.Rsa.sign.impl secret message
sigOkay = match signature with
Left err -> Left err
Right sg -> crypto.Rsa.verify.impl publicKey message sg
sigKo = match signature with
Left err -> Left err
Right sg -> crypto.Rsa.verify.impl incorrectPublicKey message sg
> signature
> sigOkay
> sigKo
```

View File

@ -0,0 +1,70 @@
```unison
up = 0xs0123456789abcdef
down = 0xsfedcba9876543210
-- | Generated with:
-- openssl genrsa -out private_key.pem 1024
-- openssl rsa -in private_key.pem -outform DER | xxd -p
secret = 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65
-- | Generated with:
-- openssl rsa -in private_key.pem -outform DER -pubout | xxd -p
publicKey = 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001
incorrectPublicKey = 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010002
message = up ++ down ++ up ++ down ++ down ++ up ++ down ++ up
signature = crypto.Rsa.sign.impl secret message
sigOkay = match signature with
Left err -> Left err
Right sg -> crypto.Rsa.verify.impl publicKey message sg
sigKo = match signature with
Left err -> Left err
Right sg -> crypto.Rsa.verify.impl incorrectPublicKey message sg
> signature
> sigOkay
> sigKo
```
```ucm
Loading changes detected in scratch.u.
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
down : Bytes
incorrectPublicKey : Bytes
message : Bytes
publicKey : Bytes
secret : Bytes
sigKo : Either Failure Boolean
sigOkay : Either Failure Boolean
signature : Either Failure Bytes
up : Bytes
Now evaluating any watch expressions (lines starting with
`>`)... Ctrl+C cancels.
27 | > signature
Right
0xs84b02b6bb0e1196b65378cb12b727f7b4b38e5979f0632e8a51cfab088827f6d3da4221788029f75a0a5f4d740372cfa590462888a1189bbd9de9b084f26116640e611af5a1a17229beec7fb2570887181bbdced8f0ebfec6cad6bdd318a616ba4f01c90e1436efe44b18417d18ce712a0763be834f8c76e0c39b2119b061373
28 | > sigOkay
Right true
29 | > sigKo
Right false
```

View File

@ -13,7 +13,7 @@ Let's look at some examples. We'll start with a namespace with just the builtins
□ 1. #3pq2vvggng (start of history)
□ 1. #i3vp9o9btm (start of history)
.> fork builtin builtin2
@ -42,21 +42,21 @@ Now suppose we `fork` a copy of builtin, then rename `Nat.+` to `frobnicate`, th
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #4g884gq7lc
⊙ 1. #tpkjb488ei
> Moves:
Original name New name
Nat.frobnicate Nat.+
⊙ 2. #hnah4l7s0j
⊙ 2. #334ak3epqt
> Moves:
Original name New name
Nat.+ Nat.frobnicate
□ 3. #3pq2vvggng (start of history)
□ 3. #i3vp9o9btm (start of history)
```
If we merge that back into `builtin`, we get that same chain of history:
@ -73,21 +73,21 @@ If we merge that back into `builtin`, we get that same chain of history:
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #4g884gq7lc
⊙ 1. #tpkjb488ei
> Moves:
Original name New name
Nat.frobnicate Nat.+
⊙ 2. #hnah4l7s0j
⊙ 2. #334ak3epqt
> Moves:
Original name New name
Nat.+ Nat.frobnicate
□ 3. #3pq2vvggng (start of history)
□ 3. #i3vp9o9btm (start of history)
```
Let's try again, but using a `merge.squash` (or just `squash`) instead. The history will be unchanged:
@ -108,7 +108,7 @@ Let's try again, but using a `merge.squash` (or just `squash`) instead. The hist
□ 1. #3pq2vvggng (start of history)
□ 1. #i3vp9o9btm (start of history)
```
The churn that happened in `mybuiltin` namespace ended up back in the same spot, so the squash merge of that namespace with our original namespace had no effect.
@ -493,13 +493,13 @@ This checks to see that squashing correctly preserves deletions:
Note: The most recent namespace hash is immediately below this
message.
⊙ 1. #jdptkosbfp
⊙ 1. #dv00hf6vmg
- Deletes:
Nat.* Nat.+
□ 2. #3pq2vvggng (start of history)
□ 2. #i3vp9o9btm (start of history)
```
Notice that `Nat.+` and `Nat.*` are deleted by the squash, and we see them deleted in one atomic step in the history.

View File

@ -57,7 +57,7 @@ proj/main> upgrade old new
proj/main> ls lib
1. builtin/ (456 terms, 71 types)
1. builtin/ (469 terms, 74 types)
2. new/ (1 term)
proj/main> view thingy