From 3399f664165932e4921f61d48a7bdf4c680da48a Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 17 Aug 2016 18:19:23 -0400 Subject: [PATCH 01/61] Added tuple types and literals, Either/Option functions --- shared/src/Unison/Eval/Interpreter.hs | 45 +++++++--------- shared/src/Unison/Node/Builtin.hs | 46 ++++++++++++++-- shared/src/Unison/Parsers.hs | 71 +++++++++++++++++++++---- shared/src/Unison/TermParser.hs | 10 +++- shared/src/Unison/TypeParser.hs | 12 ++++- shared/tests/Unison/Test/Interpreter.hs | 15 ++++-- 6 files changed, 156 insertions(+), 43 deletions(-) diff --git a/shared/src/Unison/Eval/Interpreter.hs b/shared/src/Unison/Eval/Interpreter.hs index 9949992b5..473c99c8f 100644 --- a/shared/src/Unison/Eval/Interpreter.hs +++ b/shared/src/Unison/Eval/Interpreter.hs @@ -25,34 +25,32 @@ eval :: forall f v . (Monad f, Var v) => Map R.Reference (Primop f v) -> Eval f eval env = Eval whnf step where -- reduce x args | trace ("reduce:" ++ show (x:args)) False = undefined - reduce :: Term v -> [Term v] -> f (Maybe (Term v)) - reduce (E.Lam' _) [] = pure Nothing - reduce (E.Lam' f) (arg1:args) = - let r = ABT.bind f arg1 - in pure $ Just (foldl E.app r args) - reduce (E.Ref' h) args = case M.lookup h env of - Nothing -> pure Nothing - Just op | length args >= arity op -> - call op (take (arity op) args) >>= \e -> - pure . Just $ foldl E.app e (drop (arity op) args) - Just _ | otherwise -> pure Nothing - reduce (E.App' f x) args = reduce f (x:args) - reduce (E.Let1' binding body) xs = reduce (ABT.bind body binding) xs - reduce _ _ = pure Nothing + reduce resolveRef (E.App' f x) args = reduce resolveRef f (x:args) + reduce resolveRef (E.Let1' binding body) xs = reduce resolveRef (ABT.bind body binding) xs + reduce resolveRef f args = do + f <- whnf resolveRef f + case f of + E.Ref' h -> case M.lookup h env of + Nothing -> pure Nothing + Just op | length args >= arity op -> + call op (take (arity op) args) >>= \e -> + pure . Just $ foldl E.app e (drop (arity op) args) + Just _ | otherwise -> pure Nothing + E.Lam' f -> case args of + [] -> pure Nothing + (arg1:args) -> + let r = ABT.bind f arg1 + in pure $ Just (foldl E.app r args) + _ -> pure Nothing step resolveRef e = case e of E.Ref' h -> case M.lookup h env of Just op | arity op == 0 -> call op [] _ -> pure e - E.App' (E.LetRecNamed' bs body) x -> step resolveRef (E.letRec bs (body `E.app` x)) E.App' f x -> do - f' <- E.link resolveRef f - e' <- reduce f' [x] + f <- E.link resolveRef f + e' <- reduce resolveRef f [x] maybe (pure e) pure e' - E.Ref' h -> do - f <- E.link resolveRef (E.ref h) - e <- reduce f [] - maybe (pure f) pure e E.Let1' binding body -> step resolveRef (ABT.bind body binding) E.LetRecNamed' bs body -> step resolveRef (ABT.substs substs body) where expandBinding v (E.LamNamed' name body) = E.lam name (expandBinding v body) @@ -66,12 +64,9 @@ eval env = Eval whnf step Just op | arity op == 0 -> call op [] _ -> pure e E.Ann' e _ -> whnf resolveRef e - E.App' (E.Ann' f _) x -> whnf resolveRef (f `E.app` x) - E.App' (E.LetRecNamed' bs body) x -> whnf resolveRef (E.letRec bs (body `E.app` x)) - E.App' (E.Let1Named' v b body) x -> whnf resolveRef (E.let1 [(v,b)] (body `E.app` x)) E.App' f x -> do f' <- E.link resolveRef f - e' <- reduce f' [x] + e' <- reduce resolveRef f' [x] maybe (pure e) (whnf resolveRef) e' E.Let1' binding body -> whnf resolveRef (ABT.bind body binding) E.LetRecNamed' bs body -> whnf resolveRef (ABT.substs substs body) where diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index c94b73204..52e5c1448 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -70,7 +70,7 @@ makeBuiltins whnf = , let r = R.Builtin "Color.rgba" in (r, strict r 4, unsafeParseType "Number -> Number -> Number -> Number -> Color", prefix "rgba") - -- booleans + -- Boolean , let r = R.Builtin "True" in (r, Nothing, Type.builtin "Boolean", prefix "True") , let r = R.Builtin "False"; @@ -88,7 +88,7 @@ makeBuiltins whnf = typ = "forall a . Boolean -> a -> a -> a" in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "if") - -- numbers + -- Number , let r = R.Builtin "Number.plus" in (r, Just (numeric2 (Term.ref r) (+)), numOpTyp, assoc 4 "+") , let r = R.Builtin "Number.minus" @@ -108,7 +108,7 @@ makeBuiltins whnf = , let r = R.Builtin "Number.equal" in (r, Just (numericCompare (Term.ref r) (==)), numCompareTyp, opl 3 "==") - -- remote computations + -- Remote , let r = R.Builtin "Remote.at" op [node,term] = do Term.Distributed' (Term.Node node) <- whnf node @@ -178,6 +178,7 @@ makeBuiltins whnf = , let r = R.Builtin "Symbol.Symbol" in (r, Nothing, unsafeParseType "Text -> Fixity -> Number -> Symbol", prefix "Symbol") + -- Text , let r = R.Builtin "Text.concatenate" in (r, Just (string2 (Term.ref r) mappend), strOpTyp, prefixes ["concatenate", "Text"]) , let r = R.Builtin "Text.left" @@ -189,6 +190,45 @@ makeBuiltins whnf = , let r = R.Builtin "Text.justify" in (r, Nothing, alignmentT, prefixes ["justify", "Text"]) + -- Pair + , let r = R.Builtin "Pair" + in (r, Nothing, unsafeParseType "forall a b . a -> b -> Pair a b", prefix "Pair") + , let r = R.Builtin "Pair.fold" + op [f,p] = do + Term.Apps' (Term.Builtin' "Pair") [a,b] <- whnf p + whnf (f `Term.apps` [a,b]) + op _ = error "Pair.fold unpossible" + in (r, Just (I.Primop 2 op), unsafeParseType "forall a b c . (a -> b -> c) -> Pair a b -> c", prefix "fold") + + -- Either + , let r = R.Builtin "Either.Left" + in (r, Nothing, unsafeParseType "forall a b . a -> Either a b", prefix "Left") + , let r = R.Builtin "Either.Right" + in (r, Nothing, unsafeParseType "forall a b . b -> Either a b", prefix "Right") + , let r = R.Builtin "Either.fold" + op [fa,fb,e] = do + Term.App' (Term.Builtin' tag) aOrB <- whnf e + case tag of + _ | tag == "Either.Left" -> whnf (fa `Term.app` aOrB) + | tag == "Either.Right" -> whnf (fb `Term.app` aOrB) + | otherwise -> error "type errror" + op _ = error "Either.fold unpossible" + in (r, Just (I.Primop 3 op), unsafeParseType "forall a b r . (a -> r) -> (b -> r) -> Either a b -> r", prefix "fold") + + -- Optional + , let r = R.Builtin "Optional.None" + in (r, Nothing, unsafeParseType "forall a . Optional a", prefix "None") + , let r = R.Builtin "Optional.Some" + in (r, Nothing, unsafeParseType "forall a . a -> Optional a", prefix "Some") + , let r = R.Builtin "Optional.fold" + op [fz,f,o] = whnf o >>= \o -> case o of + Term.Builtin' tag | tag == "Optional.None" -> whnf fz + Term.App' (Term.Builtin' tag) a | tag == "Optional.Some" -> whnf (f `Term.app` a) + _ -> error "Optional.fold unpossible" + op _ = error "Optional.fold unpossible" + in (r, Just (I.Primop 3 op), unsafeParseType "forall a r . r -> (a -> r) -> Optional a -> r", prefix "fold") + + -- Vector , let r = R.Builtin "Vector.append" op [last,init] = do initr <- whnf init diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index 32cba2eb5..ae0315f67 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -38,18 +38,64 @@ parseType' typeBuiltins s = case run (Parser.root TypeParser.type_) s of Succeed t n b -> Succeed (ABT.substs typeBuiltins t) n b fail -> fail -prelude = unlines +prelude' :: String -> String +prelude' body = unlines [ "let" , " Index.empty : forall k v . Remote (Index k v);" - , " Index.empty = Remote.map Index.unsafeEmpty Remote.here;" - , "" + , " Index.empty = Remote.map Index.unsafeEmpty Remote.here" + , "in", "(", prelude body, ")"] + +prelude :: String -> String +prelude body = unlines + [ "let" , " Remote.transfer : Node -> Remote Unit;" - , " Remote.transfer node = Remote.at node unit" - , "in" - , ""] + , " Remote.transfer node = Remote.at node unit;" + , "" + , " then : forall a b c . (a -> b) -> (b -> c) -> a -> c;" + , " then f1 f2 x = f2 (f1 x);" + , "" + , " Optional.map : forall a b . (a -> b) -> Optional a -> Optional b;" + , " Optional.map f = Optional.fold None (f `then` Some);" + , "" + , " Optional.bind : forall a b . (a -> Optional b) -> Optional a -> Optional b;" + , " Optional.bind f = Optional.fold None f;" + , "" + , " Optional.pure : forall a . a -> Optional a;" + , " Optional.pure = Some;" + , "" + , " Either.map : forall a b c . (b -> c) -> Either a b -> Either a c;" + , " Either.map f = Either.fold Left (f `then` Right);" + , "" + , " Either.pure : forall a b . b -> Either a b;" + , " Either.pure = Right;" + , "" + , " Either.bind : forall a b c . (b -> Either a c) -> Either a b -> Either a c;" + , " Either.bind = Either.fold Left;" + , "" + , " Either.swap : forall a b . Either a b -> Either b a;" + , " Either.swap e = Either.fold Right Left e;" + , "" + , " const x y = x;" + , "" + , " first : forall a b . Pair a b -> a;" + , " first p = Pair.fold const p;" + , "" + , " rest : forall a b . Pair a b -> b;" + , " rest p = Pair.fold (x y -> y) p;" + , "" + , " 1st = first;" + , " 2nd = rest `then` first;" + , " 3rd = rest `then` (rest `then` first);" + , " 4th = rest `then` (rest `then` (rest `then` first));" + , " 5th = rest `then` (rest `then` (rest `then` (rest `then` first)))" + , "" + , "in" , "(", body, ")"] + +unsafeParseTermWithPrelude' :: String -> Term V +unsafeParseTermWithPrelude' prog = unsafeParseTerm (prelude' prog) unsafeParseTermWithPrelude :: String -> Term V -unsafeParseTermWithPrelude prog = unsafeParseTerm (prelude ++ prog) +unsafeParseTermWithPrelude prog = unsafeParseTerm (prelude prog) unsafeParseTerm :: String -> Term V unsafeParseTerm = unsafeGetSucceed . parseTerm @@ -90,8 +136,14 @@ termBuiltins = (Var.named *** Term.ref) <$> ( , Builtin "False" , Builtin "()" , Alias "unit" "()" - , Alias "some" "Optional.Some" - , Alias "none" "Optional.None" + , Alias "Some" "Optional.Some" + , Alias "None" "Optional.None" + , Alias "Left" "Either.Left" + , Alias "Right" "Either.Right" + , Builtin "Either.fold" + , Builtin "Optional.fold" + , Builtin "Pair.fold" + , Builtin "Pair" , AliasFromModule "Vector" ["single", "prepend", "map", "fold-left", "concatenate", "append"] ["empty"] , AliasFromModule "Text" @@ -124,6 +176,7 @@ typeBuiltins = (Var.named *** Type.lit) <$> , builtin "Boolean" , ("Optional", Type.Optional) , builtin "Either" + , builtin "Pair" -- ??? , builtin "Symbol" , builtin "Alignment" diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index 1b9ec63e7..def3c0548 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -56,7 +56,15 @@ term5 :: (Var v, Show v) => Parser (Term v) term5 = lam term <|> effectBlock <|> termLeaf termLeaf :: (Var v, Show v) => Parser (Term v) -termLeaf = asum [hashLit, prefixTerm, lit, parenthesized term, blank, vector term] +termLeaf = asum [hashLit, prefixTerm, lit, tupleOrParenthesized term, blank, vector term] + +tupleOrParenthesized :: (Var v, Show v) => Parser (Term v) -> Parser (Term v) +tupleOrParenthesized rec = + parenthesized $ go <$> sepBy1 (token $ string ",") rec where + go [t] = t -- was just a parenthesized term + go terms = foldr pair unit terms -- it's a tuple literal + pair t1 t2 = Term.builtin "Pair" `Term.app` t1 `Term.app` t2 + unit = Term.builtin "()" -- | -- Remote { x := pure 23; y := at node2 23; pure 19 } diff --git a/shared/src/Unison/TypeParser.hs b/shared/src/Unison/TypeParser.hs index 8121f1f60..503728711 100644 --- a/shared/src/Unison/TypeParser.hs +++ b/shared/src/Unison/TypeParser.hs @@ -1,3 +1,5 @@ +{-# Language OverloadedStrings #-} + module Unison.TypeParser where import Control.Applicative ((<|>), some) @@ -19,10 +21,18 @@ type_ = forall type1 <|> type1 typeLeaf :: Var v => Parser (Type v) typeLeaf = asum [ literal - , parenthesized type_ + , tupleOrParenthesized type_ , fmap (Type.v' . Text.pack) (token varName) ] +tupleOrParenthesized :: Ord v => Parser (Type v) -> Parser (Type v) +tupleOrParenthesized rec = + parenthesized $ go <$> sepBy1 (token $ string ",") rec where + go [t] = t + go types = foldr pair unit types + pair t1 t2 = Type.builtin "Pair" `Type.app` t1 `Type.app` t2 + unit = Type.builtin "Unit" + type1 :: Var v => Parser (Type v) type1 = arrow type2 diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index d2feaec66..ffd3e5488 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -2,7 +2,7 @@ module Unison.Test.Interpreter where import Test.Tasty import Test.Tasty.HUnit -import Unison.Parsers (unsafeParseTerm) +import qualified Unison.Parsers as P import qualified Unison.Node as Node import qualified Unison.Note as Note import qualified Unison.Test.Common as Common @@ -29,13 +29,20 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "let id x = x; g = id 42; p = id \"hi\" in g" "42" , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" in g" "42" , t "((let id x = x in id) : forall a . a -> a) 42" "42" + , t "Optional.map ((+) 1) (Some 1)" "Optional.Some (1 + 1)" + , t "Either.fold ((+) 1) ((+) 2) (Either.Left 1)" "2" + , t "Either.fold ((+) 1) ((+) 2) (Either.Right 1)" "3" + , t "Either.swap (Left 1)" "Either.Right 1" + , t "Pair.fold (x y -> x) (1, 2)" "1" + , t "1st (1,2,3,4)" "1" + , t "2nd (1,2 + 1,3,4)" "3" ] t uneval eval = testCase (uneval ++ " ⟹ " ++ eval) $ do (node, _) <- node - let term = unsafeParseTerm uneval + let term = P.unsafeParseTermWithPrelude uneval _ <- Note.run $ Node.typeAt node term [] - [(_,_,result)] <- Note.run $ Node.evaluateTerms node [([], unsafeParseTerm uneval)] - assertEqual "comparing results" (unsafeParseTerm eval) result + [(_,_,result)] <- Note.run $ Node.evaluateTerms node [([], term)] + assertEqual "comparing results" (P.unsafeParseTerm eval) result in testGroup "Interpreter" tests main = defaultMain tests From eb3318f66e34267b360adb9896c0b236e3778bd9 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 17 Aug 2016 21:36:41 -0400 Subject: [PATCH 02/61] Vector.split and Vector.empty? --- shared/src/Unison/Node/Builtin.hs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 52e5c1448..481de4c7c 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -35,6 +35,11 @@ data Builtin = Builtin unitRef :: Ord v => Term v unitRef = Term.ref (R.Builtin "()") +true, false :: Ord v => Term v +true = Term.builtin "True" +false = Term.builtin "False" +pair :: Ord v => Term v +pair = Term.builtin "Pair" makeBuiltins :: WHNFEval -> [Builtin] makeBuiltins whnf = @@ -49,8 +54,8 @@ makeBuiltins whnf = numericCompare sym f = I.Primop 2 $ \xs -> case xs of [x,y] -> g <$> whnf x <*> whnf y where g (Term.Number' x) (Term.Number' y) = case f x y of - False -> Term.builtin "False" - True -> Term.builtin "True" + False -> false + True -> true g x y = sym `Term.app` x `Term.app` y _ -> error "unpossible" strict r n = Just (I.Primop n f) @@ -250,6 +255,21 @@ makeBuiltins whnf = op [] = pure $ Term.vector mempty op _ = fail "Vector.empty unpossible" in (r, Just (I.Primop 0 op), unsafeParseType "forall a. Vector a", prefix "empty") + , let r = R.Builtin "Vector.empty?" + op [v] = do + Term.Vector' vs <- whnf v + pure $ if Vector.null vs then true else false + op _ = fail "Vector.empty? unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Boolean", prefix "empty?") + , let r = R.Builtin "Vector.split" + op [v] = do + Term.Vector' vs <- whnf v + pure $ case Vector.null vs of + True -> pair `Term.apps` [Term.vector [], Term.vector []] + False -> case Vector.splitAt (Vector.length vs `div` 2) vs of + (x,y) -> pair `Term.app` (Term.vector' x) `Term.app` (Term.vector' y) + op _ = fail "Vector.split unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Boolean", prefix "empty?") , let r = R.Builtin "Vector.fold-left" op [f,z,vec] = whnf vec >>= \vec -> case vec of Term.Vector' vs -> Vector.foldM (\acc a -> whnf (f `Term.apps` [acc, a])) z vs From 31f60722319225df491963873ed48056dc2a3a9c Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 18 Aug 2016 17:24:03 -0400 Subject: [PATCH 03/61] splitting base out into separate .u file, which is now parsed by tests --- shared/src/Unison/ABT.hs | 3 +- shared/src/Unison/Metadata.hs | 8 +- shared/src/Unison/Node.hs | 45 ++++++++++- shared/src/Unison/Node/Builtin.hs | 2 +- shared/src/Unison/Parser.hs | 6 +- shared/src/Unison/Parsers.hs | 78 +++---------------- shared/src/Unison/TermParser.hs | 42 +++++----- shared/src/Unison/TypeParser.hs | 11 ++- shared/tests/Unison/Test/Common.hs | 22 +++++- shared/tests/Unison/Test/Interpreter.hs | 5 +- shared/tests/Unison/Test/Term.hs | 10 +-- shared/tests/Unison/Test/Typechecker.hs | 4 +- .../Unison/Test/Typechecker/Components.hs | 2 +- unison-src/base.u | 40 ++++++++++ unison-src/extra.u | 3 + 15 files changed, 169 insertions(+), 112 deletions(-) create mode 100644 unison-src/base.u create mode 100644 unison-src/extra.u diff --git a/shared/src/Unison/ABT.hs b/shared/src/Unison/ABT.hs index e2b4e0a60..72509b929 100644 --- a/shared/src/Unison/ABT.hs +++ b/shared/src/Unison/ABT.hs @@ -221,6 +221,7 @@ freshNamed' used n = fresh' used (v' n) -- | `subst v e body` substitutes `e` for `v` in `body`, avoiding capture by -- renaming abstractions in `body` +-- TODO: avoid traversing subtrees that cannot contain the free variable subst :: (Foldable f, Functor f, Var v) => v -> Term f v a -> Term f v a -> Term f v a subst v = replace match where match (Var' v') = v == v' @@ -229,7 +230,7 @@ subst v = replace match where -- | `substs [(t1,v1), (t2,v2), ...] body` performs multiple simultaneous -- substitutions, avoiding capture substs :: (Foldable f, Functor f, Var v) => [(v, Term f v a)] -> Term f v a -> Term f v a -substs replacements body = foldr f body replacements where +substs replacements body = foldr f body (reverse replacements) where f (v, t) body = subst v t body -- | `replace f t body` substitutes `t` for all maximal (outermost) diff --git a/shared/src/Unison/Metadata.hs b/shared/src/Unison/Metadata.hs index 1511769d8..288af88bf 100644 --- a/shared/src/Unison/Metadata.hs +++ b/shared/src/Unison/Metadata.hs @@ -31,9 +31,13 @@ synthetic t = Metadata t (Names []) Nothing syntheticTerm :: Metadata v h syntheticTerm = synthetic Term -data Names v = Names [v] deriving (Eq,Ord,Show,Generic) +newtype Names v = Names [v] deriving (Eq,Ord,Show,Generic) -data Query = Query Text +firstName :: Names v -> Maybe v +firstName (Names (h:_)) = Just h +firstName _ = Nothing + +newtype Query = Query Text instance Show Query where show (Query q) = show q diff --git a/shared/src/Unison/Node.hs b/shared/src/Unison/Node.hs index 3a819adf8..7fa819cad 100644 --- a/shared/src/Unison/Node.hs +++ b/shared/src/Unison/Node.hs @@ -1,9 +1,11 @@ {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE OverloadedStrings #-} module Unison.Node where -- import Data.Bytes.Serial (Serial) import Control.Monad +import Data.Foldable import Data.Aeson.TH import Data.List import Data.Map (Map) @@ -12,7 +14,7 @@ import Data.Set (Set) import Unison.Eval as Eval import Unison.Metadata (Metadata) import Unison.Node.Store (Store) -import Unison.Note (Noted) +import Unison.Note (Noted(..),Note(..)) import Unison.Paths (Path) import Unison.Reference (Reference) import Unison.Term (Term) @@ -23,11 +25,17 @@ import qualified Data.Map as Map import qualified Data.Set as Set import qualified Unison.Metadata as Metadata import qualified Unison.Node.Store as Store +import qualified Unison.Parsers as Parsers +import qualified Unison.Parser as Parser import qualified Unison.Paths as Paths import qualified Unison.Reference as Reference import qualified Unison.Term as Term import qualified Unison.TermEdit as TermEdit +import qualified Unison.TermParser as TermParser import qualified Unison.Typechecker as Typechecker +import qualified Unison.Typechecker.Components as Components +import qualified Unison.Var as Var +-- import Debug.Trace -- | The results of a search. -- On client, only need to repeat the query if we modify a character @@ -230,3 +238,38 @@ node eval hash store = types typeAt updateMetadata + + +-- | Declare a group of bindings and add them to the Node. +-- Bindings may be in any order and may refer to each other. +-- They are broken into strongly connected components before +-- being added, and any free variables are resolved using the +-- existing metadata store of the Node. +declare :: (Monad m, Var v) => (h -> Term v) -> [(v, Term v)] -> Node m v h (Type v) (Term v) -> Noted m () +declare ref bindings node = do + termBuiltins <- do + -- grab all definitions in the node + results <- search node Term.blank [] 1000000 (Metadata.Query "") Nothing + pure [ (v, ref h) | (h, md) <- references results + , v <- toList $ Metadata.firstName (Metadata.names md) ] + let groups = Components.components bindings + -- watch msg a = trace (msg ++ show (map (Var.name . fst) a)) a + bindings' = groups >>= \c -> case c of + [(v,b)] -> [(v,b)] + _ -> [ (v, Term.letRec c b) | (v,b) <- c ] + metadata v = Metadata.Metadata Metadata.Term (Metadata.Names [v]) Nothing + tb0 = Parsers.termBuiltins + step termBuiltins (v, b) = do + let md = metadata v + h <- createTerm node (Parsers.bindBuiltins (tb0 ++ termBuiltins) Parsers.typeBuiltins b) md + updateMetadata node h md + pure ((v, ref h) : termBuiltins) + foldM_ step termBuiltins bindings' + +-- | Like `declare`, but takes a `String` +declare' :: (Monad m, Var v) => (h -> Term v) -> String -> Node m v h (Type v) (Term v) -> Noted m () +declare' ref bindings node = do + bs <- case Parser.run TermParser.moduleBindings bindings of + Parser.Fail err _ -> Noted (pure $ Left (Note err)) + Parser.Succeed bs _ _ -> pure bs + declare ref bs node diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 481de4c7c..294592d86 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -231,7 +231,7 @@ makeBuiltins whnf = Term.App' (Term.Builtin' tag) a | tag == "Optional.Some" -> whnf (f `Term.app` a) _ -> error "Optional.fold unpossible" op _ = error "Optional.fold unpossible" - in (r, Just (I.Primop 3 op), unsafeParseType "forall a r . r -> (a -> r) -> Optional a -> r", prefix "fold") + in (r, Just (I.Primop 3 op), unsafeParseType "forall a r . r -> (a -> r) -> Optional a -> r", prefix "Optional.fold") -- Vector , let r = R.Builtin "Vector.append" diff --git a/shared/src/Unison/Parser.hs b/shared/src/Unison/Parser.hs index a20829ef3..e122a2b20 100644 --- a/shared/src/Unison/Parser.hs +++ b/shared/src/Unison/Parser.hs @@ -1,3 +1,7 @@ +{-# Language DeriveFunctor #-} +{-# Language DeriveTraversable #-} +{-# Language DeriveFoldable #-} + module Unison.Parser where import Control.Applicative @@ -156,7 +160,7 @@ toEither (Succeed a _ _) = Right a data Result a = Fail [String] Bool | Succeed a Int Bool - deriving (Show) + deriving (Show,Functor,Foldable,Traversable) instance Functor Parser where fmap = liftM diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index ae0315f67..18651865f 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -8,6 +8,7 @@ import Unison.Symbol (Symbol) import Unison.Term (Term) import Unison.Type (Type) import Unison.Parser (Result(..), run, unsafeGetSucceed) +import Unison.Var (Var) import Unison.View (DFO) import qualified Unison.Parser as Parser import qualified Data.Text as Text @@ -28,74 +29,15 @@ parseType :: String -> Result (Type V) parseType = parseType' typeBuiltins parseTerm' :: [(V, Term V)] -> [(V, Type V)] -> String -> Result (Term V) -parseTerm' termBuiltins typeBuiltins s = case run (Parser.root TermParser.term) s of - Succeed e n b -> - Succeed (Term.typeMap (ABT.substs typeBuiltins) (ABT.substs termBuiltins e)) n b - fail -> fail +parseTerm' termBuiltins typeBuiltins s = + bindBuiltins termBuiltins typeBuiltins <$> run (Parser.root TermParser.term) s + +bindBuiltins :: Var v => [(v, Term v)] -> [(v, Type v)] -> Term v -> Term v +bindBuiltins termBuiltins typeBuiltins = + Term.typeMap (ABT.substs typeBuiltins) . ABT.substs termBuiltins parseType' :: [(V, Type V)] -> String -> Result (Type V) -parseType' typeBuiltins s = case run (Parser.root TypeParser.type_) s of - Succeed t n b -> Succeed (ABT.substs typeBuiltins t) n b - fail -> fail - -prelude' :: String -> String -prelude' body = unlines - [ "let" - , " Index.empty : forall k v . Remote (Index k v);" - , " Index.empty = Remote.map Index.unsafeEmpty Remote.here" - , "in", "(", prelude body, ")"] - -prelude :: String -> String -prelude body = unlines - [ "let" - , " Remote.transfer : Node -> Remote Unit;" - , " Remote.transfer node = Remote.at node unit;" - , "" - , " then : forall a b c . (a -> b) -> (b -> c) -> a -> c;" - , " then f1 f2 x = f2 (f1 x);" - , "" - , " Optional.map : forall a b . (a -> b) -> Optional a -> Optional b;" - , " Optional.map f = Optional.fold None (f `then` Some);" - , "" - , " Optional.bind : forall a b . (a -> Optional b) -> Optional a -> Optional b;" - , " Optional.bind f = Optional.fold None f;" - , "" - , " Optional.pure : forall a . a -> Optional a;" - , " Optional.pure = Some;" - , "" - , " Either.map : forall a b c . (b -> c) -> Either a b -> Either a c;" - , " Either.map f = Either.fold Left (f `then` Right);" - , "" - , " Either.pure : forall a b . b -> Either a b;" - , " Either.pure = Right;" - , "" - , " Either.bind : forall a b c . (b -> Either a c) -> Either a b -> Either a c;" - , " Either.bind = Either.fold Left;" - , "" - , " Either.swap : forall a b . Either a b -> Either b a;" - , " Either.swap e = Either.fold Right Left e;" - , "" - , " const x y = x;" - , "" - , " first : forall a b . Pair a b -> a;" - , " first p = Pair.fold const p;" - , "" - , " rest : forall a b . Pair a b -> b;" - , " rest p = Pair.fold (x y -> y) p;" - , "" - , " 1st = first;" - , " 2nd = rest `then` first;" - , " 3rd = rest `then` (rest `then` first);" - , " 4th = rest `then` (rest `then` (rest `then` first));" - , " 5th = rest `then` (rest `then` (rest `then` (rest `then` first)))" - , "" - , "in" , "(", body, ")"] - -unsafeParseTermWithPrelude' :: String -> Term V -unsafeParseTermWithPrelude' prog = unsafeParseTerm (prelude' prog) - -unsafeParseTermWithPrelude :: String -> Term V -unsafeParseTermWithPrelude prog = unsafeParseTerm (prelude prog) +parseType' typeBuiltins s = ABT.substs typeBuiltins <$> run (Parser.root TypeParser.type_) s unsafeParseTerm :: String -> Term V unsafeParseTerm = unsafeGetSucceed . parseTerm @@ -120,7 +62,7 @@ data Builtin = Builtin Text -- e.g. Builtin "()" | AliasFromModule Text [Text] [Text] -- aka default imports -termBuiltins :: [(V, Term V)] +termBuiltins :: Var v => [(v, Term v)] termBuiltins = (Var.named *** Term.ref) <$> ( [ Alias "+" "Number.plus" , Alias "-" "Number.minus" @@ -169,7 +111,7 @@ termBuiltins = (Var.named *** Term.ref) <$> ( aliasFromModule m sym = alias sym (Text.intercalate "." [m, sym]) builtinInModule m sym = builtin (Text.intercalate "." [m, sym]) -typeBuiltins :: [(V, Type V)] +typeBuiltins :: Var v => [(v, Type v)] typeBuiltins = (Var.named *** Type.lit) <$> [ ("Number", Type.Number) , builtin "Unit" diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index def3c0548..e147332ab 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -31,13 +31,13 @@ operator characters (like empty? or fold-left). Sections / partial application of infix operators is not implemented. -} -term :: (Var v, Show v) => Parser (Term v) +term :: Var v => Parser (Term v) term = possiblyAnnotated term2 -term2 :: (Var v, Show v) => Parser (Term v) +term2 :: Var v => Parser (Term v) term2 = let_ term3 <|> term3 -term3 ::(Var v, Show v) => Parser (Term v) +term3 :: Var v => Parser (Term v) term3 = infixApp term4 <|> term4 infixApp :: Var v => Parser (Term v) -> Parser (Term v) @@ -49,16 +49,16 @@ infixApp p = f <$> arg <*> some ((,) <$> infixVar <*> arg) g :: Ord v => Term v -> (v, Term v) -> Term v g lhs (op, rhs) = Term.apps (Term.var op) [lhs,rhs] -term4 :: (Var v, Show v) => Parser (Term v) +term4 :: Var v => Parser (Term v) term4 = prefixApp term5 -term5 :: (Var v, Show v) => Parser (Term v) +term5 :: Var v => Parser (Term v) term5 = lam term <|> effectBlock <|> termLeaf -termLeaf :: (Var v, Show v) => Parser (Term v) +termLeaf :: Var v => Parser (Term v) termLeaf = asum [hashLit, prefixTerm, lit, tupleOrParenthesized term, blank, vector term] -tupleOrParenthesized :: (Var v, Show v) => Parser (Term v) -> Parser (Term v) +tupleOrParenthesized :: Var v => Parser (Term v) -> Parser (Term v) tupleOrParenthesized rec = parenthesized $ go <$> sepBy1 (token $ string ",") rec where go [t] = t -- was just a parenthesized term @@ -70,7 +70,7 @@ tupleOrParenthesized rec = -- Remote { x := pure 23; y := at node2 23; pure 19 } -- Remote { action1; action2; } -- Remote { action1; x = 1 + 1; action2; } -effectBlock :: (Var v, Show v) => Parser (Term v) +effectBlock :: Var v => Parser (Term v) effectBlock = do name <- wordyId <* token (string "{") let qualifiedPure = ABT.var' (Text.pack name `mappend` Text.pack ".pure") @@ -147,29 +147,29 @@ ann'' :: Var v => Parser (Type v) ann'' = token (char ':') *> TypeParser.type_ --let server = _; blah = _ in _ -let_ :: (Var v, Show v) => Parser (Term v) -> Parser (Term v) +let_ :: Var v => Parser (Term v) -> Parser (Term v) let_ p = f <$> (let_ *> optional rec_) <*> bindings' <* in_ <*> body where let_ = token (string "let") rec_ = token (string "rec") $> () bindings' = lineErrorUnless "error parsing let bindings" (bindings p) - in_ = lineErrorUnless "missing 'in' after bindings in let-expression'" $ token (string "in") + in_ = lineErrorUnless "missing 'in' after bindings in let-expression'" $ + (optional (token (string ";")) *> token (string "in")) body = lineErrorUnless "parse error in body of let-expression" p -- f = maybe Term.let1' f :: Ord v => Maybe () -> [(v, Term v)] -> Term v -> Term v f Nothing bindings body = Term.let1 bindings body f (Just _) bindings body = Term.letRec bindings body - semicolon :: Parser () semicolon = void $ token (char ';') -infixBinding :: (Var v, Show v) => Parser (Term v) -> Parser (v, Term v) +infixBinding :: Var v => Parser (Term v) -> Parser (v, Term v) infixBinding p = ((,,,,) <$> optional (typedecl <* semicolon) <*> prefixVar <*> infixVar <*> prefixVar <*> bindingEqBody p) >>= f where - f :: (Ord v, Show v) => (Maybe (v, Type v), v, v, v, Term v) -> Parser (v, Term v) + f :: Var v => (Maybe (v, Type v), v, v, v, Term v) -> Parser (v, Term v) f (Just (opName', _), _, opName, _, _) | opName /= opName' = - failWith ("The type signature for ‘" ++ show opName' ++ "’ lacks an accompanying binding") + failWith ("The type signature for ‘" ++ show (Var.name opName') ++ "’ lacks an accompanying binding") f (Nothing, arg1, opName, arg2, body) = pure (mkBinding opName [arg1,arg2] body) f (Just (_, type'), arg1, opName, arg2, body) = pure $ (`Term.ann` type') <$> mkBinding opName [arg1,arg2] body @@ -180,12 +180,11 @@ mkBinding f args body = (f, Term.lam'' args body) typedecl :: Var v => Parser (v, Type v) typedecl = (,) <$> prefixVar <*> ann'' -prefixBinding :: (Var v, Show v) => Parser (Term v) -> Parser (v, Term v) +prefixBinding :: Var v => Parser (Term v) -> Parser (v, Term v) prefixBinding p = ((,,,) <$> optional (typedecl <* semicolon) <*> prefixVar <*> many prefixVar <*> bindingEqBody p) >>= f -- todo where - f :: (Ord v, Show v) => (Maybe (v, Type v), v, [v], Term v) -> Parser (v, Term v) f (Just (opName, _), opName', _, _) | opName /= opName' = - failWith ("The type signature for ‘" ++ show opName' ++ "’ lacks an accompanying binding") + failWith ("The type signature for ‘" ++ show (Var.name opName') ++ "’ lacks an accompanying binding") f (Nothing, name, args, body) = pure $ mkBinding name args body f (Just (_, t), name, args, body) = pure $ (`Term.ann` t) <$> mkBinding name args body @@ -240,6 +239,9 @@ prefixApp p = f <$> some p f (func:args) = Term.apps func args f [] = error "'some' shouldn't produce an empty list" -bindings :: (Var v, Show v) => Parser (Term v) -> Parser [(v, Term v)] -bindings p = --many (binding term) - sepBy1 (token (char ';' <|> char '\n')) (prefixBinding p <|> infixBinding p) +bindings :: Var v => Parser (Term v) -> Parser [(v, Term v)] +bindings p = + sepBy1 (token (char ';')) (prefixBinding p <|> infixBinding p) + +moduleBindings :: Var v => Parser [(v, Term v)] +moduleBindings = root (bindings term3 <* optional (token (char ';'))) diff --git a/shared/src/Unison/TypeParser.hs b/shared/src/Unison/TypeParser.hs index 503728711..4a922945b 100644 --- a/shared/src/Unison/TypeParser.hs +++ b/shared/src/Unison/TypeParser.hs @@ -2,19 +2,18 @@ module Unison.TypeParser where + import Control.Applicative ((<|>), some) import Data.Char (isUpper, isLower, isAlpha) -import Data.List (foldl1') import Data.Foldable (asum) -import qualified Data.Text as Text - +import Data.Functor +import Data.List (foldl1') import Unison.Parser import Unison.Type (Type) import Unison.Var (Var) +import qualified Data.Text as Text import qualified Unison.Type as Type --- type V = Symbol DFO - type_ :: Var v => Parser (Type v) type_ = forall type1 <|> type1 @@ -49,7 +48,7 @@ arrow rec = foldr1 Type.arrow <$> sepBy1 (token $ string "->") rec -- "forall a b . List a -> List b -> Maybe Text" forall :: Var v => Parser (Type v) -> Parser (Type v) forall rec = do - _ <- token $ string "forall" + (void . token $ string "forall") <|> void (token (char '∀')) vars <- some $ token varName _ <- token (char '.') t <- rec diff --git a/shared/tests/Unison/Test/Common.hs b/shared/tests/Unison/Test/Common.hs index f96a92585..b82add01d 100644 --- a/shared/tests/Unison/Test/Common.hs +++ b/shared/tests/Unison/Test/Common.hs @@ -2,6 +2,8 @@ module Unison.Test.Common where import Control.Monad.IO.Class +import Data.Foldable +import System.IO (FilePath) import Unison.Symbol (Symbol) import Unison.Node (Node) import Unison.Reference (Reference) @@ -9,6 +11,8 @@ import Unison.Term (Term) import Unison.Type (Type) import Unison.Views (defaultSymbol) import qualified Data.Map as Map +import qualified Data.Text.IO as Text.IO +import qualified Data.Text as Text import qualified Unison.Metadata as Metadata import qualified Unison.Node as Node import qualified Unison.Node.MemNode as MemNode @@ -18,13 +22,27 @@ import qualified Unison.View as View type V = Symbol View.DFO -- A Node for testing -type TNode = (Node IO V Reference (Type V) (Term V), Reference -> V) +type TNode = (Node IO V Reference (Type V) (Term V), Reference -> V, [(V, Term V)]) + +loadDeclarations :: FilePath -> Node IO V Reference (Type V) (Term V) -> IO () +loadDeclarations path node = do + txt <- Text.IO.readFile path + let str = Text.unpack txt + Note.run $ Node.declare' Term.ref str node node :: IO TNode node = do node <- MemNode.make + loadDeclarations "unison-src/base.u" node symbols <- liftIO . Note.run $ Map.fromList . Node.references <$> Node.search node Term.blank [] 1000 (Metadata.Query "") Nothing + base <- Note.run $ do + -- grab all definitions in the node + results <- Node.search node Term.blank [] 1000000 (Metadata.Query "") Nothing + let x = [ (v, Term.ref h) | (h, md) <- Node.references results + , v <- toList $ Metadata.firstName (Metadata.names md) ] + Note.lift $ putStrLn (show x) + pure x let firstName (Metadata.Names (n:_)) = n let lookupSymbol ref = maybe (defaultSymbol ref) (firstName . Metadata.names) (Map.lookup ref symbols) - pure (node, lookupSymbol) + pure (node, lookupSymbol, base) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index ffd3e5488..1e42628e7 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -38,8 +38,9 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "2nd (1,2 + 1,3,4)" "3" ] t uneval eval = testCase (uneval ++ " ⟹ " ++ eval) $ do - (node, _) <- node - let term = P.unsafeParseTermWithPrelude uneval + (node, _, builtins) <- node + -- putStrLn (show $ map fst builtins) + let term = P.bindBuiltins builtins [] $ P.unsafeParseTerm uneval _ <- Note.run $ Node.typeAt node term [] [(_,_,result)] <- Note.run $ Node.evaluateTerms node [([], term)] assertEqual "comparing results" (P.unsafeParseTerm eval) result diff --git a/shared/tests/Unison/Test/Term.hs b/shared/tests/Unison/Test/Term.hs index f4fffb537..92181caf6 100644 --- a/shared/tests/Unison/Test/Term.hs +++ b/shared/tests/Unison/Test/Term.hs @@ -32,7 +32,7 @@ hash :: TTerm -> Hash hash = ABT.hash atPts :: Bool -> Common.TNode -> [(Int,Int)] -> TTerm -> [(Paths.Path, Region)] -atPts print (_,symbol) pts t = map go pts where +atPts print (_,symbol,_) pts t = map go pts where go (x,y) = let p = path x y in (p, Doc.region bounds p) doc = Views.term symbol t layout = Doc.layout Doc.textWidth (Width 80) doc @@ -48,20 +48,20 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Term" , testCase "hash cycles" $ assertEqual "pingpong" (hash pingpong1) (hash pingpong2) - , testCase "infix-rendering (1)" $ node >>= \(_,symbol) -> + , testCase "infix-rendering (1)" $ node >>= \(_,symbol,_) -> let t = unsafeParseTerm "Number.plus 1 1" in assertEqual "+" "1 + 1" (Doc.formatText (Width 80) (Views.term symbol t)) - , testCase "infix-rendering (unsaturated)" $ node >>= \(_,symbol) -> + , testCase "infix-rendering (unsaturated)" $ node >>= \(_,symbol,_) -> let t = unsafeParseTerm "Number.plus _" in assertEqual "+" "(+) _" (Doc.formatText (Width 80) (Views.term symbol t)) - , testCase "infix-rendering (totally unsaturated)" $ node >>= \(_,symbol) -> + , testCase "infix-rendering (totally unsaturated)" $ node >>= \(_,symbol,_) -> let t = unsafeParseTerm "Number.plus" in assertEqual "+" "(+)" (Doc.formatText (Width 80) (Views.term symbol t)) - , testCase "infix-rendering (2)" $ node >>= \(_,symbol) -> + , testCase "infix-rendering (2)" $ node >>= \(_,symbol,_) -> do t <- pure $ unsafeParseTerm "Number.plus 1 1" let d = Views.term symbol t diff --git a/shared/tests/Unison/Test/Typechecker.hs b/shared/tests/Unison/Test/Typechecker.hs index 3b976a528..8b160dcd2 100644 --- a/shared/tests/Unison/Test/Typechecker.hs +++ b/shared/tests/Unison/Test/Typechecker.hs @@ -35,7 +35,7 @@ instance Show StrongEq where show (StrongEq t) = show t env :: TNode -> TEnv IO env node r = do - (node, _) <- Note.lift node + (node, _, _) <- Note.lift node Node.typeAt node (E.ref r) mempty localsAt :: TNode -> Path -> TTerm -> IO [(V, Type V)] @@ -45,7 +45,7 @@ localsAt node path e = Note.run $ do synthesizesAt :: TNode -> Path -> TTerm -> TType -> Assertion synthesizesAt node path e t = Note.run $ do - (node, _) <- Note.lift node + (node, _, _) <- Note.lift node t2 <- Node.typeAt node e path _ <- Note.fromEither (Typechecker.subtype t2 t) _ <- Note.fromEither (Typechecker.subtype t t2) diff --git a/shared/tests/Unison/Test/Typechecker/Components.hs b/shared/tests/Unison/Test/Typechecker/Components.hs index 7f6c879e3..f780aedff 100644 --- a/shared/tests/Unison/Test/Typechecker/Components.hs +++ b/shared/tests/Unison/Test/Typechecker/Components.hs @@ -32,7 +32,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> "let id x = x; g = id 42; y = id id g in (let rec ping x = pong x; pong x = id (ping x) in y)" ] t before after = testCase (before ++ " ⟹ " ++ after) $ do - (node, _) <- node + (node, _, _) <- node let term = unsafeParseTerm before case term of Term.LetRecNamed' bs _ -> diff --git a/unison-src/base.u b/unison-src/base.u new file mode 100644 index 000000000..ca2052b8f --- /dev/null +++ b/unison-src/base.u @@ -0,0 +1,40 @@ +Remote.transfer : Node -> Remote Unit; +Remote.transfer node = Remote.at node unit; + +then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; +then f1 f2 x = f2 (f1 x); + +Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b; +Optional.map f = Optional.fold None (f `then` Some); + +Optional.bind : ∀ a b . (a -> Optional b) -> Optional a -> Optional b; +Optional.bind f = Optional.fold None f; + +Optional.pure : ∀ a . a -> Optional a; +Optional.pure = Some; + +Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; +Either.map f = Either.fold Left (f `then` Right); + +Either.pure : ∀ a b . b -> Either a b; +Either.pure = Right; + +Either.bind : ∀ a b c . (b -> Either a c) -> Either a b -> Either a c; +Either.bind = Either.fold Left; + +Either.swap : ∀ a b . Either a b -> Either b a; +Either.swap e = Either.fold Right Left e; + +const x y = x; + +first : ∀ a b . Pair a b -> a; +first p = Pair.fold const p; + +rest : ∀ a b . Pair a b -> b; +rest p = Pair.fold (x y -> y) p; + +1st = first; +2nd = rest `then` first; +3rd = rest `then` (rest `then` first); +4th = rest `then` (rest `then` (rest `then` first)); +5th = rest `then` (rest `then` (rest `then` (rest `then` first))); diff --git a/unison-src/extra.u b/unison-src/extra.u new file mode 100644 index 000000000..ec3482abf --- /dev/null +++ b/unison-src/extra.u @@ -0,0 +1,3 @@ + +Index.empty : ∀ k v . Remote (Index k v); +Index.empty = Remote.map Index.unsafeEmpty Remote.here From 00e64724043e8f288064c26dce34026b62f7875d Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 18 Aug 2016 21:28:50 -0400 Subject: [PATCH 04/61] prevent duplicate definitions in Node, which can cause cyclic references --- shared/src/Unison/Node.hs | 7 +++++-- shared/src/Unison/Note.hs | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/shared/src/Unison/Node.hs b/shared/src/Unison/Node.hs index 7fa819cad..26bf82043 100644 --- a/shared/src/Unison/Node.hs +++ b/shared/src/Unison/Node.hs @@ -5,6 +5,7 @@ module Unison.Node where -- import Data.Bytes.Serial (Serial) import Control.Monad +import Control.Applicative import Data.Foldable import Data.Aeson.TH import Data.List @@ -125,9 +126,11 @@ node eval hash store = Reference.Builtin _ -> Store.writeMetadata store r md -- can't change builtin types, just metadata Reference.Derived h -> do - Store.writeTerm store h e + new <- (False <$ Store.readTerm store h) <|> pure True Store.writeMetadata store r md - Store.annotateTerm store r t + when new $ do + Store.writeTerm store h e + Store.annotateTerm store r t createType _ _ = error "todo - createType" diff --git a/shared/src/Unison/Note.hs b/shared/src/Unison/Note.hs index a830f3815..ee876d9fd 100644 --- a/shared/src/Unison/Note.hs +++ b/shared/src/Unison/Note.hs @@ -63,6 +63,18 @@ instance Applicative m => Applicative (Noted m) where pure = Noted . pure . pure (Noted f) <*> (Noted a) = Noted $ liftA2 (<*>) f a +instance Monad m => MonadPlus (Noted m) where + mzero = Noted (pure (Left (Note []))) + mplus (Noted n1) (Noted n2) = Noted $ do + n1 <- n1 + case n1 of + Left _ -> n2 + Right a -> pure (Right a) + +instance Monad m => Alternative (Noted m) where + empty = mzero + (<|>) = mplus + note :: String -> Note note s = Note [s] From d00c3f1cfad7ca9ed8e042f1e82083dc0c5f7a19 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Fri, 19 Aug 2016 17:46:10 -0400 Subject: [PATCH 05/61] hacking to get node workers loading base.u and extra.u from file --- .gitignore | 2 + node/src/Container.hs | 4 +- node/src/Unison/Node/UnisonBlockStore.hs | 6 +- node/src/Unison/NodeContainer.hs | 16 +++-- node/src/Unison/NodeProtocol.hs | 22 +++--- node/src/Unison/NodeWorker.hs | 10 +-- node/src/Unison/Runtime/Multiplex.hs | 80 ++++++++++++---------- node/src/Unison/Runtime/Remote.hs | 10 +-- node/src/Worker.hs | 85 +++++++++++++++++------- node/unison-node.cabal | 3 + shared/src/Unison/Node.hs | 18 +++-- shared/src/Unison/Node/BasicNode.hs | 21 ------ shared/src/Unison/Node/Builtin.hs | 14 ++-- shared/src/Unison/Parsers.hs | 4 +- shared/tests/Unison/Test/Common.hs | 2 + shared/tests/Unison/Test/Interpreter.hs | 1 + stack.yaml | 5 ++ unison-src/base.u | 3 + 18 files changed, 184 insertions(+), 122 deletions(-) diff --git a/.gitignore b/.gitignore index 0c9962adb..202f769cf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ cabal-dev **/cache/** **/build/** store +codestore tags +unison-src/.loaded **cabal.sandbox.config .cabal-sandbox/** diff --git a/node/src/Container.hs b/node/src/Container.hs index 788fa2050..d581832d5 100644 --- a/node/src/Container.hs +++ b/node/src/Container.hs @@ -13,7 +13,7 @@ import System.IO (hSetBinaryMode, hFlush, stdin) import System.Process as P import Unison.NodeProtocol.V0 (protocol) import Unison.NodeServer as NS -import Unison.Parsers (unsafeParseTermWithPrelude) +import Unison.Parsers (unsafeParseTerm) import Unison.Runtime.Lock (Lock(..),Lease(..)) import Web.Scotty as S import qualified Data.ByteArray as BA @@ -65,7 +65,7 @@ main = Mux.uniqueChannel >>= \rand -> let node = R.Node "localhost" (Put.runPutS . serialize . Base64.decodeLenient $ nodepk) programtxt <- S.body let programstr = Text.unpack (decodeUtf8 (LB.toStrict programtxt)) - let !prog = unsafeParseTermWithPrelude programstr + let !prog = unsafeParseTerm programstr let !prog' = Components.minimize' prog liftIO . putStrLn $ "parsed " ++ show prog liftIO . putStrLn $ "parsed' " ++ show prog' diff --git a/node/src/Unison/Node/UnisonBlockStore.hs b/node/src/Unison/Node/UnisonBlockStore.hs index 42f303a26..7ea34fd3b 100644 --- a/node/src/Unison/Node/UnisonBlockStore.hs +++ b/node/src/Unison/Node/UnisonBlockStore.hs @@ -57,11 +57,11 @@ make bs = let StoreData trm tym (Map.insert ref met mm) in do journaledStore <- J.fromBlocks bs apply keyframeBlock updateBlock - let readTerm h = Note.noted . atomically $ (maybeToEither (Note.note "term not found") . Map.lookup h . termMap) + let readTerm h = Note.noted . atomically $ (maybeToEither (Note.note $ "term not found " ++ show h) . Map.lookup h . termMap) <$> J.get journaledStore - typeOfTerm r = Note.noted . atomically $ (maybeToEither (Note.note "type not found") . Map.lookup r . annotationMap) + typeOfTerm r = Note.noted . atomically $ (maybeToEither (Note.note $ "type not found " ++ show r) . Map.lookup r . annotationMap) <$> J.get journaledStore - readMetadata r = Note.noted . atomically $ (maybeToEither (Note.note "metadata not found") . Map.lookup r . metadataMap) + readMetadata r = Note.noted . atomically $ (maybeToEither (Note.note $ "metadata not found " ++ show r) . Map.lookup r . metadataMap) <$> J.get journaledStore writeTerm h t = Note.lift $ J.update (WriteTerm h t) journaledStore annotateTerm r t = Note.lift $ J.update (AnnotateTerm r t) journaledStore diff --git a/node/src/Unison/NodeContainer.hs b/node/src/Unison/NodeContainer.hs index 251932f57..169abb5ab 100644 --- a/node/src/Unison/NodeContainer.hs +++ b/node/src/Unison/NodeContainer.hs @@ -99,13 +99,19 @@ make bs nodeLock p genNode launchNodeCmd = do writer <- Async.async . forever $ do (bytes, force) <- tryReadChan toNodeRead bytes <- tryRead bytes >>= \bytes -> case bytes of - Nothing -> hFlush stdin >> force -- flush buffer whenever there's a pause + Nothing -> do + L.trace logger $ "flushing bytes sent to stdin of node worker" + hFlush stdin >> force -- flush buffer whenever there's a pause Just bytes -> pure bytes -- we're saturating the channel, no need to flush manually let nodeBytes = Put.runPutS (S.serialize node) - L.trace logger $ "writing bytes " ++ show (B.length bytes) + let numbytes = B.length bytes + L.trace logger $ "sending " ++ show numbytes ++ " bytes to node " ++ show node safely $ - B.hPut stdin bytes `onException` - writeChan packetWrite (Mux.Packet nodeBytes bytes) + do + B.hPut stdin bytes + L.trace logger $ "done sending " ++ show numbytes ++ " bytes to node " ++ show node + `onException` + writeChan packetWrite (Mux.Packet nodeBytes bytes) -- establish routes for processing packets coming from the node routes <- id $ @@ -125,7 +131,9 @@ make bs nodeLock p genNode launchNodeCmd = do handleRequest :: (S.Serial a, S.Serial b) => (a -> IO b) -> ByteString -> IO () handleRequest h bytes = safely $ do (a, replyTo) <- either fail pure (Get.runGetS S.deserialize bytes) + L.debug logger $ "got request " ++ show (Base64.encode replyTo) b <- h a + L.debug logger $ "got response " ++ show (Base64.encode replyTo) send $ Put.runPutS (S.serialize (Mux.Packet replyTo $ Put.runPutS (S.serialize b))) insert = handleRequest (BS.insert bs) lookup = handleRequest (BS.lookup bs) diff --git a/node/src/Unison/NodeProtocol.hs b/node/src/Unison/NodeProtocol.hs index 7fb1bb32f..7aad1e2d6 100644 --- a/node/src/Unison/NodeProtocol.hs +++ b/node/src/Unison/NodeProtocol.hs @@ -53,17 +53,17 @@ data Protocol term signature hash thash = blockStoreProxy :: (Serial hash) => Protocol term signature hash thash -> Mux.Multiplex (BlockStore hash) blockStoreProxy p = go <$> Mux.ask where - timeout = 5000000 :: Mux.Microseconds + timeout = Mux.seconds 25 go env = let - mt :: (Serial a, Serial b) => Request a b -> a -> IO b - mt chan a = Mux.run env . join $ Mux.requestTimed timeout chan a - insert bytes = mt (_insert p) bytes - lookup h = mt (_lookup p) h - declare series = mt (_declare p) series - delete series = mt (_delete p) series - update series h bytes = mt (_update p) (series,h,bytes) - append series h bytes = mt (_append p) (series,h,bytes) - resolve series = mt (_resolve p) series - resolves series = mt (_resolves p) series + mt :: (Serial a, Serial b) => String -> Request a b -> a -> IO b + mt msg chan a = Mux.run env . join $ Mux.requestTimed msg timeout chan a + insert bytes = mt "BlockStore.insert" (_insert p) bytes + lookup h = mt "BlockStore.lookup" (_lookup p) h + declare series = mt "BlockStore.declare" (_declare p) series + delete series = mt "BlockStore.delete" (_delete p) series + update series h bytes = mt "BlockStore.update" (_update p) (series,h,bytes) + append series h bytes = mt "BlockStore.append" (_append p) (series,h,bytes) + resolve series = mt "BlockStore.resolve" (_resolve p) series + resolves series = mt "BlockStore.resolves" (_resolves p) series in BlockStore insert lookup declare delete update append resolve resolves diff --git a/node/src/Unison/NodeWorker.hs b/node/src/Unison/NodeWorker.hs index 85bedc549..eb88be815 100644 --- a/node/src/Unison/NodeWorker.hs +++ b/node/src/Unison/NodeWorker.hs @@ -43,7 +43,7 @@ make :: ( BA.ByteArrayAccess key -> (Keypair key -> Cryptography key symmetricKey signKey skp signature hash Remote.Cleartext) -> Get (Cryptography key symmetricKey signKey skp signature hash Remote.Cleartext -> BlockStore h - -> IO (Remote.Language term thash, term -> IO (Either String ()))) + -> IO (Remote.Language term thash, term -> IO (Either String term), IO ())) -> IO () make protocol mkCrypto makeSandbox = do logger <- L.scope "worker" <$> Config.loggerStandardError @@ -56,21 +56,23 @@ make protocol mkCrypto makeSandbox = do (sandbox, _, rem) <- Mux.deserializeHandle1 stdin (Get.runGetPartial deserialize rem) publicKey <- either die pure $ Get.runGetS deserialize (Remote.publicKey node) let keypair = Keypair publicKey privateKey + L.debug logger $ "parsed private key, node id, universe, sandbox description" L.debug logger $ "remaining bytes: " ++ show (B.length rem) interrupt <- atomically $ newTSem 0 Mux.runStandardIO logger (Mux.seconds 5) rem (atomically $ waitTSem interrupt) $ do blockStore <- P.blockStoreProxy protocol makeSandbox <- either die pure $ Get.runGetS makeSandbox sandbox let crypto = mkCrypto keypair - (sandbox, typecheck) <- liftIO $ makeSandbox crypto blockStore + (sandbox, typecheck, initialize) <- liftIO $ makeSandbox crypto blockStore let skHash = Put.runPutS (serialize $ C.hash crypto [Put.runPutS (serialize $ private keypair)]) -- todo: load this from persistent store also connectionSandbox <- pure $ Remote.ConnectionSandbox (\_ -> pure True) (\_ -> pure True) env <- liftIO $ Remote.makeEnv universe node blockStore - Mux.info $ "... done initializing" _ <- Remote.server crypto connectionSandbox env sandbox protocol _ <- do (prog, cancel) <- Mux.subscribeTimed (Mux.seconds 60) (P._localEval protocol) + liftIO $ initialize + Mux.info $ "... done initializing" Mux.fork . Mux.scope "_localEval" . Mux.repeatWhile $ do e <- prog case e of @@ -83,7 +85,7 @@ make protocol mkCrypto makeSandbox = do Mux.warn $ "typechecking failed on: " ++ show r Mux.warn $ "typechecking error:\n" ++ err pure True - Right _ -> do + Right r -> do Mux.debug "typechecked" r <- liftIO $ Remote.eval sandbox r Mux.debug $ "evaluated to " ++ show r diff --git a/node/src/Unison/Runtime/Multiplex.hs b/node/src/Unison/Runtime/Multiplex.hs index 03408174e..aa1ff3f42 100644 --- a/node/src/Unison/Runtime/Multiplex.hs +++ b/node/src/Unison/Runtime/Multiplex.hs @@ -174,6 +174,12 @@ scope :: String -> Multiplex a -> Multiplex a scope msg = local tweak where tweak (a,b,c,logger) = (a,b,c,L.scope msg logger) +-- | Crash with a message. Include the current logging scope. +crash :: String -> Multiplex a +crash msg = scope msg $ do + l <- logger + fail (show $ L.getScope l) + info, warn, debug :: String -> Multiplex () info msg = logger >>= \logger -> liftIO $ L.info logger msg warn msg = logger >>= \logger -> liftIO $ L.warn logger msg @@ -253,40 +259,41 @@ type Request a b = Channel (a, Channel b) type Microseconds = Int requestTimedVia' :: (Serial a, Serial b) - => Microseconds + => String + -> Microseconds -> (STM (a, Channel b) -> Multiplex ()) -> Channel b -> STM a -> Multiplex (Multiplex b) -requestTimedVia' micros send replyTo a = do +requestTimedVia' msg micros send replyTo a = do env <- ask (receive, cancel) <- receiveCancellable replyTo send $ (,replyTo) <$> a watchdog <- liftIO . C.forkIO $ do liftIO $ C.threadDelay micros - run env cancel + run env (cancel $ "requestTimedVia timeout " ++ msg) pure $ receive <* liftIO (C.killThread watchdog) -requestTimedVia :: (Serial a, Serial b) => Microseconds -> Request a b -> Channel b -> STM a +requestTimedVia :: (Serial a, Serial b) => String -> Microseconds -> Request a b -> Channel b -> STM a -> Multiplex (Multiplex b) -requestTimedVia micros req replyTo a = - requestTimedVia' micros (send' req) replyTo a +requestTimedVia msg micros req replyTo a = + requestTimedVia' msg micros (send' req) replyTo a -requestTimed' :: (Serial a, Serial b) => Microseconds -> Request a b -> STM a -> Multiplex (Multiplex b) -requestTimed' micros req a = do +requestTimed' :: (Serial a, Serial b) => String -> Microseconds -> Request a b -> STM a -> Multiplex (Multiplex b) +requestTimed' msg micros req a = do replyTo <- channel - requestTimedVia micros req replyTo a + requestTimedVia msg micros req replyTo a -requestTimed :: (Serial a, Serial b) => Microseconds -> Request a b -> a -> Multiplex (Multiplex b) -requestTimed micros req a = do +requestTimed :: (Serial a, Serial b) => String -> Microseconds -> Request a b -> a -> Multiplex (Multiplex b) +requestTimed msg micros req a = do replyTo <- channel env <- ask (receive, cancel) <- receiveCancellable replyTo send req (a, replyTo) watchdog <- liftIO . C.forkIO $ do liftIO $ C.threadDelay micros - run env cancel - pure $ receive <* liftIO (C.killThread watchdog) <* cancel + run env (cancel $ "requestTimed timeout " ++ msg) + pure $ receive <* liftIO (C.killThread watchdog) <* cancel ("requestTimed completed") type Cleartext = B.ByteString type Ciphertext = B.ByteString @@ -294,18 +301,19 @@ type CipherState = (Cleartext -> STM Ciphertext, Ciphertext -> STM Cleartext) encryptedRequestTimedVia :: (Serial a, Serial b) - => CipherState + => String + -> CipherState -> Microseconds -> ((a,Channel b) -> Multiplex ()) -> Channel b -> a -> Multiplex b -encryptedRequestTimedVia (_,decrypt) micros send replyTo@(Channel _ bs) a = do - responseCiphertext <- receiveTimed micros (Channel Type bs) +encryptedRequestTimedVia msg (_,decrypt) micros send replyTo@(Channel _ bs) a = do + responseCiphertext <- receiveTimed msg micros (Channel Type bs) send (a, replyTo) responseCiphertext <- responseCiphertext -- force the receive responseCleartext <- liftIO . atomically . decrypt $ responseCiphertext - either fail pure $ Get.runGetS deserialize responseCleartext + either crash pure $ Get.runGetS deserialize responseCleartext encryptAndSendTo :: (Serial a, Serial node) @@ -346,29 +354,29 @@ send' (Channel _ key) a = do ~(send,_,_,_) <- ask liftIO . atomically $ send (Packet key . Put.runPutS . serialize <$> a) -receiveCancellable :: Serial a => Channel a -> Multiplex (Multiplex a, Multiplex ()) +receiveCancellable :: Serial a => Channel a -> Multiplex (Multiplex a, String -> Multiplex ()) receiveCancellable (Channel _ key) = do (_,Callbacks cbs cba,_,_) <- ask result <- liftIO newEmptyMVar liftIO . atomically $ M.insert (putMVar result . Right) key cbs liftIO $ bumpActivity' cba - cancel <- pure $ do + cancel <- pure $ \reason -> do liftIO . atomically $ M.delete key cbs - liftIO $ putMVar result (Left "cancelled") - force <- pure . liftIO $ do - bytes <- takeMVar result - bytes <- either fail pure bytes - either fail pure $ Get.runGetS deserialize bytes + liftIO $ putMVar result (Left $ "Mux.cancelled: " ++ reason) + force <- pure . scope "receiveCancellable" $ do + bytes <- liftIO $ takeMVar result + bytes <- either crash pure bytes + either crash pure $ Get.runGetS deserialize bytes pure (force, cancel) -receiveTimed :: Serial a => Microseconds -> Channel a -> Multiplex (Multiplex a) -receiveTimed micros chan = do +receiveTimed :: Serial a => String -> Microseconds -> Channel a -> Multiplex (Multiplex a) +receiveTimed msg micros chan = do (force, cancel) <- receiveCancellable chan env <- ask watchdog <- liftIO . C.forkIO $ do liftIO $ C.threadDelay micros - run env cancel - pure $ force <* liftIO (C.killThread watchdog) <* cancel + run env (cancel $ "receiveTimed timeout during " ++ msg) + pure $ scope "receiveTimed" (force <* liftIO (C.killThread watchdog) <* cancel ("receiveTimed completed" ++ msg)) timeout' :: Microseconds -> a -> Multiplex a -> Multiplex a timeout' micros onTimeout m = fromMaybe onTimeout <$> timeout micros m @@ -413,15 +421,15 @@ subscribeTimed micros chan = do loop logger activity result cancel subscribe :: Serial a => Channel a -> Multiplex (Multiplex a, Multiplex ()) -subscribe (Channel _ key) = do +subscribe (Channel _ key) = scope "subscribe" $ do (_, Callbacks cbs cba, _, _) <- ask q <- liftIO . atomically $ newTQueue liftIO . atomically $ M.insert (atomically . writeTQueue q) key cbs liftIO $ bumpActivity' cba unsubscribe <- pure . liftIO . atomically . M.delete key $ cbs - force <- pure . liftIO $ do - bytes <- atomically $ readTQueue q - either fail pure $ Get.runGetS deserialize bytes + force <- pure $ do + bytes <- liftIO . atomically $ readTQueue q + either crash pure $ Get.runGetS deserialize bytes pure (force, unsubscribe) seconds :: Microseconds -> Int @@ -487,7 +495,7 @@ pipeInitiate crypto rootChan (recipient,recipientKey) u = scope "pipeInitiate" $ bytes <- fetchh debug "... handshake round trip completed" case bytes of - Nothing -> cancelh >> cancelc >> fail "cancelled handshake" + Nothing -> cancelh >> cancelc >> crash "cancelled handshake" Just bytes -> liftIO (atomically $ decrypt bytes) >> go -- todo: add access control here, better to bail ASAP (or after 1s delay @@ -505,7 +513,7 @@ pipeRespond crypto allow _ extractSender payload = do (doneHandshake, senderKey, encrypt, decrypt) <- liftIO $ C.pipeResponder crypto debug $ "decrypting initial payload" bytes <- (liftLogged "[Mux.pipeRespond] decrypt" . atomically . decrypt) payload - (u, chans@(handshakeChan,connectedChan)) <- either fail pure $ Get.runGetS deserialize bytes + (u, chans@(handshakeChan,connectedChan)) <- either crash pure $ Get.runGetS deserialize bytes debug $ "handshake channels: " ++ show chans let sender = extractSender u handshakeSub <- subscribeTimed handshakeTimeout handshakeChan @@ -531,7 +539,7 @@ pipeRespond crypto allow _ extractSender payload = do Nothing -> pure () Just senderKey -> allow senderKey >>= \ok -> if ok then pure () - else liftIO (C.threadDelay delayBeforeFailure) >> fail "disallowed key" + else liftIO (C.threadDelay delayBeforeFailure) >> crash "disallowed key" go = do ready <- liftIO $ atomically doneHandshake checkSenderKey @@ -545,5 +553,5 @@ pipeRespond crypto allow _ extractSender payload = do nest sender $ send' chanh (encrypt B.empty) bytes <- fetchh case bytes of - Nothing -> cancelh >> cancelc >> fail "cancelled handshake" + Nothing -> cancelh >> cancelc >> crash "cancelled handshake" Just bytes -> liftIO (atomically $ decrypt bytes) >> go diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index 21d398b9f..a47050592 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -132,7 +132,8 @@ server crypto allow env lang p = do where fetch hs = do syncChan <- Mux.channel - Mux.encryptedRequestTimedVia cipherstate (Mux.seconds 5) (send . Just . Just) syncChan (Set.toList hs) + Mux.encryptedRequestTimedVia "fetching hashes" + cipherstate (Mux.seconds 5) (send . Just . Just) syncChan (Set.toList hs) loop needs | Set.null needs = pure () loop needs = fetch needs >>= \hashes -> case hashes of Nothing -> fail "expected hashes, got timeout" @@ -185,7 +186,7 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of pure $ node lang (currentNode env) runLocal Spawn = do Mux.debug $ "runLocal Spawn" - n <- Mux.requestTimed (Mux.seconds 5) (P._spawn p) B.empty + n <- Mux.requestTimed "runLocal.spawn" (Mux.seconds 5) (P._spawn p) B.empty n <- n Mux.debug $ "runLocal Spawn completed: " ++ show n pure (node lang n) @@ -198,7 +199,8 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of pure (unit lang) runLocal (ReceiveAsync chan@(Channel cid) (Seconds seconds)) = do Mux.debug $ "runLocal ReceiveAsync " ++ show (seconds, cid) - _ <- Mux.receiveTimed (floor $ seconds * 1000 * 1000) ((Mux.Channel Mux.Type cid) :: Mux.Channel (Maybe B.ByteString)) + _ <- Mux.receiveTimed ("receiveAsync on " ++ show chan) + (floor $ seconds * 1000 * 1000) ((Mux.Channel Mux.Type cid) :: Mux.Channel (Maybe B.ByteString)) pure (remote lang (Step (Local (Receive chan)))) runLocal (Receive (Channel cid)) = do Mux.debug $ "runLocal Receive " ++ show cid @@ -233,7 +235,7 @@ client crypto allow env p recipient r = Mux.scope "Remote.client" $ do Mux.info $ "connected" replyChan <- Mux.channel let send' (a,b) = send (Just (a,b)) - _ <- Mux.encryptedRequestTimedVia cipherstate (Mux.seconds 5) send' replyChan r + _ <- Mux.encryptedRequestTimedVia "client ack" cipherstate (Mux.seconds 5) send' replyChan r Mux.debug $ "got ack on " ++ show replyChan -- todo - might want to retry if ack doesn't come back id $ diff --git a/node/src/Worker.hs b/node/src/Worker.hs index 7f08ae53b..981e1d8e4 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -2,50 +2,82 @@ module Main where +import Control.Concurrent.STM.TVar import Control.Monad +import System.Directory (doesFileExist) +import System.IO (stderr) import Unison.Hash (Hash) import Unison.NodeProtocol.V0 (protocol) import Unison.NodeWorker as W import Unison.SerializationAndHashing (TermV) import qualified Data.Map as Map +import qualified Control.Concurrent.STM as STM import qualified Data.Set as Set +import qualified Data.Text as Text +import qualified Data.Text.IO as Text.IO +import qualified Unison.Config as Config import qualified Unison.Cryptography as C -import qualified Unison.Eval as Eval import qualified Unison.Eval.Interpreter as I +import qualified Unison.Node as Node +import qualified Unison.Node.BasicNode as BasicNode import qualified Unison.Node.Builtin as Builtin +import qualified Unison.Node.FileStore as Store import qualified Unison.Note as Note +import qualified Unison.Parsers as Parsers +import qualified Unison.Reference as Reference import qualified Unison.Remote as RT import qualified Unison.Runtime.ExtraBuiltins as ExtraBuiltins import qualified Unison.Runtime.Remote as R +import qualified Unison.SerializationAndHashing as SAH import qualified Unison.Term as Term -import qualified Unison.Typechecker as Typechecker +import qualified Unison.Util.Logger as L main :: IO () -main = W.make protocol crypto (pure lang) where +main = do + logger <- L.scope "worker-main" <$> Config.loggerTo stderr + W.make protocol crypto (pure $ lang logger) where crypto keypair = C.noop (W.public keypair) - lang crypto blockstore = do + lang logger crypto blockstore = do let b0 = Builtin.makeBuiltins b1 <- ExtraBuiltins.makeAPI blockstore crypto - pure $ go b0 b1 + store <- Store.make "codestore" + backend <- BasicNode.make SAH.hash store (\whnf -> b0 whnf ++ b1 whnf) + initialized <- STM.atomically $ newTVar False + pure $ go backend initialized b0 b1 where - go b0 b1 = (lang, typecheck) where - lang :: R.Language TermV Hash - lang = R.Language localDependencies eval apply node unit channel local unRemote remote - codestore = R.makeCodestore blockstore :: R.Codestore TermV Hash - localDependencies _ = Set.empty -- todo, compute this for real - evaluator = I.eval allprimops - whnf = Eval.whnf evaluator gethash - allbuiltins = b0 whnf ++ b1 whnf - allprimops = Map.fromList [ (r, op) | Builtin.Builtin r (Just op) _ _ <- allbuiltins ] - gethash h = Note.lift $ do - [(h',t)] <- R.getHashes codestore (Set.singleton h) - guard $ h == h' - pure t - typeEnv ref = case lookup ref [ (r, t) | Builtin.Builtin r _ t _ <- allbuiltins ] of - Nothing -> fail $ "unknown reference " ++ show ref - Just t -> pure t - eval t = Note.run (whnf t) - typecheck term = Note.attemptRun . void $ Typechecker.synthesize typeEnv term + go backend initialized b0 b1 = + let + lang :: R.Language TermV Hash + lang = R.Language localDependencies eval apply node unit channel local unRemote remote + codestore = R.makeCodestore blockstore :: R.Codestore TermV Hash + localDependencies _ = Set.empty -- todo, compute this for real + whnf e = do -- todo: may want to have this use evaluator + codestore directly + Note.lift . STM.atomically $ readTVar initialized >>= \ok -> + if ok then pure () + else STM.retry + [(_,_,e)] <- Node.evaluateTerms backend [([], e)] + pure e + eval t = Note.run (whnf t) + -- evaluator = I.eval allprimops + -- allbuiltins = b0 whnf ++ b1 whnf + -- allprimops = Map.fromList [ (r, op) | Builtin.Builtin r (Just op) _ _ <- allbuiltins ] + typecheck e = do + bindings <- Note.run $ Node.allTermsByVarName Term.ref backend + let e' = Parsers.bindBuiltins bindings [] e + Note.unnote (Node.typeAt backend e' []) >>= \t -> case t of + Left note -> pure $ Left (show note) + Right _ -> pure (Right e') + initialize = do + L.info logger "checking if base libraries loaded" + alreadyInitialized <- doesDirectoryExist "codestore" + when (not alreadyInitialized) $ do + L.info logger "codestore/ directory not found, loading base libraries..." + loadDeclarations "unison-src/base.u" backend + loadDeclarations "unison-src/extra.u" backend + hs <- Note.run (Node.allTerms backend) + R.saveHashes codestore [ (h,v) | (Reference.Derived h, v) <- hs ] + STM.atomically $ writeTVar initialized True + in (lang, typecheck, initialize) apply = Term.app node = Term.node unit = Term.builtin "()" @@ -54,3 +86,10 @@ main = W.make protocol crypto (pure lang) where unRemote (Term.Distributed' (Term.Remote r)) = Just r unRemote _ = Nothing remote = Term.remote + loadDeclarations path node = do + txt <- Text.IO.readFile path + let str = Text.unpack txt + L.info logger $ "loading " ++ path + r <- Note.run $ Node.declare' Term.ref str node + L.info logger $ "done loading " ++ path + pure r diff --git a/node/unison-node.cabal b/node/unison-node.cabal index 3c6723957..32f7fed15 100644 --- a/node/unison-node.cabal +++ b/node/unison-node.cabal @@ -161,6 +161,7 @@ executable worker ghc-options: -funbox-strict-fields -O2 build-depends: + aeson, async, base, base64-bytestring, @@ -171,6 +172,8 @@ executable worker configurator, cryptonite, curl, + directory, + filepath, free, hashable, list-t, diff --git a/shared/src/Unison/Node.hs b/shared/src/Unison/Node.hs index 26bf82043..38c387b22 100644 --- a/shared/src/Unison/Node.hs +++ b/shared/src/Unison/Node.hs @@ -250,11 +250,7 @@ node eval hash store = -- existing metadata store of the Node. declare :: (Monad m, Var v) => (h -> Term v) -> [(v, Term v)] -> Node m v h (Type v) (Term v) -> Noted m () declare ref bindings node = do - termBuiltins <- do - -- grab all definitions in the node - results <- search node Term.blank [] 1000000 (Metadata.Query "") Nothing - pure [ (v, ref h) | (h, md) <- references results - , v <- toList $ Metadata.firstName (Metadata.names md) ] + termBuiltins <- allTermsByVarName ref node let groups = Components.components bindings -- watch msg a = trace (msg ++ show (map (Var.name . fst) a)) a bindings' = groups >>= \c -> case c of @@ -276,3 +272,15 @@ declare' ref bindings node = do Parser.Fail err _ -> Noted (pure $ Left (Note err)) Parser.Succeed bs _ _ -> pure bs declare ref bs node + +allTermsByVarName :: (Monad m, Var v) => (h -> Term v) -> Node m v h (Type v) (Term v) -> Noted m [(v, Term v)] +allTermsByVarName ref node = do + -- grab all definitions in the node + results <- search node Term.blank [] 1000000 (Metadata.Query "") Nothing + pure [ (v, ref h) | (h, md) <- references results + , v <- toList $ Metadata.firstName (Metadata.names md) ] + +allTerms :: (Monad m, Var v) => Node m v h (Type v) (Term v) -> Noted m [(h, Term v)] +allTerms node = do + hs <- map fst . references <$> search node Term.blank [] 100000 (Metadata.Query "") Nothing + Map.toList <$> terms node hs diff --git a/shared/src/Unison/Node/BasicNode.hs b/shared/src/Unison/Node/BasicNode.hs index 205ed161e..33c95791b 100644 --- a/shared/src/Unison/Node/BasicNode.hs +++ b/shared/src/Unison/Node/BasicNode.hs @@ -43,28 +43,7 @@ make hash store getBuiltins = readTerm h = Store.readTerm store h whnf = Eval.whnf eval readTerm node = Node.node eval hash store - - -- stub :: Metadata V R.Reference -> Type V -> N.Noted IO () - -- stub s t = () <$ Node.createTerm node (Term.blank `Term.ann` t) s - in N.run $ do - _ <- Node.createTerm node (unsafeParseTerm "a -> a") (prefix "identity") mapM_ (\(B.Builtin r _ t md) -> Node.updateMetadata node r md *> Store.annotateTerm store r t) builtins - compose <- Node.createTerm node (unsafeParseTerm "f g x -> f (g x)") (prefix "compose") - -- Node.createTerm node (\f -> bind (compose pure f)) - let composeH = unsafeHashStringFromReference compose - _ <- Node.createTerm node (unsafeParseTerm $ "f -> bind ("++composeH++" pure f)") - (prefix "map") pure node - where - unsafeHashStringFromReference (R.Derived h) = "#" ++ Text.unpack (H.base64 h) - unsafeHashStringFromReference _ = error "tried to extract a Derived hash from a Builtin" - -prefix :: Text -> Metadata V h -prefix s = prefixes [s] - -prefixes :: [Text] -> Metadata V h -prefixes s = Metadata Metadata.Term - (Metadata.Names (map Var.named s)) - Nothing diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 294592d86..495676054 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -153,13 +153,13 @@ makeBuiltins whnf = op [a] = pure $ Term.remote (Remote.Step (Remote.Local (Remote.Pure a))) op _ = fail "unpossible" in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.pure", prefix "pure") - , let r = R.Builtin "Remote.map" - op [f, r] = pure $ Term.builtin "Remote.bind" `Term.app` - (Term.lam' ["x"] $ Term.remote - (Remote.Step . Remote.Local . Remote.Pure $ f `Term.app` Term.var' "x")) - `Term.app` r - op _ = fail "unpossible" - in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.map", prefix "map") + --, let r = R.Builtin "Remote.map" + -- op [f, r] = pure $ Term.builtin "Remote.bind" `Term.app` + -- (Term.lam' ["x"] $ Term.remote + -- (Remote.Step . Remote.Local . Remote.Pure $ f `Term.app` Term.var' "x")) + -- `Term.app` r + -- op _ = fail "unpossible" + -- in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.map", prefix "map") , let r = R.Builtin "Remote.receiveAsync" op [chan, timeout] = do Term.Number' seconds <- whnf timeout diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index 18651865f..f3edb3380 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -91,10 +91,10 @@ termBuiltins = (Var.named *** Term.ref) <$> ( , AliasFromModule "Text" ["concatenate", "left", "right", "center", "justify"] [] , AliasFromModule "Remote" - ["fork", "receive", "receiveAsync", "pure", "bind", "map", "channel", "send", "here", "at", "spawn"] [] + ["fork", "receive", "receiveAsync", "pure", "bind", "channel", "send", "here", "at", "spawn"] [] , AliasFromModule "Color" ["rgba"] [] , AliasFromModule "Symbol" ["Symbol"] [] - , AliasFromModule "Index" ["lookup", "unsafeLookup", "insert", "unsafeInsert", "empty", "unsafeEmpty"] [] + , AliasFromModule "Index" ["lookup", "unsafeLookup", "insert", "unsafeInsert", "unsafeEmpty"] [] , AliasFromModule "Html" ["getLinks", "getHref", "getDescription"] [] , AliasFromModule "Http" ["getURL", "unsafeGetURL"] [] ] >>= unpackAliases) diff --git a/shared/tests/Unison/Test/Common.hs b/shared/tests/Unison/Test/Common.hs index b82add01d..e11dc9fd1 100644 --- a/shared/tests/Unison/Test/Common.hs +++ b/shared/tests/Unison/Test/Common.hs @@ -39,6 +39,8 @@ node = do base <- Note.run $ do -- grab all definitions in the node results <- Node.search node Term.blank [] 1000000 (Metadata.Query "") Nothing + sources <- Node.terms node (map fst $ Node.references results) + Note.lift $ putStrLn (show sources) let x = [ (v, Term.ref h) | (h, md) <- Node.references results , v <- toList $ Metadata.firstName (Metadata.names md) ] Note.lift $ putStrLn (show x) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 1e42628e7..5ab44d968 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -34,6 +34,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Either.fold ((+) 1) ((+) 2) (Either.Right 1)" "3" , t "Either.swap (Left 1)" "Either.Right 1" , t "Pair.fold (x y -> x) (1, 2)" "1" + , t "const 41 0" "41" , t "1st (1,2,3,4)" "1" , t "2nd (1,2 + 1,3,4)" "3" ] diff --git a/stack.yaml b/stack.yaml index a32cc9905..a6a20e9db 100644 --- a/stack.yaml +++ b/stack.yaml @@ -13,3 +13,8 @@ extra-deps: - cacophony-0.7.0 - cryptonite-0.17 - unagi-chan-0.4.0.0 + +extra-include-dirs: +- /usr/local/include +extra-lib-dirs: +- /usr/local/lib diff --git a/unison-src/base.u b/unison-src/base.u index ca2052b8f..041c1e362 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -1,6 +1,9 @@ Remote.transfer : Node -> Remote Unit; Remote.transfer node = Remote.at node unit; +Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; +Remote.map f = Remote.bind (f `then` Remote.pure); + then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; then f1 f2 x = f2 (f1 x); From 744ca6ccf00b11e0f99cba8c8a14fa3a4b272eea Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Fri, 19 Aug 2016 17:49:00 -0400 Subject: [PATCH 06/61] fix compile error --- node/src/Worker.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/Worker.hs b/node/src/Worker.hs index 981e1d8e4..42d5ceb6f 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -4,7 +4,7 @@ module Main where import Control.Concurrent.STM.TVar import Control.Monad -import System.Directory (doesFileExist) +import System.Directory (doesDirectoryExist) import System.IO (stderr) import Unison.Hash (Hash) import Unison.NodeProtocol.V0 (protocol) From fdac639034ffb5402918d6aed536da643731b4a7 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sat, 20 Aug 2016 16:27:54 -0400 Subject: [PATCH 07/61] examples working again, needed to resurrect Remote.map builtin --- node/src/Container.hs | 20 +++++++++++++++++--- node/src/Worker.hs | 30 +++++++++++++++++------------- node/unison-node.cabal | 5 ++--- shared/src/Unison/Node/Builtin.hs | 14 +++++++------- unison-src/base.u | 3 +++ 5 files changed, 46 insertions(+), 26 deletions(-) diff --git a/node/src/Container.hs b/node/src/Container.hs index d581832d5..6722bcebd 100644 --- a/node/src/Container.hs +++ b/node/src/Container.hs @@ -1,5 +1,6 @@ {-# Language BangPatterns #-} {-# Language OverloadedStrings #-} +{-# Language CPP #-} module Main where @@ -22,7 +23,11 @@ import qualified Data.ByteString.Base64.URL as Base64 import qualified Data.ByteString.Lazy as LB import qualified Data.Bytes.Put as Put import qualified Data.Text as Text +#ifdef leveldb +import qualified Unison.BlockStore.LevelDbStore as LDBS +#else import qualified Unison.BlockStore.FileBlockStore as FBS +#endif import qualified Unison.NodeContainer as C import qualified Unison.NodeProtocol as NP import qualified Unison.Remote as R @@ -32,8 +37,12 @@ import qualified Unison.Typechecker.Components as Components main :: IO () main = Mux.uniqueChannel >>= \rand -> let - fileBS = FBS.make' rand h "blockstore" h bytes = BA.convert (hash bytes :: Digest Blake2b_512) + #ifdef leveldb + blockstore = LDBS.make rand h "blockstore.leveldb" + #else + blockstore = FBS.make' rand h "blockstore" + #endif locker _ = pure held held = Lock (pure (Just (Lease (pure True) (pure ())))) mkNode _ = do -- todo: actually use node params @@ -55,8 +64,13 @@ main = Mux.uniqueChannel >>= \rand -> P.std_in = P.CreatePipe, P.std_err = P.CreatePipe } in do - fileBS <- fileBS - send <- C.make fileBS locker protocol mkNode launchNode + #ifdef leveldb + putStrLn "using leveldb-based block store" + #else + putStrLn "using file-based block store" + #endif + blockstore <- blockstore + send <- C.make blockstore locker protocol mkNode launchNode S.scotty 8081 $ do S.middleware logStdoutDev S.addroute OPTIONS (S.regex ".*") $ NS.originOptions diff --git a/node/src/Worker.hs b/node/src/Worker.hs index 42d5ceb6f..70b711058 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -4,13 +4,11 @@ module Main where import Control.Concurrent.STM.TVar import Control.Monad -import System.Directory (doesDirectoryExist) import System.IO (stderr) import Unison.Hash (Hash) import Unison.NodeProtocol.V0 (protocol) import Unison.NodeWorker as W import Unison.SerializationAndHashing (TermV) -import qualified Data.Map as Map import qualified Control.Concurrent.STM as STM import qualified Data.Set as Set import qualified Data.Text as Text @@ -21,7 +19,7 @@ import qualified Unison.Eval.Interpreter as I import qualified Unison.Node as Node import qualified Unison.Node.BasicNode as BasicNode import qualified Unison.Node.Builtin as Builtin -import qualified Unison.Node.FileStore as Store +import qualified Unison.Node.MemStore as Store import qualified Unison.Note as Note import qualified Unison.Parsers as Parsers import qualified Unison.Reference as Reference @@ -40,12 +38,14 @@ main = do lang logger crypto blockstore = do let b0 = Builtin.makeBuiltins b1 <- ExtraBuiltins.makeAPI blockstore crypto - store <- Store.make "codestore" + store <- Store.make backend <- BasicNode.make SAH.hash store (\whnf -> b0 whnf ++ b1 whnf) + loadDeclarations "unison-src/base.u" backend + loadDeclarations "unison-src/extra.u" backend initialized <- STM.atomically $ newTVar False - pure $ go backend initialized b0 b1 + pure $ go backend initialized where - go backend initialized b0 b1 = + go backend initialized = let lang :: R.Language TermV Hash lang = R.Language localDependencies eval apply node unit channel local unRemote remote @@ -69,13 +69,15 @@ main = do Right _ -> pure (Right e') initialize = do L.info logger "checking if base libraries loaded" - alreadyInitialized <- doesDirectoryExist "codestore" + let idf = Term.lam' ["x"] (Term.var' "x") :: TermV + let Reference.Derived hashIdf = SAH.hash idf + alreadyInitialized <- pure False -- not . null <$> R.getHashes codestore (Set.fromList [hashIdf]) when (not alreadyInitialized) $ do - L.info logger "codestore/ directory not found, loading base libraries..." - loadDeclarations "unison-src/base.u" backend - loadDeclarations "unison-src/extra.u" backend + L.info logger "codestore not loaded... inserting" hs <- Note.run (Node.allTerms backend) - R.saveHashes codestore [ (h,v) | (Reference.Derived h, v) <- hs ] + -- todo + -- R.saveHashes codestore [ (h,v) | (Reference.Derived h, v) <- hs ] + pure () STM.atomically $ writeTVar initialized True in (lang, typecheck, initialize) apply = Term.app @@ -89,7 +91,9 @@ main = do loadDeclarations path node = do txt <- Text.IO.readFile path let str = Text.unpack txt - L.info logger $ "loading " ++ path r <- Note.run $ Node.declare' Term.ref str node - L.info logger $ "done loading " ++ path + L.info logger $ "loaded " ++ path + L.debug' logger $ do + ts <- Note.run $ Node.allTermsByVarName Term.ref node + pure $ show ts pure r diff --git a/node/unison-node.cabal b/node/unison-node.cabal index 32f7fed15..d40bc5afd 100644 --- a/node/unison-node.cabal +++ b/node/unison-node.cabal @@ -149,6 +149,7 @@ library if flag(leveldb) build-depends: exceptions, leveldb-haskell + cpp-options: -Dleveldb exposed-modules: Unison.BlockStore.LevelDbStore @@ -190,9 +191,6 @@ executable worker unison-shared, vector - if flag(leveldb) - build-depends: exceptions, leveldb-haskell - executable container main-is: Container.hs hs-source-dirs: src @@ -242,6 +240,7 @@ executable container if flag(leveldb) build-depends: exceptions, leveldb-haskell + cpp-options: -Dleveldb executable node main-is: Node.hs diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 495676054..294592d86 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -153,13 +153,13 @@ makeBuiltins whnf = op [a] = pure $ Term.remote (Remote.Step (Remote.Local (Remote.Pure a))) op _ = fail "unpossible" in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.pure", prefix "pure") - --, let r = R.Builtin "Remote.map" - -- op [f, r] = pure $ Term.builtin "Remote.bind" `Term.app` - -- (Term.lam' ["x"] $ Term.remote - -- (Remote.Step . Remote.Local . Remote.Pure $ f `Term.app` Term.var' "x")) - -- `Term.app` r - -- op _ = fail "unpossible" - -- in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.map", prefix "map") + , let r = R.Builtin "Remote.map" + op [f, r] = pure $ Term.builtin "Remote.bind" `Term.app` + (Term.lam' ["x"] $ Term.remote + (Remote.Step . Remote.Local . Remote.Pure $ f `Term.app` Term.var' "x")) + `Term.app` r + op _ = fail "unpossible" + in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.map", prefix "map") , let r = R.Builtin "Remote.receiveAsync" op [chan, timeout] = do Term.Number' seconds <- whnf timeout diff --git a/unison-src/base.u b/unison-src/base.u index 041c1e362..cd5cbc145 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -1,3 +1,6 @@ +identity : ∀ a . a -> a; +identity a = a; + Remote.transfer : Node -> Remote Unit; Remote.transfer node = Remote.at node unit; From 6e198724d68647ea47e8a8f729dd0248d9cdd6c3 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sat, 20 Aug 2016 21:33:42 -0400 Subject: [PATCH 08/61] some cleanup, tests passing again with loading base libs from file --- shared/src/Unison/Node/BasicNode.hs | 7 ------- shared/tests/Unison/Test/Common.hs | 16 ++++++---------- .../tests/Unison/Test/Typechecker/Components.hs | 5 ----- shared/unison-shared.cabal | 1 + 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/shared/src/Unison/Node/BasicNode.hs b/shared/src/Unison/Node/BasicNode.hs index 33c95791b..ba9bca0da 100644 --- a/shared/src/Unison/Node/BasicNode.hs +++ b/shared/src/Unison/Node/BasicNode.hs @@ -2,27 +2,20 @@ {-# LANGUAGE ScopedTypeVariables #-} module Unison.Node.BasicNode where -import Data.Text (Text) -import Unison.Metadata (Metadata(..)) import Unison.Node (Node) import Unison.Node.Store (Store) -import Unison.Parsers (unsafeParseTerm) import Unison.Symbol (Symbol) import Unison.Term (Term) import Unison.Type (Type) import qualified Data.Map as M -import qualified Data.Text as Text import qualified Unison.Eval as Eval import qualified Unison.Eval.Interpreter as I -import qualified Unison.Hash as H -import qualified Unison.Metadata as Metadata import qualified Unison.Node as Node import qualified Unison.Node.Builtin as B import qualified Unison.Node.Store as Store import qualified Unison.Note as N import qualified Unison.Reference as R import qualified Unison.Type as Type -import qualified Unison.Var as Var import qualified Unison.View as View infixr 7 --> diff --git a/shared/tests/Unison/Test/Common.hs b/shared/tests/Unison/Test/Common.hs index e11dc9fd1..440cbdde7 100644 --- a/shared/tests/Unison/Test/Common.hs +++ b/shared/tests/Unison/Test/Common.hs @@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} module Unison.Test.Common where +import Control.Applicative import Control.Monad.IO.Class import Data.Foldable import System.IO (FilePath) @@ -13,6 +14,7 @@ import Unison.Views (defaultSymbol) import qualified Data.Map as Map import qualified Data.Text.IO as Text.IO import qualified Data.Text as Text +import qualified System.FilePath as FP import qualified Unison.Metadata as Metadata import qualified Unison.Node as Node import qualified Unison.Node.MemNode as MemNode @@ -26,7 +28,9 @@ type TNode = (Node IO V Reference (Type V) (Term V), Reference -> V, [(V, Term V loadDeclarations :: FilePath -> Node IO V Reference (Type V) (Term V) -> IO () loadDeclarations path node = do - txt <- Text.IO.readFile path + -- note - when run from repl current directory is root, but when run via stack test, current + -- directory is the shared subdir - so we check both locations + txt <- Text.IO.readFile path <|> Text.IO.readFile (".." `FP.combine` path) let str = Text.unpack txt Note.run $ Node.declare' Term.ref str node @@ -36,15 +40,7 @@ node = do loadDeclarations "unison-src/base.u" node symbols <- liftIO . Note.run $ Map.fromList . Node.references <$> Node.search node Term.blank [] 1000 (Metadata.Query "") Nothing - base <- Note.run $ do - -- grab all definitions in the node - results <- Node.search node Term.blank [] 1000000 (Metadata.Query "") Nothing - sources <- Node.terms node (map fst $ Node.references results) - Note.lift $ putStrLn (show sources) - let x = [ (v, Term.ref h) | (h, md) <- Node.references results - , v <- toList $ Metadata.firstName (Metadata.names md) ] - Note.lift $ putStrLn (show x) - pure x + base <- Note.run $ Node.allTermsByVarName Term.ref node let firstName (Metadata.Names (n:_)) = n let lookupSymbol ref = maybe (defaultSymbol ref) (firstName . Metadata.names) (Map.lookup ref symbols) pure (node, lookupSymbol, base) diff --git a/shared/tests/Unison/Test/Typechecker/Components.hs b/shared/tests/Unison/Test/Typechecker/Components.hs index f780aedff..901081513 100644 --- a/shared/tests/Unison/Test/Typechecker/Components.hs +++ b/shared/tests/Unison/Test/Typechecker/Components.hs @@ -34,11 +34,6 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> t before after = testCase (before ++ " ⟹ " ++ after) $ do (node, _, _) <- node let term = unsafeParseTerm before - case term of - Term.LetRecNamed' bs _ -> - putStrLn $ "components: " ++ show (map (map fst) components) - where components = Components.components bs - _ -> pure () let after' = Components.minimize' term _ <- Note.run $ Node.typeAt node after' [] assertEqual "comparing results" (unsafeParseTerm after) after' diff --git a/shared/unison-shared.cabal b/shared/unison-shared.cabal index 6bbfd7007..c752d81d5 100644 --- a/shared/unison-shared.cabal +++ b/shared/unison-shared.cabal @@ -112,6 +112,7 @@ test-suite tests build-depends: base, containers, + filepath, tasty, tasty-hunit, tasty-smallcheck, From 78c3cddef0f19e68d3413226c8e34c5d34fad2c6 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 21 Aug 2016 17:33:20 -0400 Subject: [PATCH 09/61] added layout blocks, using for do blocks and let bindings, altered syntax of let bindings to closer match do blocks --- .../Unison/Test/SerializationAndHashing.hs | 4 +- shared/src/Unison/Parser.hs | 101 +++++++++++---- shared/src/Unison/TermParser.hs | 121 ++++++++---------- shared/tests/Unison/Test/Common.hs | 3 +- shared/tests/Unison/Test/Interpreter.hs | 12 +- shared/tests/Unison/Test/Term.hs | 116 ++++++++--------- shared/tests/Unison/Test/TermParser.hs | 43 ++++--- shared/tests/Unison/Test/Typechecker.hs | 34 ++--- .../Unison/Test/Typechecker/Components.hs | 20 +-- unison-src/base.u | 66 +++++----- unison-src/extra.u | 5 +- 11 files changed, 289 insertions(+), 236 deletions(-) diff --git a/node/tests/Unison/Test/SerializationAndHashing.hs b/node/tests/Unison/Test/SerializationAndHashing.hs index 17bcb9910..9aece1a11 100644 --- a/node/tests/Unison/Test/SerializationAndHashing.hs +++ b/node/tests/Unison/Test/SerializationAndHashing.hs @@ -26,10 +26,10 @@ lambda :: Assertion lambda = testTermString "x -> x" letBinding :: Assertion -letBinding = testTermString "let x = 42 in x + 1" +letBinding = testTermString "let x = 42; x + 1" letRec :: Assertion -letRec = testTermString "let rec x = x + 1 in x" +letRec = testTermString "let rec x = x + 1; x" vec :: Assertion vec = testTermString "[\"a\", \"b\", \"c\"]" diff --git a/shared/src/Unison/Parser.hs b/shared/src/Unison/Parser.hs index e122a2b20..e494c7820 100644 --- a/shared/src/Unison/Parser.hs +++ b/shared/src/Unison/Parser.hs @@ -12,22 +12,32 @@ import Data.Maybe import Prelude hiding (takeWhile) import qualified Data.Char as Char import qualified Prelude +import Debug.Trace -newtype Parser a = Parser { run :: String -> Result a } +type InLayout = Bool +newtype Parser a = Parser { run' :: (String,InLayout) -> Result a } root :: Parser a -> Parser a -root p = many (whitespace1 <|> haskellLineComment) *> (p <* eof) +root p = ignored *> (p <* (optional semicolon <* eof)) + +semicolon :: Parser () +semicolon = void $ token (char ';') eof :: Parser () -eof = Parser $ \s -> case s of +eof = Parser $ \(s,_) -> case s of [] -> Succeed () 0 False _ -> Fail [Prelude.takeWhile (/= '\n') s, "expected eof, got"] False attempt :: Parser a -> Parser a -attempt p = Parser $ \s -> case run p s of +attempt p = Parser $ \s -> case run' p s of Fail stack _ -> Fail stack False Succeed a n _ -> Succeed a n False +run :: Parser a -> String -> Result a +-- run p s = run' p (watch "layoutized" $ layoutize s, False) +run p s = run' p (layoutize s, False) + where watch msg a = trace (msg ++ ":\n" ++ a) a + unsafeRun :: Parser a -> String -> a unsafeRun p s = case toEither $ run p s of Right a -> a @@ -39,7 +49,7 @@ unsafeGetSucceed r = case r of Fail e _ -> error (unlines ("Parse error:":e)) string :: String -> Parser String -string s = Parser $ \input -> +string s = Parser $ \(input,_) -> if s `isPrefixOf` input then Succeed s (length s) False else Fail ["expected '" ++ s ++ "', got " ++ takeLine input] False @@ -47,12 +57,12 @@ takeLine :: String -> String takeLine = Prelude.takeWhile (/= '\n') char :: Char -> Parser Char -char c = Parser $ \input -> +char c = Parser $ \(input,_) -> if listToMaybe input == Just c then Succeed c 1 False - else Fail [] False + else Fail ["expected " ++ show c ++ " near " ++ takeLine input] False one :: (Char -> Bool) -> Parser Char -one f = Parser $ \s -> case s of +one f = Parser $ \(s,_) -> case s of (h:_) | f h -> Succeed h 1 False _ -> Fail [] False @@ -83,31 +93,31 @@ identifier' charTests stringTests = do pure i token :: Parser a -> Parser a -token p = p <* many (whitespace1 <|> haskellLineComment) +token p = p <* ignored haskellLineComment :: Parser () haskellLineComment = void $ string "--" *> takeWhile "-- comment" (/= '\n') lineErrorUnless :: String -> Parser a -> Parser a -lineErrorUnless s p = commitFail $ Parser $ \input -> case run p input of +lineErrorUnless s p = commitFail $ Parser $ \input -> case run' p input of Fail e b -> Fail (s:m:e) b - where m = "near \'" ++ Prelude.takeWhile (/= '\n') input ++ "\'" + where m = "near \'" ++ Prelude.takeWhile (/= '\n') (fst input) ++ "\'" ok -> ok parenthesized :: Parser a -> Parser a parenthesized p = lp *> body <* rp where - lp = token (char '(') - body = p + lp = char '(' *> withoutLayout ignored + body = withoutLayout p rp = lineErrorUnless "missing )" $ token (char ')') takeWhile :: String -> (Char -> Bool) -> Parser String -takeWhile msg f = scope msg . Parser $ \s -> +takeWhile msg f = scope msg . Parser $ \(s,_) -> let hd = Prelude.takeWhile f s in Succeed hd (length hd) False takeWhile1 :: String -> (Char -> Bool) -> Parser String -takeWhile1 msg f = scope msg . Parser $ \s -> +takeWhile1 msg f = scope msg . Parser $ \(s,_) -> let hd = Prelude.takeWhile f s in if null hd then Fail ["takeWhile1 empty: " ++ take 20 s] False else Succeed hd (length hd) False @@ -119,22 +129,22 @@ whitespace1 :: Parser () whitespace1 = void $ takeWhile1 "whitespace1" Char.isSpace nonempty :: Parser a -> Parser a -nonempty p = Parser $ \s -> case run p s of +nonempty p = Parser $ \s -> case run' p s of Succeed _ 0 b -> Fail [] b ok -> ok scope :: String -> Parser a -> Parser a -scope s p = Parser $ \input -> case run p input of +scope s p = Parser $ \input -> case run' p input of Fail e b -> Fail (s:e) b ok -> ok commitSuccess :: Parser a -> Parser a -commitSuccess p = Parser $ \input -> case run p input of +commitSuccess p = Parser $ \input -> case run' p input of Fail e b -> Fail e b Succeed a n _ -> Succeed a n True commitFail :: Parser a -> Parser a -commitFail p = Parser $ \input -> case run p input of +commitFail p = Parser $ \input -> case run' p input of Fail e _ -> Fail e True Succeed a n b -> Succeed a n b @@ -153,6 +163,53 @@ sepBy sep pb = f <$> optional (sepBy1 sep pb) sepBy1 :: Parser a -> Parser b -> Parser [b] sepBy1 sep pb = (:) <$> pb <*> many (sep *> pb) +inLayout :: Parser Bool +inLayout = Parser $ \(_,inLayout) -> Succeed inLayout 0 False + +layoutChar :: Parser () +layoutChar = void $ one (\c -> c == ';' || c == '{' || c == '}') + +ignored :: Parser () +ignored = void $ do + inLayout <- inLayout + case inLayout of + True -> many (whitespace1 <|> haskellLineComment) + False -> many (whitespace1 <|> haskellLineComment <|> layoutChar) + +withLayout :: Parser a -> Parser a +withLayout p = Parser $ \(s,_) -> run' p (s,True) + +withoutLayout :: Parser a -> Parser a +withoutLayout p = Parser $ \(s,_) -> run' p (s,False) + +layout :: Parser a -> Parser a +layout p = withLayout $ token (char '{') *> p <* token (char '}') + +layoutize :: String -> String +layoutize s = tweak $ go s [] where + close s = '}' : s + onlysemis line = all (\c -> Char.isSpace c || c == ';') line + tweak [] = [] + tweak s = case span (/= '\n') s of + ([],[]) -> [] + ([],nl:s) -> nl : tweak s + (line,rem) -> + if onlysemis line then (filter (/= ';') line) ++ tweak rem + else line ++ tweak rem + go s stack = case s of + '\n' : tl -> handle tl stack where + indent = length $ Prelude.takeWhile (\c -> c == ' ' || c == '\t') tl + handle :: String -> [Int] -> String + handle tl [] | indent == 0 = ';' : '\n' : go tl stack + | otherwise = '\n' : '{' : go tl (indent : stack) + handle tl stack@(level:levels) = + if indent == level then ';' : '\n' : go tl stack + else if indent > level then '{' : '\n' : go tl (indent:stack) + else close $ go ('\n' : tl) levels + '\r' : tl -> go tl stack + hd : tl -> hd : go tl stack + [] -> replicate (length stack) '}' + toEither :: Result a -> Either String a toEither (Fail e _) = Left (intercalate "\n" e) toEither (Succeed a _ _) = Right a @@ -176,13 +233,13 @@ instance Alternative Parser where instance Monad Parser where return a = Parser $ \_ -> Succeed a 0 False Parser p >>= f = Parser $ \s -> case p s of - Succeed a n committed -> case run (f a) (drop n s) of + Succeed a n committed -> case run' (f a) (drop n (fst s), snd s) of Succeed b m c2 -> Succeed b (n+m) (committed || c2) Fail e b -> Fail e (committed || b) Fail e b -> Fail e b instance MonadPlus Parser where mzero = Parser $ \_ -> Fail [] False - mplus p1 p2 = Parser $ \s -> case run p1 s of - Fail _ False -> run p2 s + mplus p1 p2 = Parser $ \s -> case run' p1 s of + Fail _ False -> run' p2 s ok -> ok diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index e147332ab..a3b5807d5 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -7,7 +7,7 @@ import Prelude hiding (takeWhile) import Control.Applicative import Data.Char (isDigit, isAlphaNum, isSpace, isSymbol, isPunctuation) import Data.Foldable (asum) -import Data.Functor (($>), void) +import Data.Functor import Data.List (foldl') import Data.Set (Set) import Unison.Parser @@ -67,32 +67,36 @@ tupleOrParenthesized rec = unit = Term.builtin "()" -- | --- Remote { x := pure 23; y := at node2 23; pure 19 } --- Remote { action1; action2; } --- Remote { action1; x = 1 + 1; action2; } +-- do Remote { x := pure 23; y := at node2 23; pure 19 } +-- do Remote { action1; action2; } +-- do Remote { action1; x = 1 + 1; action2; } +-- do Remote +-- x := pure 23 +-- y = 11 +-- pure (f x) effectBlock :: Var v => Parser (Term v) -effectBlock = do - name <- wordyId <* token (string "{") - let qualifiedPure = ABT.var' (Text.pack name `mappend` Text.pack ".pure") - qualifiedBind = ABT.var' (Text.pack name `mappend` Text.pack ".bind") - bindings <- some $ asum [Right <$> binding qualifiedPure, Left <$> action qualifiedPure] - Just result <- pure $ foldr (bind qualifiedBind) Nothing bindings - result <$ lineErrorUnless "missing }" (token (string "}")) - where - bind qb = go where - go (Right (lhs,rhs)) (Just acc) = Just $ qb `Term.apps` [Term.lam lhs acc, rhs] - go (Right (_,_)) Nothing = Nothing - go (Left action) (Just acc) = Just $ qb `Term.apps` [Term.lam (ABT.v' "_") acc, action] - go (Left action) _ = Just action - interpretPure qp = ABT.subst (ABT.v' "pure") qp - binding qp = scope "binding" $ do - lhs <- ABT.v' . Text.pack <$> token wordyId - eff <- token $ (True <$ string ":=") <|> (False <$ string "=") - rhs <- term <* token (string ";") - let rhs' = if eff then interpretPure qp rhs - else qp `Term.app` rhs - pure (lhs, rhs') - action qp = attempt . scope "action" $ (interpretPure qp <$> term) <* token (string ";") +effectBlock = (token (string "do") *> withLayout wordyId) >>= go where + go name = layout $ do + bindings <- sepBy1 semicolon $ asum [Right <$> binding, Left <$> action] + Just result <- pure $ foldr bind Nothing bindings + pure result + where + qualifiedPure = ABT.var' (Text.pack name `mappend` Text.pack ".pure") + qualifiedBind = ABT.var' (Text.pack name `mappend` Text.pack ".bind") + bind = go where + go (Right (lhs,rhs)) (Just acc) = Just $ qualifiedBind `Term.apps` [Term.lam lhs acc, rhs] + go (Right (_,_)) Nothing = Nothing + go (Left action) (Just acc) = Just $ qualifiedBind `Term.apps` [Term.lam (ABT.v' "_") acc, action] + go (Left action) _ = Just action + interpretPure = ABT.subst (ABT.v' "pure") qualifiedPure + binding = scope "binding" $ do + lhs <- ABT.v' . Text.pack <$> token wordyId + eff <- token $ (True <$ string ":=") <|> (False <$ string "=") + rhs <- term + let rhs' = if eff then interpretPure rhs + else qualifiedPure `Term.app` rhs + pure (lhs, rhs') + action = attempt . scope "action" $ (interpretPure <$> term) text' :: Parser Literal text' = @@ -148,48 +152,24 @@ ann'' = token (char ':') *> TypeParser.type_ --let server = _; blah = _ in _ let_ :: Var v => Parser (Term v) -> Parser (Term v) -let_ p = f <$> (let_ *> optional rec_) <*> bindings' <* in_ <*> body +let_ p = f <$> withLayout (let_ *> optional rec_) <*> (layout bindings' <|> bindings') where let_ = token (string "let") rec_ = token (string "rec") $> () - bindings' = lineErrorUnless "error parsing let bindings" (bindings p) - in_ = lineErrorUnless "missing 'in' after bindings in let-expression'" $ - (optional (token (string ";")) *> token (string "in")) - body = lineErrorUnless "parse error in body of let-expression" p - -- f = maybe Term.let1' - f :: Ord v => Maybe () -> [(v, Term v)] -> Term v -> Term v - f Nothing bindings body = Term.let1 bindings body - f (Just _) bindings body = Term.letRec bindings body - -semicolon :: Parser () -semicolon = void $ token (char ';') - -infixBinding :: Var v => Parser (Term v) -> Parser (v, Term v) -infixBinding p = ((,,,,) <$> optional (typedecl <* semicolon) <*> prefixVar <*> infixVar <*> prefixVar <*> bindingEqBody p) >>= f - where - f :: Var v => (Maybe (v, Type v), v, v, v, Term v) -> Parser (v, Term v) - f (Just (opName', _), _, opName, _, _) | opName /= opName' = - failWith ("The type signature for ‘" ++ show (Var.name opName') ++ "’ lacks an accompanying binding") - f (Nothing, arg1, opName, arg2, body) = pure (mkBinding opName [arg1,arg2] body) - f (Just (_, type'), arg1, opName, arg2, body) = pure $ (`Term.ann` type') <$> mkBinding opName [arg1,arg2] body - -mkBinding :: Ord v => v -> [v] -> Term v -> (v, Term v) -mkBinding f [] body = (f, body) -mkBinding f args body = (f, Term.lam'' args body) + bindings' = withLayout $ do + bs <- lineErrorUnless "error parsing let bindings" (bindings p) + semicolon + body <- lineErrorUnless "parse error in body of let-expression" p + pure (bs, body) + f :: Ord v => Maybe () -> ([(v, Term v)], Term v) -> Term v + f Nothing (bindings, body) = Term.let1 bindings body + f (Just _) (bindings, body) = Term.letRec bindings body typedecl :: Var v => Parser (v, Type v) typedecl = (,) <$> prefixVar <*> ann'' -prefixBinding :: Var v => Parser (Term v) -> Parser (v, Term v) -prefixBinding p = ((,,,) <$> optional (typedecl <* semicolon) <*> prefixVar <*> many prefixVar <*> bindingEqBody p) >>= f -- todo - where - f (Just (opName, _), opName', _, _) | opName /= opName' = - failWith ("The type signature for ‘" ++ show (Var.name opName') ++ "’ lacks an accompanying binding") - f (Nothing, name, args, body) = pure $ mkBinding name args body - f (Just (_, t), name, args, body) = pure $ (`Term.ann` t) <$> mkBinding name args body - bindingEqBody :: Parser (Term v) -> Parser (Term v) -bindingEqBody p = eq *> body +bindingEqBody p = eq *> (layout body <|> body) where eq = token (char '=') body = lineErrorUnless "parse error in body of binding" p @@ -213,7 +193,6 @@ infixVar = (Var.named . Text.pack) <$> (backticked <|> symbolyId) where backticked = char '`' *> wordyId <* token (char '`') - prefixVar :: Var v => Parser v prefixVar = (Var.named . Text.pack) <$> prefixOp where @@ -224,7 +203,7 @@ prefixTerm :: Var v => Parser (Term v) prefixTerm = Term.var <$> prefixVar keywords :: Set String -keywords = Set.fromList ["let", "rec", "in", "->", ":", "=", "where"] +keywords = Set.fromList ["do", "let", "rec", "in", "->", ":", "=", "where"] lam :: Var v => Parser (Term v) -> Parser (Term v) lam p = Term.lam'' <$> vars <* arrow <*> body @@ -240,8 +219,20 @@ prefixApp p = f <$> some p f [] = error "'some' shouldn't produce an empty list" bindings :: Var v => Parser (Term v) -> Parser [(v, Term v)] -bindings p = - sepBy1 (token (char ';')) (prefixBinding p <|> infixBinding p) +bindings p = withLayout (sepBy1 semicolon binding) where + binding = do + typ <- optional (typedecl <* semicolon) + (name, args) <- ((\arg1 op arg2 -> (op,[arg1,arg2])) <$> prefixVar <*> infixVar <*> prefixVar) + <|> ((,) <$> prefixVar <*> many prefixVar) + body <- bindingEqBody p + case typ of + Nothing -> pure $ mkBinding name args body + Just (nameT, typ) + | name == nameT -> case mkBinding name args body of (v,body) -> pure (v, Term.ann body typ) + | otherwise -> failWith ("The type signature for ‘" ++ show (Var.name nameT) ++ "’ lacks an accompanying binding") + + mkBinding f [] body = (f, body) + mkBinding f args body = (f, Term.lam'' args body) moduleBindings :: Var v => Parser [(v, Term v)] -moduleBindings = root (bindings term3 <* optional (token (char ';'))) +moduleBindings = root (bindings term3) diff --git a/shared/tests/Unison/Test/Common.hs b/shared/tests/Unison/Test/Common.hs index 440cbdde7..4098f9893 100644 --- a/shared/tests/Unison/Test/Common.hs +++ b/shared/tests/Unison/Test/Common.hs @@ -32,7 +32,8 @@ loadDeclarations path node = do -- directory is the shared subdir - so we check both locations txt <- Text.IO.readFile path <|> Text.IO.readFile (".." `FP.combine` path) let str = Text.unpack txt - Note.run $ Node.declare' Term.ref str node + _ <- Note.run $ Node.declare' Term.ref str node + putStrLn $ "loaded file: " ++ path node :: IO TNode node = do diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 5ab44d968..b5be135c9 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -14,7 +14,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> [ t "1 + 1" "2" , t "1 + 1 + 1" "3" , t "(x -> x) 42" "42" - , t "let x = 2; y = 3 in x + y" "5" + , t "let x = 2; y = 3 ; x + y" "5" , t "if False 0 1" "1" , t "if True 12 13" "12" , t "1 > 0" "True" @@ -23,12 +23,12 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "1 < 2" "True" , t "1 <= 1" "True" , t "1 >= 1" "True" - , t "let rec fac n = if (n == 0) 1 (n * fac (n - 1)) in fac 5" "120" - , t "let rec ping n = if (n >= 10) n (pong (n + 1)); pong n = ping (n + 1) in ping 0" + , t "let rec fac n = if (n == 0) 1 (n * fac (n - 1)); fac 5" "120" + , t "let rec ping n = if (n >= 10) n (pong (n + 1)); pong n = ping (n + 1); ping 0" "10" - , t "let id x = x; g = id 42; p = id \"hi\" in g" "42" - , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" in g" "42" - , t "((let id x = x in id) : forall a . a -> a) 42" "42" + , t "let id x = x; g = id 42; p = id \"hi\" ; g" "42" + , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" ; g" "42" + , t "((let id x = x; id) : forall a . a -> a) 42" "42" , t "Optional.map ((+) 1) (Some 1)" "Optional.Some (1 + 1)" , t "Either.fold ((+) 1) ((+) 2) (Either.Left 1)" "2" , t "Either.fold ((+) 1) ((+) 2) (Either.Right 1)" "3" diff --git a/shared/tests/Unison/Test/Term.hs b/shared/tests/Unison/Test/Term.hs index 92181caf6..725aa7883 100644 --- a/shared/tests/Unison/Test/Term.hs +++ b/shared/tests/Unison/Test/Term.hs @@ -40,6 +40,9 @@ atPts print (_,symbol,_) pts t = map go pts where path x y = Doc.at bounds (X (fromIntegral x), Y (fromIntegral y)) debug b = if print then trace ("\n" ++ Doc.debugDoc doc ++ "\n\n" ++ Doc.debugBox b ++ "\n\n" ++ Doc.debugBoxp b) b else b +main :: IO () +main = defaultMain tests + tests :: TestTree tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Term" [ testCase "alpha equivalence (term)" $ assertEqual "identity" @@ -48,56 +51,56 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Term" , testCase "hash cycles" $ assertEqual "pingpong" (hash pingpong1) (hash pingpong2) - , testCase "infix-rendering (1)" $ node >>= \(_,symbol,_) -> - let t = unsafeParseTerm "Number.plus 1 1" - in assertEqual "+" - "1 + 1" - (Doc.formatText (Width 80) (Views.term symbol t)) - , testCase "infix-rendering (unsaturated)" $ node >>= \(_,symbol,_) -> - let t = unsafeParseTerm "Number.plus _" - in assertEqual "+" - "(+) _" - (Doc.formatText (Width 80) (Views.term symbol t)) - , testCase "infix-rendering (totally unsaturated)" $ node >>= \(_,symbol,_) -> - let t = unsafeParseTerm "Number.plus" - in assertEqual "+" "(+)" (Doc.formatText (Width 80) (Views.term symbol t)) - , testCase "infix-rendering (2)" $ node >>= \(_,symbol,_) -> - do - t <- pure $ unsafeParseTerm "Number.plus 1 1" - let d = Views.term symbol t - assertEqual "path sanity check" - [Paths.Fn,Paths.Arg] - (head $ Doc.leafPaths d) - , testCase "let-rendering (1)" $ node >>= \node -> - do - -- let xy = 4223 in 42 - t <- pure $ unsafeParseTerm "let xy = 4223 in 42" - [(p1,r1), (p2,_), (p3,r3), (p4,_), (p5,r5), (p6,r6)] <- pure $ - atPts False node [(0,0), (1,0), (10,0), (11,0), (5,0), (8,0)] t - assertEqual "p1" [] p1 - assertEqual "p2" [] p2 - assertEqual "r1" (rect 0 0 19 1) r1 - assertEqual "p3" [Paths.Binding 0, Paths.Body] p3 - assertEqual "r3" (rect 9 0 4 1) r3 - assertEqual "p3 == p4" p3 p4 - assertEqual "p5" [Paths.Binding 0, Paths.Bound] p5 - assertEqual "r5" (rect 4 0 2 1) r5 - assertEqual "p6" [Paths.Binding 0] p6 - assertEqual "r6" (rect 4 0 9 1) r6 - , testCase "map lambda rendering" $ node >>= \node -> - do - -- map (x -> _) [1,2,3] - t <- pure $ builtin "Vector.map" `app` lam' ["x"] blank `app` vector (map num [1,2,3]) - [(p1,r1)] <- pure $ atPts False node [(5,0)] t - assertEqual "p1" [Paths.Fn, Paths.Arg] p1 - assertEqual "r1" (rect 4 0 8 1) r1 - , testCase "operator chain rendering" $ node >>= \node -> - do - t <- pure $ unsafeParseTerm "1 + 2 + 3" - [(p1,r1),(p2,_)] <- pure $ atPts False node [(1,0), (2,0)] t - assertEqual "p1" [Paths.Fn, Paths.Arg, Paths.Fn, Paths.Arg] p1 - assertEqual "r1" (rect 0 0 1 1) r1 - assertEqual "p2" [] p2 +-- , testCase "infix-rendering (1)" $ node >>= \(_,symbol,_) -> +-- let t = unsafeParseTerm "Number.plus 1 1" +-- in assertEqual "+" +-- "1 + 1" +-- (Doc.formatText (Width 80) (Views.term symbol t)) +-- , testCase "infix-rendering (unsaturated)" $ node >>= \(_,symbol,_) -> +-- let t = unsafeParseTerm "Number.plus _" +-- in assertEqual "+" +-- "(+) _" +-- (Doc.formatText (Width 80) (Views.term symbol t)) +-- , testCase "infix-rendering (totally unsaturated)" $ node >>= \(_,symbol,_) -> +-- let t = unsafeParseTerm "Number.plus" +-- in assertEqual "+" "(+)" (Doc.formatText (Width 80) (Views.term symbol t)) +-- , testCase "infix-rendering (2)" $ node >>= \(_,symbol,_) -> +-- do +-- t <- pure $ unsafeParseTerm "Number.plus 1 1" +-- let d = Views.term symbol t +-- assertEqual "path sanity check" +-- [Paths.Fn,Paths.Arg] +-- (head $ Doc.leafPaths d) +-- , testCase "let-rendering (1)" $ node >>= \node -> +-- do +-- -- let xy = 4223 in 42 +-- t <- pure $ unsafeParseTerm "let xy = 4223 in 42" +-- [(p1,r1), (p2,_), (p3,r3), (p4,_), (p5,r5), (p6,r6)] <- pure $ +-- atPts False node [(0,0), (1,0), (10,0), (11,0), (5,0), (8,0)] t +-- assertEqual "p1" [] p1 +-- assertEqual "p2" [] p2 +-- assertEqual "r1" (rect 0 0 19 1) r1 +-- assertEqual "p3" [Paths.Binding 0, Paths.Body] p3 +-- assertEqual "r3" (rect 9 0 4 1) r3 +-- assertEqual "p3 == p4" p3 p4 +-- assertEqual "p5" [Paths.Binding 0, Paths.Bound] p5 +-- assertEqual "r5" (rect 4 0 2 1) r5 +-- assertEqual "p6" [Paths.Binding 0] p6 +-- assertEqual "r6" (rect 4 0 9 1) r6 +-- , testCase "map lambda rendering" $ node >>= \node -> +-- do +-- -- map (x -> _) [1,2,3] +-- t <- pure $ builtin "Vector.map" `app` lam' ["x"] blank `app` vector (map num [1,2,3]) +-- [(p1,r1)] <- pure $ atPts False node [(5,0)] t +-- assertEqual "p1" [Paths.Fn, Paths.Arg] p1 +-- assertEqual "r1" (rect 4 0 8 1) r1 +-- , testCase "operator chain rendering" $ node >>= \node -> +-- do +-- t <- pure $ unsafeParseTerm "1 + 2 + 3" +-- [(p1,r1),(p2,_)] <- pure $ atPts False node [(1,0), (2,0)] t +-- assertEqual "p1" [Paths.Fn, Paths.Arg, Paths.Fn, Paths.Arg] p1 +-- assertEqual "r1" (rect 0 0 1 1) r1 +-- assertEqual "p2" [] p2 ] rect :: Int -> Int -> Int -> Int -> (X,Y,Width,Height) @@ -108,15 +111,12 @@ rect x y w h = pingpong1 :: TTerm pingpong1 = unsafeParseTerm $ - unlines [ "let rec ping = x -> pong (x + 1)" - , " ; pong = y -> ping (y - 1)" - , " in ping 1" + unlines [ "let rec " + , " ping x = pong (x + 1)" + , " pong y = ping (y - 1)" + , " ping 1" ] pingpong2 :: TTerm pingpong2 = - unsafeParseTerm $ - unlines [ "let rec pong1 = p -> ping1 (p - 1)" - , " ; ping1 = q -> pong1 (q + 1)" - , " in ping1 1" - ] + unsafeParseTerm $ "let rec pong1 p = ping1 (p - 1); ping1 q = pong1 (q + 1); ping1 1" diff --git a/shared/tests/Unison/Test/TermParser.hs b/shared/tests/Unison/Test/TermParser.hs index 6d841350f..928679eec 100644 --- a/shared/tests/Unison/Test/TermParser.hs +++ b/shared/tests/Unison/Test/TermParser.hs @@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} module Unison.Test.TermParser where +import Data.List import Data.Text (Text) import Test.Tasty import Test.Tasty.HUnit @@ -19,7 +20,7 @@ parse :: (String, Term (Symbol DFO)) -> TestTree parse (s, expected) = testCase ("`" ++ s ++ "`") $ case parseTerm s of - Fail _ _ -> assertFailure "parse failure" + Fail e _ -> assertFailure $ "parse failure " ++ intercalate "\n" e Succeed a _ _ -> assertEqual "mismatch" expected a parseFail :: (String,String) -> TestTree @@ -62,51 +63,51 @@ tests = testGroup "TermParser" $ (parse <$> shouldPass) ++ (parseFail <$> should , ("1:Int", ann one int) , ("(1:Int)", ann one int) , ("(1:Int) : Int", ann (ann one int) int) - , ("let a = 1 in a + 1", let1' [("a", one)] (apps numberplus [a, one])) - , ("let a : Int; a = 1 in a + 1", let_a_int1_in_aplus1) - , ("let a: Int; a = 1 in a + 1", let_a_int1_in_aplus1) - , ("let a :Int; a = 1 in a + 1", let_a_int1_in_aplus1) - , ("let a:Int; a = 1 in a + 1", let_a_int1_in_aplus1) + , ("let a = 1; a + 1", let1' [("a", one)] (apps numberplus [a, one])) + , ("let a : Int; a = 1; a + 1", let_a_int1_in_aplus1) + , ("let a: Int; a = 1; a + 1", let_a_int1_in_aplus1) + , ("let a :Int; a = 1; a + 1", let_a_int1_in_aplus1) + , ("let a:Int; a = 1; a + 1", let_a_int1_in_aplus1) , ("a b -> a + b", lam_ab_aplusb) , ("(a b -> a + b) : Int -> Int -> Int", ann lam_ab_aplusb intintint) , ("a b -> a + b : Int", lam' ["a", "b"] (ann (apps numberplus [a, b]) int)) , ("a -> a", lam' ["a"] a) , ("(a -> a) : forall a . a -> a", ann (lam' ["a"] a) (T.forall' ["a"] (T.arrow a' a'))) - , ("let f = a b -> a + b in f 1 1", f_eq_lamab_in_f11) - , ("let f a b = a + b in f 1 1", f_eq_lamab_in_f11) - , ("let f (+) b = 1 + b in f g 1", let1' [("f", lam' ["+", "b"] (apps plus [one, b]))] (apps f [g,one])) - , ("let a + b = f a b in 1 + 1", let1' [("+", lam' ["a", "b"] fab)] one_plus_one) - , ("let (+) : Int -> Int -> Int; a + b = f a b in 1 + 1", plusintintint_fab_in_1plus1) - , ("let (+) : Int -> Int -> Int; (+) a b = f a b in 1 + 1", plusintintint_fab_in_1plus1) - , ("let (+) : Int -> Int -> Int; (+) a b = f a b in (+) 1 1", plusintintint_fab_in_1plus1) - , ("let f b = b + 1; a = 1 in (+) a (f 1)", let1' [("f", lam_b_bplus1), ("a", one)] (apps numberplus [a, apps f [one]])) + , ("let f = a b -> a + b; f 1 1", f_eq_lamab_in_f11) + , ("let f a b = a + b; f 1 1", f_eq_lamab_in_f11) + , ("let f (+) b = 1 + b; f g 1", let1' [("f", lam' ["+", "b"] (apps plus [one, b]))] (apps f [g,one])) + , ("let a + b = f a b; 1 + 1", let1' [("+", lam' ["a", "b"] fab)] one_plus_one) + , ("let (+) : Int -> Int -> Int; a + b = f a b; 1 + 1", plusintintint_fab_in_1plus1) + , ("let (+) : Int -> Int -> Int; (+) a b = f a b; 1 + 1", plusintintint_fab_in_1plus1) + , ("let (+) : Int -> Int -> Int; (+) a b = f a b; (+) 1 1", plusintintint_fab_in_1plus1) + , ("let f b = b + 1; a = 1; (+) a (f 1)", let1' [("f", lam_b_bplus1), ("a", one)] (apps numberplus [a, apps f [one]])) -- from Unison.Test.Term , ("a -> a", lam' ["a"] $ var' "a") -- id , ("x y -> x", lam' ["x", "y"] $ var' "x") -- const - , ("let rec fix = f -> f (fix f) in fix", fix) -- fix - , ("let rec fix f = f (fix f) in fix", fix) -- fix + , ("let rec fix = f -> f (fix f); fix", fix) -- fix + , ("let rec fix f = f (fix f); fix", fix) -- fix , ("1 + 2 + 3", num 1 `plus'` num 2 `plus'` num 3) , ("[1, 2, 1 + 1]", vector [num 1, num 2, num 1 `plus'` num 1]) - , ("(id -> let x = id 42; y = id \"hi\" in 43) : (forall a.a) -> Number", lam' ["id"] (let1' + , ("(id -> let x = id 42; y = id \"hi\"; 43) : (forall a.a) -> Number", lam' ["id"] (let1' [ ("x", var' "id" `app` num 42), ("y", var' "id" `app` text "hi") ] (num 43)) `ann` (T.forall' ["a"] (T.v' "a") `T.arrow` T.lit T.Number)) , ("#" ++ Text.unpack sampleHash64, derived' sampleHash64) , ("#" ++ Text.unpack sampleHash512, derived' sampleHash512) - , ("(Remote { pure 42; })", builtin "Remote.pure" `app` num 42) - , ("Remote { x = 42; pure (x + 1); }", + , ("(do Remote { pure 42 })", builtin "Remote.pure" `app` num 42) + , ("do Remote { x = 42; pure (x + 1) }", builtin "Remote.bind" `apps` [ lam' ["q"] (builtin "Remote.pure" `app` (var' "q" `plus'` num 1)), builtin "Remote.pure" `app` num 42 ] ) - , ("Remote { x := pure 42; pure (x + 1); }", + , ("do Remote { x := pure 42; pure (x + 1) }", builtin "Remote.bind" `apps` [ lam' ["q"] (builtin "Remote.pure" `app` (var' "q" `plus'` num 1)), builtin "Remote.pure" `app` num 42 ] ) - , ("Remote { x := pure 42; y := pure 18; pure (x + y); }", + , ("do Remote\n x := pure 42\n y := pure 18\n pure (x + y)", builtin "Remote.bind" `apps` [ lam' ["x"] (builtin "Remote.bind" `apps` [ lam' ["y"] (builtin "Remote.pure" `app` (var' "x" `plus'` var' "y")), diff --git a/shared/tests/Unison/Test/Typechecker.hs b/shared/tests/Unison/Test/Typechecker.hs index 8b160dcd2..b29544476 100644 --- a/shared/tests/Unison/Test/Typechecker.hs +++ b/shared/tests/Unison/Test/Typechecker.hs @@ -80,7 +80,7 @@ synthesizesAndChecks node e t = --singleTest = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typechecker" -- [ --- testTerm "f -> let x = (let saved = f in 42) in 1" $ \tms -> +-- testTerm "f -> let x = (let saved = f; 42); 1" $ \tms -> -- testCase ("synthesize/check ("++tms++")") $ synthesizesAndChecks node -- (unsafeParseTerm tms) -- (unsafeParseType "forall x. x -> Number") @@ -119,14 +119,14 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck , testCase "synthesize/check (x y -> y)" $ synthesizesAndChecks node (unsafeParseTerm "x y -> y") (unsafeParseType "forall a b. a -> b -> b") - , testCase "synthesize/check (let f = (+) in f 1)" $ synthesizesAndChecks node - (unsafeParseTerm "let f = (+) in f 1") + , testCase "synthesize/check (let f = (+); f 1)" $ synthesizesAndChecks node + (unsafeParseTerm "let f = (+); f 1") (T.lit T.Number --> T.lit T.Number) - , testCase "synthesize/check (let blank x = _ in blank 1)" $ synthesizesAndChecks node - (unsafeParseTerm "let blank x = _ in blank 1") + , testCase "synthesize/check (let blank x = _; blank 1)" $ synthesizesAndChecks node + (unsafeParseTerm "let blank x = _; blank 1") (forall' ["a"] $ T.v' "a") , testCase "synthesize/check Term.fix" $ synthesizesAndChecks node - (unsafeParseTerm "let rec fix f = f (fix f) in fix") + (unsafeParseTerm "let rec fix f = f (fix f); fix") (forall' ["a"] $ (T.v' "a" --> T.v' "a") --> T.v' "a") , testCase "synthesize/check Term.pingpong1" $ synthesizesAndChecks node Term.pingpong1 @@ -137,15 +137,15 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck , testTerm "[1, 2, 1 + 1]" $ \tms -> testCase ("synthesize/checkAt "++tms++"@[Index 2]") $ synthesizesAndChecksAt node [Paths.Index 2] (unsafeParseTerm tms) (T.lit T.Number) - , testTerm "let x = _ in _" $ \tms -> + , testTerm "let x = _; _" $ \tms -> testCase ("synthesize/checkAt ("++tms++")@[Binding 0,Body]") $ synthesizesAndChecksAt node [Paths.Binding 0, Paths.Body] (unsafeParseTerm tms) unconstrained -- fails - , testTerm "f -> let x = (let saved = f in 42) in 1" $ \tms -> + , testTerm "f -> let x = (let saved = f; 42); 1" $ \tms -> testCase ("synthesize/check ("++tms++")") $ synthesizesAndChecks node (unsafeParseTerm tms) (unsafeParseType "forall x. x -> Number") - , testTerm "f -> let x = (b a -> b) 42 f in 1" $ \tms -> + , testTerm "f -> let x = (b a -> b) 42 f; 1" $ \tms -> testCase ("synthesize/check ("++tms++")") $ synthesizesAndChecks node (unsafeParseTerm tms) (unsafeParseType "forall x. x -> Number") , testTerm "f x y -> (x y -> y) f _ + _" $ \tms -> @@ -153,14 +153,14 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck synthesizesAndChecks node (unsafeParseTerm tms) (unsafeParseType "forall a b c. a -> b -> c -> Number") - , testTerm "(id -> let x = id 42; y = id \"hi\" in 43) : (forall a . a -> a) -> Number" $ \tms -> + , testTerm "(id -> let x = id 42; y = id \"hi\"; 43) : (forall a . a -> a) -> Number" $ \tms -> testCase ("higher rank checking: " ++ tms) $ let t = unsafeParseType "(forall a . a -> a) -> Number" tm = unsafeParseTerm tms in synthesizesAndChecks node tm t -- Let generalization not implemented yet; this test fails - --, testCase "let generalization: let id a = a; x = id 42; y = id 'hi' in 23" $ + --, testCase "let generalization: let id a = a; x = id 42; y = id 'hi'; 23" $ -- let -- tm = E.let1' -- [ ("id", E.lam' ["a"] (E.var' "a") `E.ann` T.forall' ["a"] (T.v' "a")), @@ -174,22 +174,22 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck [(_,xt), (_,yt)] <- localsAt node [Paths.Body, Paths.Body, Paths.Fn, Paths.Arg] tm assertEqual "xt unconstrainted" unconstrained (T.generalize xt) assertEqual "yt unconstrainted" unconstrained (T.generalize yt) - , testTerm "let x = _ in _" $ \tms -> + , testTerm "let x = _; _" $ \tms -> testCase ("locals ("++tms++")") $ do let tm = unsafeParseTerm tms [(_,xt)] <- localsAt node [Paths.Body] tm [] <- localsAt node [Paths.Binding 0, Paths.Body] tm assertEqual "xt unconstrainted" unconstrained (T.generalize xt) - , testTerm "let x = _; y = _ in _" $ \tms -> + , testTerm "let x = _; y = _; _" $ \tms -> testCase ("locals ("++tms++")@[Body,Body]") $ do let tm = unsafeParseTerm tms [(_,xt), (_,yt)] <- localsAt node [Paths.Body, Paths.Body] tm assertEqual "xt unconstrained" unconstrained (T.generalize xt) assertEqual "yt unconstrained" unconstrained (T.generalize yt) - , testTerm "let x = _; y = _ in _" $ \tms -> - -- testTerm "let x = 42; y = _ in _" $ \tms -> - -- testTerm "let x = 42; y = 43 in _" $ \tms -> - -- testTerm "let x = 42; y = 43 in 4224" $ \tms -> + , testTerm "let x = _; y = _; _" $ \tms -> + -- testTerm "let x = 42; y = _; _" $ \tms -> + -- testTerm "let x = 42; y = 43; _" $ \tms -> + -- testTerm "let x = 42; y = 43; 4224" $ \tms -> testCase ("locals ("++tms++")@[Body,Binding 0,Body]") $ do let tm = unsafeParseTerm tms [(_,xt)] <- localsAt node [Paths.Body, Paths.Binding 0, Paths.Body] tm diff --git a/shared/tests/Unison/Test/Typechecker/Components.hs b/shared/tests/Unison/Test/Typechecker/Components.hs index 901081513..895ac5b30 100644 --- a/shared/tests/Unison/Test/Typechecker/Components.hs +++ b/shared/tests/Unison/Test/Typechecker/Components.hs @@ -15,21 +15,21 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> tests = [ -- simple case, no minimization done - t "let id x = x; g = id 42; y = id id g in y" - "let id x = x; g = id 42; y = id id g in y" + t "let id x = x; g = id 42; y = id id g; y" + "let id x = x; g = id 42; y = id id g; y" -- check that we get let generalization - , t "let rec id x = x; g = id 42; y = id id g in y" - "let id x = x; g = id 42; y = id id g in y" + , t "let rec id x = x; g = id 42; y = id id g; y" + "let id x = x; g = id 42; y = id id g; y" -- check that we preserve order of components as much as possible - , t "let rec id2 x = x; id1 x = x; id3 x = x in id3" - "let id2 x = x; id1 x = x; id3 x = x in id3" + , t "let rec id2 x = x; id1 x = x; id3 x = x; id3" + "let id2 x = x; id1 x = x; id3 x = x; id3" -- check that we reorder according to dependencies - , t "let rec g = id 42; y = id id g; id x = x in y" - "let id x = x; g = id 42; y = id id g in y" + , t "let rec g = id 42; y = id id g; id x = x; y" + "let id x = x; g = id 42; y = id id g; y" -- insane example, checks for: generalization, reordering, -- preservation of order when possible - , t "let rec g = id 42; y = id id g; ping x = pong x; pong x = id (ping x); id x = x in y" - "let id x = x; g = id 42; y = id id g in (let rec ping x = pong x; pong x = id (ping x) in y)" + , t "let rec g = id 42; y = id id g; ping x = pong x; pong x = id (ping x); id x = x; y" + "let id x = x; g = id 42; y = id id g ; (let rec ping x = pong x; pong x = id (ping x) ; y)" ] t before after = testCase (before ++ " ⟹ " ++ after) $ do (node, _, _) <- node diff --git a/unison-src/base.u b/unison-src/base.u index cd5cbc145..ffc5debdf 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -1,46 +1,48 @@ -identity : ∀ a . a -> a; -identity a = a; +identity : ∀ a . a -> a +identity a = a -Remote.transfer : Node -> Remote Unit; -Remote.transfer node = Remote.at node unit; +then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c +then f1 f2 x = f2 (f1 x) -Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; -Remote.map f = Remote.bind (f `then` Remote.pure); +Remote.transfer : Node -> Remote Unit +Remote.transfer node = Remote.at node unit -then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; -then f1 f2 x = f2 (f1 x); +-- Apply a function to a `Remote` +Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b +Remote.map f = + Remote.bind (f `then` Remote.pure) -Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b; -Optional.map f = Optional.fold None (f `then` Some); +Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b +Optional.map f = Optional.fold None (f `then` Some) -Optional.bind : ∀ a b . (a -> Optional b) -> Optional a -> Optional b; -Optional.bind f = Optional.fold None f; +Optional.bind : ∀ a b . (a -> Optional b) -> Optional a -> Optional b +Optional.bind f = Optional.fold None f -Optional.pure : ∀ a . a -> Optional a; -Optional.pure = Some; +Optional.pure : ∀ a . a -> Optional a +Optional.pure = Some -Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; -Either.map f = Either.fold Left (f `then` Right); +Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c +Either.map f = Either.fold Left (f `then` Right) -Either.pure : ∀ a b . b -> Either a b; -Either.pure = Right; +Either.pure : ∀ a b . b -> Either a b +Either.pure = Right -Either.bind : ∀ a b c . (b -> Either a c) -> Either a b -> Either a c; -Either.bind = Either.fold Left; +Either.bind : ∀ a b c . (b -> Either a c) -> Either a b -> Either a c +Either.bind = Either.fold Left -Either.swap : ∀ a b . Either a b -> Either b a; -Either.swap e = Either.fold Right Left e; +Either.swap : ∀ a b . Either a b -> Either b a +Either.swap e = Either.fold Right Left e -const x y = x; +const x y = x -first : ∀ a b . Pair a b -> a; -first p = Pair.fold const p; +first : ∀ a b . Pair a b -> a +first p = Pair.fold const p -rest : ∀ a b . Pair a b -> b; -rest p = Pair.fold (x y -> y) p; +rest : ∀ a b . Pair a b -> b +rest p = Pair.fold (x y -> y) p -1st = first; -2nd = rest `then` first; -3rd = rest `then` (rest `then` first); -4th = rest `then` (rest `then` (rest `then` first)); -5th = rest `then` (rest `then` (rest `then` (rest `then` first))); +1st = first +2nd = rest `then` first +3rd = rest `then` (rest `then` first) +4th = rest `then` (rest `then` (rest `then` first)) +5th = rest `then` (rest `then` (rest `then` (rest `then` first))) diff --git a/unison-src/extra.u b/unison-src/extra.u index ec3482abf..09b61814c 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -1,3 +1,4 @@ -Index.empty : ∀ k v . Remote (Index k v); -Index.empty = Remote.map Index.unsafeEmpty Remote.here +Index.empty : ∀ k v . Remote (Index k v) +Index.empty = + Remote.map Index.unsafeEmpty Remote.here From 632fc73cf9bce09a9de2db8bf348e1897cd32289 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 21 Aug 2016 17:55:12 -0400 Subject: [PATCH 10/61] ignore layout tokens during type parsing --- shared/src/Unison/TermParser.hs | 2 +- shared/src/Unison/TypeParser.hs | 11 +++++++++-- unison-src/base.u | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index a3b5807d5..a893d9edf 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -148,7 +148,7 @@ possiblyAnnotated p = f <$> p <*> optional ann'' f t Nothing = t ann'' :: Var v => Parser (Type v) -ann'' = token (char ':') *> TypeParser.type_ +ann'' = withoutLayout $ token (char ':') *> TypeParser.type_ --let server = _; blah = _ in _ let_ :: Var v => Parser (Term v) -> Parser (Term v) diff --git a/shared/src/Unison/TypeParser.hs b/shared/src/Unison/TypeParser.hs index 4a922945b..f8d95b1bb 100644 --- a/shared/src/Unison/TypeParser.hs +++ b/shared/src/Unison/TypeParser.hs @@ -3,12 +3,12 @@ module Unison.TypeParser where -import Control.Applicative ((<|>), some) +import Control.Applicative ((<|>), some, many) import Data.Char (isUpper, isLower, isAlpha) import Data.Foldable (asum) import Data.Functor import Data.List (foldl1') -import Unison.Parser +import Unison.Parser hiding (ignored, token) import Unison.Type (Type) import Unison.Var (Var) import qualified Data.Text as Text @@ -17,6 +17,13 @@ import qualified Unison.Type as Type type_ :: Var v => Parser (Type v) type_ = forall type1 <|> type1 +-- we ignore indentation markers { and }, but not semicolon +ignored :: Parser () +ignored = void $ many (whitespace1 <|> haskellLineComment <|> (void $ one (\c -> c == '{' || c == '}'))) + +token :: Parser a -> Parser a +token p = (p <* ignored) + typeLeaf :: Var v => Parser (Type v) typeLeaf = asum [ literal diff --git a/unison-src/base.u b/unison-src/base.u index ffc5debdf..3071790d7 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -7,8 +7,8 @@ then f1 f2 x = f2 (f1 x) Remote.transfer : Node -> Remote Unit Remote.transfer node = Remote.at node unit --- Apply a function to a `Remote` -Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b +Remote.map : + ∀ a b . (a -> b) -> Remote a -> Remote b Remote.map f = Remote.bind (f `then` Remote.pure) From 441e41311aa7226fbef251efa345013bf7c2d4ef Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 21 Aug 2016 17:58:07 -0400 Subject: [PATCH 11/61] move all unison files to unison-src directory --- {node/tests => unison-src}/html.u | 0 {node/tests => unison-src}/index.u | 0 {node/tests => unison-src}/pingpong.u | 0 unison-src/searchengine.u | 11 +++++++++++ 4 files changed, 11 insertions(+) rename {node/tests => unison-src}/html.u (100%) rename {node/tests => unison-src}/index.u (100%) rename {node/tests => unison-src}/pingpong.u (100%) create mode 100644 unison-src/searchengine.u diff --git a/node/tests/html.u b/unison-src/html.u similarity index 100% rename from node/tests/html.u rename to unison-src/html.u diff --git a/node/tests/index.u b/unison-src/index.u similarity index 100% rename from node/tests/index.u rename to unison-src/index.u diff --git a/node/tests/pingpong.u b/unison-src/pingpong.u similarity index 100% rename from node/tests/pingpong.u rename to unison-src/pingpong.u diff --git a/unison-src/searchengine.u b/unison-src/searchengine.u new file mode 100644 index 000000000..f96be19ff --- /dev/null +++ b/unison-src/searchengine.u @@ -0,0 +1,11 @@ +-- going to need tuples at least +-- allow declarations somehow +-- going to need hashing + +let rec + dindex : Vector Node + -> Remote (k -> Remote (Optional v), -- lookup + k -> v -> Remote Unit) -- insert + +in + _ From d95b9ce2868316bc313f3399afa16ff92e93f8bb Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 22 Aug 2016 10:58:28 -0400 Subject: [PATCH 12/61] creating duplicate definitions now merges metadata, and we allow multiple names to alias same hash in Node.allTermsByVarName --- shared/src/Unison/Metadata.hs | 9 +++++++++ shared/src/Unison/Node.hs | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/shared/src/Unison/Metadata.hs b/shared/src/Unison/Metadata.hs index 288af88bf..ac733dd83 100644 --- a/shared/src/Unison/Metadata.hs +++ b/shared/src/Unison/Metadata.hs @@ -2,6 +2,7 @@ {-# LANGUAGE TemplateHaskell #-} module Unison.Metadata where +import Control.Applicative import Data.Aeson import Data.Aeson.TH import Data.Text (Text) @@ -19,6 +20,11 @@ data Metadata v h = description :: Maybe h } deriving (Eq,Ord,Show,Generic) +combine :: Maybe (Metadata v h) -> Metadata v h -> Metadata v h +combine Nothing md2 = md2 +combine (Just (Metadata _ (Names names1) desc1)) (Metadata sort (Names names2) desc2) = + Metadata sort (Names $ names2 ++ names1) (desc2 <|> desc1) + matches :: Var v => Query -> Metadata v h -> Bool matches (Query txt) (Metadata _ (Names ns) _) = any (Text.isPrefixOf txt) (map Var.name ns) @@ -37,6 +43,9 @@ firstName :: Names v -> Maybe v firstName (Names (h:_)) = Just h firstName _ = Nothing +allNames :: Names v -> [v] +allNames (Names ns) = ns + newtype Query = Query Text instance Show Query where diff --git a/shared/src/Unison/Node.hs b/shared/src/Unison/Node.hs index 38c387b22..798a67b2d 100644 --- a/shared/src/Unison/Node.hs +++ b/shared/src/Unison/Node.hs @@ -35,7 +35,6 @@ import qualified Unison.TermEdit as TermEdit import qualified Unison.TermParser as TermParser import qualified Unison.Typechecker as Typechecker import qualified Unison.Typechecker.Components as Components -import qualified Unison.Var as Var -- import Debug.Trace -- | The results of a search. @@ -127,7 +126,8 @@ node eval hash store = Store.writeMetadata store r md -- can't change builtin types, just metadata Reference.Derived h -> do new <- (False <$ Store.readTerm store h) <|> pure True - Store.writeMetadata store r md + md0 <- (Just <$> Store.readMetadata store r) <|> pure Nothing + Store.writeMetadata store r (Metadata.combine md0 md) when new $ do Store.writeTerm store h e Store.annotateTerm store r t @@ -278,7 +278,7 @@ allTermsByVarName ref node = do -- grab all definitions in the node results <- search node Term.blank [] 1000000 (Metadata.Query "") Nothing pure [ (v, ref h) | (h, md) <- references results - , v <- toList $ Metadata.firstName (Metadata.names md) ] + , v <- Metadata.allNames (Metadata.names md) ] allTerms :: (Monad m, Var v) => Node m v h (Type v) (Term v) -> Noted m [(h, Term v)] allTerms node = do From 1d4e3210d58f4553bd84e66c2e01c59978a8e284 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 22 Aug 2016 15:27:13 -0400 Subject: [PATCH 13/61] get rid of hacky layout support, tweak syntax, build up standard library --- .../Unison/Test/SerializationAndHashing.hs | 5 +- shared/src/Unison/Node.hs | 1 - shared/src/Unison/Node/Builtin.hs | 29 ++++-- shared/src/Unison/Parser.hs | 55 ++---------- shared/src/Unison/Parsers.hs | 2 +- shared/src/Unison/TermParser.hs | 33 ++++--- shared/src/Unison/TypeParser.hs | 9 +- shared/tests/Unison/Test/Interpreter.hs | 19 ++-- shared/tests/Unison/Test/Term.hs | 8 +- shared/tests/Unison/Test/TermParser.hs | 40 ++++----- shared/tests/Unison/Test/Typechecker.hs | 24 ++--- .../Unison/Test/Typechecker/Components.hs | 20 ++--- unison-src/base.u | 88 ++++++++++++------- unison-src/extra.u | 4 +- unison-src/index.u | 10 +-- 15 files changed, 173 insertions(+), 174 deletions(-) diff --git a/node/tests/Unison/Test/SerializationAndHashing.hs b/node/tests/Unison/Test/SerializationAndHashing.hs index 9aece1a11..f2ca0c209 100644 --- a/node/tests/Unison/Test/SerializationAndHashing.hs +++ b/node/tests/Unison/Test/SerializationAndHashing.hs @@ -26,10 +26,10 @@ lambda :: Assertion lambda = testTermString "x -> x" letBinding :: Assertion -letBinding = testTermString "let x = 42; x + 1" +letBinding = testTermString "let x = 42; x + 1;;" letRec :: Assertion -letRec = testTermString "let rec x = x + 1; x" +letRec = testTermString "let rec x = x + 1; x;;" vec :: Assertion vec = testTermString "[\"a\", \"b\", \"c\"]" @@ -43,5 +43,4 @@ tests = testGroup "SerializationAndHashing" , testCase "letBinding" letBinding , testCase "letRec" letRec , testCase "vec" vec - ] diff --git a/shared/src/Unison/Node.hs b/shared/src/Unison/Node.hs index 798a67b2d..e038f9188 100644 --- a/shared/src/Unison/Node.hs +++ b/shared/src/Unison/Node.hs @@ -6,7 +6,6 @@ module Unison.Node where -- import Data.Bytes.Serial (Serial) import Control.Monad import Control.Applicative -import Data.Foldable import Data.Aeson.TH import Data.List import Data.Map (Map) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 294592d86..402a57717 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -40,6 +40,8 @@ true = Term.builtin "True" false = Term.builtin "False" pair :: Ord v => Term v pair = Term.builtin "Pair" +pair' :: Ord v => Term v -> Term v -> Term v +pair' t1 t2 = pair `Term.app` t1 `Term.app` (pair `Term.app` t2 `Term.app` unitRef) makeBuiltins :: WHNFEval -> [Builtin] makeBuiltins whnf = @@ -200,8 +202,10 @@ makeBuiltins whnf = in (r, Nothing, unsafeParseType "forall a b . a -> b -> Pair a b", prefix "Pair") , let r = R.Builtin "Pair.fold" op [f,p] = do - Term.Apps' (Term.Builtin' "Pair") [a,b] <- whnf p - whnf (f `Term.apps` [a,b]) + p <- whnf p + case p of + Term.Apps' (Term.Builtin' "Pair") [a,b] -> whnf (f `Term.apps` [a,b]) + p -> fail $ "expected pair, got: " ++ show p op _ = error "Pair.fold unpossible" in (r, Just (I.Primop 2 op), unsafeParseType "forall a b c . (a -> b -> c) -> Pair a b -> c", prefix "fold") @@ -261,15 +265,28 @@ makeBuiltins whnf = pure $ if Vector.null vs then true else false op _ = fail "Vector.empty? unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Boolean", prefix "empty?") + , let r = R.Builtin "Vector.size" + op [v] = do + Term.Vector' vs <- whnf v + pure $ Term.num (fromIntegral $ Vector.length vs) + op _ = fail "Vector.size unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Number", prefix "Vector.size") + , let r = R.Builtin "Vector.reverse" + op [v] = do + Term.Vector' vs <- whnf v + pure $ Term.vector' (Vector.reverse vs) + op _ = fail "Vector.reverse unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Vector a", prefix "Vector.reverse") , let r = R.Builtin "Vector.split" op [v] = do Term.Vector' vs <- whnf v pure $ case Vector.null vs of - True -> pair `Term.apps` [Term.vector [], Term.vector []] + True -> pair' (Term.vector []) (Term.vector []) False -> case Vector.splitAt (Vector.length vs `div` 2) vs of - (x,y) -> pair `Term.app` (Term.vector' x) `Term.app` (Term.vector' y) + (x,y) -> pair' (Term.vector' x) (Term.vector' y) op _ = fail "Vector.split unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Boolean", prefix "empty?") + typ = "forall a. Vector a -> (Vector a, Vector a)" + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Vector.split") , let r = R.Builtin "Vector.fold-left" op [f,z,vec] = whnf vec >>= \vec -> case vec of Term.Vector' vs -> Vector.foldM (\acc a -> whnf (f `Term.apps` [acc, a])) z vs @@ -295,7 +312,7 @@ makeBuiltins whnf = , let r = R.Builtin "Vector.single" op [hd] = pure $ Term.vector (pure hd) op _ = fail "Vector.single unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "forall a. a -> Vector a", prefix "single") + in (r, Just (I.Primop 1 op), unsafeParseType "forall a. a -> Vector a", prefix "Vector.single") ] -- type helpers diff --git a/shared/src/Unison/Parser.hs b/shared/src/Unison/Parser.hs index e494c7820..1e0d914bc 100644 --- a/shared/src/Unison/Parser.hs +++ b/shared/src/Unison/Parser.hs @@ -23,6 +23,9 @@ root p = ignored *> (p <* (optional semicolon <* eof)) semicolon :: Parser () semicolon = void $ token (char ';') +semicolon2 :: Parser () +semicolon2 = semicolon *> semicolon + eof :: Parser () eof = Parser $ \(s,_) -> case s of [] -> Succeed () 0 False @@ -35,7 +38,7 @@ attempt p = Parser $ \s -> case run' p s of run :: Parser a -> String -> Result a -- run p s = run' p (watch "layoutized" $ layoutize s, False) -run p s = run' p (layoutize s, False) +run p s = run' p (s, False) where watch msg a = trace (msg ++ ":\n" ++ a) a unsafeRun :: Parser a -> String -> a @@ -107,8 +110,8 @@ lineErrorUnless s p = commitFail $ Parser $ \input -> case run' p input of parenthesized :: Parser a -> Parser a parenthesized p = lp *> body <* rp where - lp = char '(' *> withoutLayout ignored - body = withoutLayout p + lp = token (char '(') + body = p rp = lineErrorUnless "missing )" $ token (char ')') takeWhile :: String -> (Char -> Bool) -> Parser String @@ -163,52 +166,8 @@ sepBy sep pb = f <$> optional (sepBy1 sep pb) sepBy1 :: Parser a -> Parser b -> Parser [b] sepBy1 sep pb = (:) <$> pb <*> many (sep *> pb) -inLayout :: Parser Bool -inLayout = Parser $ \(_,inLayout) -> Succeed inLayout 0 False - -layoutChar :: Parser () -layoutChar = void $ one (\c -> c == ';' || c == '{' || c == '}') - ignored :: Parser () -ignored = void $ do - inLayout <- inLayout - case inLayout of - True -> many (whitespace1 <|> haskellLineComment) - False -> many (whitespace1 <|> haskellLineComment <|> layoutChar) - -withLayout :: Parser a -> Parser a -withLayout p = Parser $ \(s,_) -> run' p (s,True) - -withoutLayout :: Parser a -> Parser a -withoutLayout p = Parser $ \(s,_) -> run' p (s,False) - -layout :: Parser a -> Parser a -layout p = withLayout $ token (char '{') *> p <* token (char '}') - -layoutize :: String -> String -layoutize s = tweak $ go s [] where - close s = '}' : s - onlysemis line = all (\c -> Char.isSpace c || c == ';') line - tweak [] = [] - tweak s = case span (/= '\n') s of - ([],[]) -> [] - ([],nl:s) -> nl : tweak s - (line,rem) -> - if onlysemis line then (filter (/= ';') line) ++ tweak rem - else line ++ tweak rem - go s stack = case s of - '\n' : tl -> handle tl stack where - indent = length $ Prelude.takeWhile (\c -> c == ' ' || c == '\t') tl - handle :: String -> [Int] -> String - handle tl [] | indent == 0 = ';' : '\n' : go tl stack - | otherwise = '\n' : '{' : go tl (indent : stack) - handle tl stack@(level:levels) = - if indent == level then ';' : '\n' : go tl stack - else if indent > level then '{' : '\n' : go tl (indent:stack) - else close $ go ('\n' : tl) levels - '\r' : tl -> go tl stack - hd : tl -> hd : go tl stack - [] -> replicate (length stack) '}' +ignored = void $ many (whitespace1 <|> haskellLineComment) toEither :: Result a -> Either String a toEither (Fail e _) = Left (intercalate "\n" e) diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index f3edb3380..e13298685 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -87,7 +87,7 @@ termBuiltins = (Var.named *** Term.ref) <$> ( , Builtin "Pair.fold" , Builtin "Pair" , AliasFromModule "Vector" - ["single", "prepend", "map", "fold-left", "concatenate", "append"] ["empty"] + ["single", "prepend", "map", "fold-left", "concatenate", "append", "empty"] [] , AliasFromModule "Text" ["concatenate", "left", "right", "center", "justify"] [] , AliasFromModule "Remote" diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index a893d9edf..43517525d 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -1,4 +1,5 @@ {-# Language OverloadedStrings #-} +{-# Language ScopedTypeVariables #-} module Unison.TermParser where @@ -74,21 +75,26 @@ tupleOrParenthesized rec = -- x := pure 23 -- y = 11 -- pure (f x) -effectBlock :: Var v => Parser (Term v) -effectBlock = (token (string "do") *> withLayout wordyId) >>= go where - go name = layout $ do - bindings <- sepBy1 semicolon $ asum [Right <$> binding, Left <$> action] +effectBlock :: forall v . Var v => Parser (Term v) +effectBlock = (token (string "do") *> wordyId) >>= go where + go name = do + bindings <- some $ asum [Right <$> binding, Left <$> action] <* semicolon + semicolon Just result <- pure $ foldr bind Nothing bindings pure result where + qualifiedPure, qualifiedBind :: Term v qualifiedPure = ABT.var' (Text.pack name `mappend` Text.pack ".pure") qualifiedBind = ABT.var' (Text.pack name `mappend` Text.pack ".bind") + bind :: (Either (Term v) (v, Term v)) -> Maybe (Term v) -> Maybe (Term v) bind = go where go (Right (lhs,rhs)) (Just acc) = Just $ qualifiedBind `Term.apps` [Term.lam lhs acc, rhs] go (Right (_,_)) Nothing = Nothing go (Left action) (Just acc) = Just $ qualifiedBind `Term.apps` [Term.lam (ABT.v' "_") acc, action] go (Left action) _ = Just action + interpretPure :: Term v -> Term v interpretPure = ABT.subst (ABT.v' "pure") qualifiedPure + binding :: Parser (v, Term v) binding = scope "binding" $ do lhs <- ABT.v' . Text.pack <$> token wordyId eff <- token $ (True <$ string ":=") <|> (False <$ string "=") @@ -96,7 +102,8 @@ effectBlock = (token (string "do") *> withLayout wordyId) >>= go where let rhs' = if eff then interpretPure rhs else qualifiedPure `Term.app` rhs pure (lhs, rhs') - action = attempt . scope "action" $ (interpretPure <$> term) + action :: Parser (Term v) + action = scope "action" $ (interpretPure <$> term) text' :: Parser Literal text' = @@ -148,18 +155,18 @@ possiblyAnnotated p = f <$> p <*> optional ann'' f t Nothing = t ann'' :: Var v => Parser (Type v) -ann'' = withoutLayout $ token (char ':') *> TypeParser.type_ +ann'' = token (char ':') *> TypeParser.type_ --let server = _; blah = _ in _ let_ :: Var v => Parser (Term v) -> Parser (Term v) -let_ p = f <$> withLayout (let_ *> optional rec_) <*> (layout bindings' <|> bindings') +let_ p = f <$> (let_ *> optional rec_) <*> bindings' where let_ = token (string "let") rec_ = token (string "rec") $> () - bindings' = withLayout $ do + bindings' = do bs <- lineErrorUnless "error parsing let bindings" (bindings p) - semicolon - body <- lineErrorUnless "parse error in body of let-expression" p + body <- lineErrorUnless "parse error in body of let-expression" term + semicolon2 pure (bs, body) f :: Ord v => Maybe () -> ([(v, Term v)], Term v) -> Term v f Nothing (bindings, body) = Term.let1 bindings body @@ -169,7 +176,7 @@ typedecl :: Var v => Parser (v, Type v) typedecl = (,) <$> prefixVar <*> ann'' bindingEqBody :: Parser (Term v) -> Parser (Term v) -bindingEqBody p = eq *> (layout body <|> body) +bindingEqBody p = eq *> body where eq = token (char '=') body = lineErrorUnless "parse error in body of binding" p @@ -219,12 +226,12 @@ prefixApp p = f <$> some p f [] = error "'some' shouldn't produce an empty list" bindings :: Var v => Parser (Term v) -> Parser [(v, Term v)] -bindings p = withLayout (sepBy1 semicolon binding) where +bindings p = some (binding <* semicolon) where binding = do typ <- optional (typedecl <* semicolon) (name, args) <- ((\arg1 op arg2 -> (op,[arg1,arg2])) <$> prefixVar <*> infixVar <*> prefixVar) <|> ((,) <$> prefixVar <*> many prefixVar) - body <- bindingEqBody p + body <- bindingEqBody term case typ of Nothing -> pure $ mkBinding name args body Just (nameT, typ) diff --git a/shared/src/Unison/TypeParser.hs b/shared/src/Unison/TypeParser.hs index f8d95b1bb..ce95fd26e 100644 --- a/shared/src/Unison/TypeParser.hs +++ b/shared/src/Unison/TypeParser.hs @@ -8,7 +8,7 @@ import Data.Char (isUpper, isLower, isAlpha) import Data.Foldable (asum) import Data.Functor import Data.List (foldl1') -import Unison.Parser hiding (ignored, token) +import Unison.Parser import Unison.Type (Type) import Unison.Var (Var) import qualified Data.Text as Text @@ -17,13 +17,6 @@ import qualified Unison.Type as Type type_ :: Var v => Parser (Type v) type_ = forall type1 <|> type1 --- we ignore indentation markers { and }, but not semicolon -ignored :: Parser () -ignored = void $ many (whitespace1 <|> haskellLineComment <|> (void $ one (\c -> c == '{' || c == '}'))) - -token :: Parser a -> Parser a -token p = (p <* ignored) - typeLeaf :: Var v => Parser (Type v) typeLeaf = asum [ literal diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index b5be135c9..259086cf2 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -14,7 +14,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> [ t "1 + 1" "2" , t "1 + 1 + 1" "3" , t "(x -> x) 42" "42" - , t "let x = 2; y = 3 ; x + y" "5" + , t "let x = 2; y = 3 ; x + y;;" "5" , t "if False 0 1" "1" , t "if True 12 13" "12" , t "1 > 0" "True" @@ -23,12 +23,12 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "1 < 2" "True" , t "1 <= 1" "True" , t "1 >= 1" "True" - , t "let rec fac n = if (n == 0) 1 (n * fac (n - 1)); fac 5" "120" - , t "let rec ping n = if (n >= 10) n (pong (n + 1)); pong n = ping (n + 1); ping 0" + , t "let rec fac n = if (n == 0) 1 (n * fac (n - 1)); fac 5;;" "120" + , t "let rec ping n = if (n >= 10) n (pong (n + 1)); pong n = ping (n + 1); ping 0;;" "10" - , t "let id x = x; g = id 42; p = id \"hi\" ; g" "42" - , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" ; g" "42" - , t "((let id x = x; id) : forall a . a -> a) 42" "42" + , t "let id x = x; g = id 42; p = id \"hi\" ; g;;" "42" + , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" ; g;;" "42" + , t "(let id x = x; id;; : forall a . a -> a) 42" "42" , t "Optional.map ((+) 1) (Some 1)" "Optional.Some (1 + 1)" , t "Either.fold ((+) 1) ((+) 2) (Either.Left 1)" "2" , t "Either.fold ((+) 1) ((+) 2) (Either.Right 1)" "3" @@ -37,6 +37,13 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "const 41 0" "41" , t "1st (1,2,3,4)" "1" , t "2nd (1,2 + 1,3,4)" "3" + , t "Vector.reverse [1,2,3]" "[3,2,1]" + , t "Vector.reverse Vector.empty" "[]" + , t "Vector.fold-right Vector.prepend Vector.empty [1,2,3]" "[1,2,3]" + , t "Vector.fold-balanced Vector.concatenate Vector.empty (Vector.map Vector.single [1,2,3,4,5])" + "[1,2,3,4,5]" + , t "Vector.fold-balanced Vector.concatenate Vector.empty [[1],[2],[3,4],[5]]" + "[1,2,3,4,5]" ] t uneval eval = testCase (uneval ++ " ⟹ " ++ eval) $ do (node, _, builtins) <- node diff --git a/shared/tests/Unison/Test/Term.hs b/shared/tests/Unison/Test/Term.hs index 725aa7883..22a7b7946 100644 --- a/shared/tests/Unison/Test/Term.hs +++ b/shared/tests/Unison/Test/Term.hs @@ -112,11 +112,11 @@ pingpong1 :: TTerm pingpong1 = unsafeParseTerm $ unlines [ "let rec " - , " ping x = pong (x + 1)" - , " pong y = ping (y - 1)" - , " ping 1" + , " ping x = pong (x + 1);" + , " pong y = ping (y - 1);" + , " ping 1;;" ] pingpong2 :: TTerm pingpong2 = - unsafeParseTerm $ "let rec pong1 p = ping1 (p - 1); ping1 q = pong1 (q + 1); ping1 1" + unsafeParseTerm $ "let rec pong1 p = ping1 (p - 1); ping1 q = pong1 (q + 1); ping1 1;;" diff --git a/shared/tests/Unison/Test/TermParser.hs b/shared/tests/Unison/Test/TermParser.hs index 928679eec..2a13fe957 100644 --- a/shared/tests/Unison/Test/TermParser.hs +++ b/shared/tests/Unison/Test/TermParser.hs @@ -63,51 +63,51 @@ tests = testGroup "TermParser" $ (parse <$> shouldPass) ++ (parseFail <$> should , ("1:Int", ann one int) , ("(1:Int)", ann one int) , ("(1:Int) : Int", ann (ann one int) int) - , ("let a = 1; a + 1", let1' [("a", one)] (apps numberplus [a, one])) - , ("let a : Int; a = 1; a + 1", let_a_int1_in_aplus1) - , ("let a: Int; a = 1; a + 1", let_a_int1_in_aplus1) - , ("let a :Int; a = 1; a + 1", let_a_int1_in_aplus1) - , ("let a:Int; a = 1; a + 1", let_a_int1_in_aplus1) + , ("let a = 1; a + 1;;", let1' [("a", one)] (apps numberplus [a, one])) + , ("let a : Int; a = 1; a + 1;;", let_a_int1_in_aplus1) + , ("let a: Int; a = 1; a + 1;;", let_a_int1_in_aplus1) + , ("let a :Int; a = 1; a + 1;;", let_a_int1_in_aplus1) + , ("let a:Int; a = 1; a + 1;;", let_a_int1_in_aplus1) , ("a b -> a + b", lam_ab_aplusb) , ("(a b -> a + b) : Int -> Int -> Int", ann lam_ab_aplusb intintint) , ("a b -> a + b : Int", lam' ["a", "b"] (ann (apps numberplus [a, b]) int)) , ("a -> a", lam' ["a"] a) , ("(a -> a) : forall a . a -> a", ann (lam' ["a"] a) (T.forall' ["a"] (T.arrow a' a'))) - , ("let f = a b -> a + b; f 1 1", f_eq_lamab_in_f11) - , ("let f a b = a + b; f 1 1", f_eq_lamab_in_f11) - , ("let f (+) b = 1 + b; f g 1", let1' [("f", lam' ["+", "b"] (apps plus [one, b]))] (apps f [g,one])) - , ("let a + b = f a b; 1 + 1", let1' [("+", lam' ["a", "b"] fab)] one_plus_one) - , ("let (+) : Int -> Int -> Int; a + b = f a b; 1 + 1", plusintintint_fab_in_1plus1) - , ("let (+) : Int -> Int -> Int; (+) a b = f a b; 1 + 1", plusintintint_fab_in_1plus1) - , ("let (+) : Int -> Int -> Int; (+) a b = f a b; (+) 1 1", plusintintint_fab_in_1plus1) - , ("let f b = b + 1; a = 1; (+) a (f 1)", let1' [("f", lam_b_bplus1), ("a", one)] (apps numberplus [a, apps f [one]])) + , ("let f = a b -> a + b; f 1 1;;", f_eq_lamab_in_f11) + , ("let f a b = a + b; f 1 1;;", f_eq_lamab_in_f11) + , ("let f (+) b = 1 + b; f g 1;;", let1' [("f", lam' ["+", "b"] (apps plus [one, b]))] (apps f [g,one])) + , ("let a + b = f a b; 1 + 1;;", let1' [("+", lam' ["a", "b"] fab)] one_plus_one) + , ("let (+) : Int -> Int -> Int; a + b = f a b; 1 + 1;;", plusintintint_fab_in_1plus1) + , ("let (+) : Int -> Int -> Int; (+) a b = f a b; 1 + 1;;", plusintintint_fab_in_1plus1) + , ("let (+) : Int -> Int -> Int; (+) a b = f a b; (+) 1 1;;", plusintintint_fab_in_1plus1) + , ("let f b = b + 1; a = 1; (+) a (f 1);;", let1' [("f", lam_b_bplus1), ("a", one)] (apps numberplus [a, apps f [one]])) -- from Unison.Test.Term , ("a -> a", lam' ["a"] $ var' "a") -- id , ("x y -> x", lam' ["x", "y"] $ var' "x") -- const - , ("let rec fix = f -> f (fix f); fix", fix) -- fix - , ("let rec fix f = f (fix f); fix", fix) -- fix + , ("let rec fix = f -> f (fix f); fix;;", fix) -- fix + , ("let rec fix f = f (fix f); fix;;", fix) -- fix , ("1 + 2 + 3", num 1 `plus'` num 2 `plus'` num 3) , ("[1, 2, 1 + 1]", vector [num 1, num 2, num 1 `plus'` num 1]) - , ("(id -> let x = id 42; y = id \"hi\"; 43) : (forall a.a) -> Number", lam' ["id"] (let1' + , ("(id -> let x = id 42; y = id \"hi\"; 43;;) : (forall a.a) -> Number", lam' ["id"] (let1' [ ("x", var' "id" `app` num 42), ("y", var' "id" `app` text "hi") ] (num 43)) `ann` (T.forall' ["a"] (T.v' "a") `T.arrow` T.lit T.Number)) , ("#" ++ Text.unpack sampleHash64, derived' sampleHash64) , ("#" ++ Text.unpack sampleHash512, derived' sampleHash512) - , ("(do Remote { pure 42 })", builtin "Remote.pure" `app` num 42) - , ("do Remote { x = 42; pure (x + 1) }", + , ("(do Remote pure 42;;)", builtin "Remote.pure" `app` num 42) + , ("do Remote x = 42; pure (x + 1) ;;", builtin "Remote.bind" `apps` [ lam' ["q"] (builtin "Remote.pure" `app` (var' "q" `plus'` num 1)), builtin "Remote.pure" `app` num 42 ] ) - , ("do Remote { x := pure 42; pure (x + 1) }", + , ("do Remote x := pure 42; pure (x + 1) ;;", builtin "Remote.bind" `apps` [ lam' ["q"] (builtin "Remote.pure" `app` (var' "q" `plus'` num 1)), builtin "Remote.pure" `app` num 42 ] ) - , ("do Remote\n x := pure 42\n y := pure 18\n pure (x + y)", + , ("do Remote\n x := pure 42;\n y := pure 18;\n pure (x + y);;", builtin "Remote.bind" `apps` [ lam' ["x"] (builtin "Remote.bind" `apps` [ lam' ["y"] (builtin "Remote.pure" `app` (var' "x" `plus'` var' "y")), diff --git a/shared/tests/Unison/Test/Typechecker.hs b/shared/tests/Unison/Test/Typechecker.hs index b29544476..b98dcfded 100644 --- a/shared/tests/Unison/Test/Typechecker.hs +++ b/shared/tests/Unison/Test/Typechecker.hs @@ -119,14 +119,14 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck , testCase "synthesize/check (x y -> y)" $ synthesizesAndChecks node (unsafeParseTerm "x y -> y") (unsafeParseType "forall a b. a -> b -> b") - , testCase "synthesize/check (let f = (+); f 1)" $ synthesizesAndChecks node - (unsafeParseTerm "let f = (+); f 1") + , testCase "synthesize/check (let f = (+); f 1;;)" $ synthesizesAndChecks node + (unsafeParseTerm "let f = (+); f 1;;") (T.lit T.Number --> T.lit T.Number) - , testCase "synthesize/check (let blank x = _; blank 1)" $ synthesizesAndChecks node - (unsafeParseTerm "let blank x = _; blank 1") + , testCase "synthesize/check (let blank x = _; blank 1;;)" $ synthesizesAndChecks node + (unsafeParseTerm "let blank x = _; blank 1;;") (forall' ["a"] $ T.v' "a") , testCase "synthesize/check Term.fix" $ synthesizesAndChecks node - (unsafeParseTerm "let rec fix f = f (fix f); fix") + (unsafeParseTerm "let rec fix f = f (fix f); fix;;") (forall' ["a"] $ (T.v' "a" --> T.v' "a") --> T.v' "a") , testCase "synthesize/check Term.pingpong1" $ synthesizesAndChecks node Term.pingpong1 @@ -137,15 +137,15 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck , testTerm "[1, 2, 1 + 1]" $ \tms -> testCase ("synthesize/checkAt "++tms++"@[Index 2]") $ synthesizesAndChecksAt node [Paths.Index 2] (unsafeParseTerm tms) (T.lit T.Number) - , testTerm "let x = _; _" $ \tms -> + , testTerm "let x = _; _;;" $ \tms -> testCase ("synthesize/checkAt ("++tms++")@[Binding 0,Body]") $ synthesizesAndChecksAt node [Paths.Binding 0, Paths.Body] (unsafeParseTerm tms) unconstrained -- fails - , testTerm "f -> let x = (let saved = f; 42); 1" $ \tms -> + , testTerm "f -> let x = (let saved = f; 42;;); 1;;" $ \tms -> testCase ("synthesize/check ("++tms++")") $ synthesizesAndChecks node (unsafeParseTerm tms) (unsafeParseType "forall x. x -> Number") - , testTerm "f -> let x = (b a -> b) 42 f; 1" $ \tms -> + , testTerm "f -> let x = (b a -> b) 42 f; 1;;" $ \tms -> testCase ("synthesize/check ("++tms++")") $ synthesizesAndChecks node (unsafeParseTerm tms) (unsafeParseType "forall x. x -> Number") , testTerm "f x y -> (x y -> y) f _ + _" $ \tms -> @@ -153,7 +153,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck synthesizesAndChecks node (unsafeParseTerm tms) (unsafeParseType "forall a b c. a -> b -> c -> Number") - , testTerm "(id -> let x = id 42; y = id \"hi\"; 43) : (forall a . a -> a) -> Number" $ \tms -> + , testTerm "(id -> let x = id 42; y = id \"hi\"; 43;;) : (forall a . a -> a) -> Number" $ \tms -> testCase ("higher rank checking: " ++ tms) $ let t = unsafeParseType "(forall a . a -> a) -> Number" @@ -174,19 +174,19 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> testGroup "Typecheck [(_,xt), (_,yt)] <- localsAt node [Paths.Body, Paths.Body, Paths.Fn, Paths.Arg] tm assertEqual "xt unconstrainted" unconstrained (T.generalize xt) assertEqual "yt unconstrainted" unconstrained (T.generalize yt) - , testTerm "let x = _; _" $ \tms -> + , testTerm "let x = _; _;;" $ \tms -> testCase ("locals ("++tms++")") $ do let tm = unsafeParseTerm tms [(_,xt)] <- localsAt node [Paths.Body] tm [] <- localsAt node [Paths.Binding 0, Paths.Body] tm assertEqual "xt unconstrainted" unconstrained (T.generalize xt) - , testTerm "let x = _; y = _; _" $ \tms -> + , testTerm "let x = _; y = _; _;;" $ \tms -> testCase ("locals ("++tms++")@[Body,Body]") $ do let tm = unsafeParseTerm tms [(_,xt), (_,yt)] <- localsAt node [Paths.Body, Paths.Body] tm assertEqual "xt unconstrained" unconstrained (T.generalize xt) assertEqual "yt unconstrained" unconstrained (T.generalize yt) - , testTerm "let x = _; y = _; _" $ \tms -> + , testTerm "let x = _; y = _; _;;" $ \tms -> -- testTerm "let x = 42; y = _; _" $ \tms -> -- testTerm "let x = 42; y = 43; _" $ \tms -> -- testTerm "let x = 42; y = 43; 4224" $ \tms -> diff --git a/shared/tests/Unison/Test/Typechecker/Components.hs b/shared/tests/Unison/Test/Typechecker/Components.hs index 895ac5b30..1e2507e8c 100644 --- a/shared/tests/Unison/Test/Typechecker/Components.hs +++ b/shared/tests/Unison/Test/Typechecker/Components.hs @@ -15,21 +15,21 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> tests = [ -- simple case, no minimization done - t "let id x = x; g = id 42; y = id id g; y" - "let id x = x; g = id 42; y = id id g; y" + t "let id x = x; g = id 42; y = id id g; y;;" + "let id x = x; g = id 42; y = id id g; y;;" -- check that we get let generalization - , t "let rec id x = x; g = id 42; y = id id g; y" - "let id x = x; g = id 42; y = id id g; y" + , t "let rec id x = x; g = id 42; y = id id g; y;;" + "let id x = x; g = id 42; y = id id g; y;;" -- check that we preserve order of components as much as possible - , t "let rec id2 x = x; id1 x = x; id3 x = x; id3" - "let id2 x = x; id1 x = x; id3 x = x; id3" + , t "let rec id2 x = x; id1 x = x; id3 x = x; id3;;" + "let id2 x = x; id1 x = x; id3 x = x; id3;;" -- check that we reorder according to dependencies - , t "let rec g = id 42; y = id id g; id x = x; y" - "let id x = x; g = id 42; y = id id g; y" + , t "let rec g = id 42; y = id id g; id x = x; y;;" + "let id x = x; g = id 42; y = id id g; y;;" -- insane example, checks for: generalization, reordering, -- preservation of order when possible - , t "let rec g = id 42; y = id id g; ping x = pong x; pong x = id (ping x); id x = x; y" - "let id x = x; g = id 42; y = id id g ; (let rec ping x = pong x; pong x = id (ping x) ; y)" + , t "let rec g = id 42; y = id id g; ping x = pong x; pong x = id (ping x); id x = x; y;;" + "let id x = x; g = id 42; y = id id g ; (let rec ping x = pong x; pong x = id (ping x) ; y;;);;" ] t before after = testCase (before ++ " ⟹ " ++ after) $ do (node, _, _) <- node diff --git a/unison-src/base.u b/unison-src/base.u index 3071790d7..3570d5b11 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -1,48 +1,68 @@ -identity : ∀ a . a -> a -identity a = a +identity : ∀ a . a -> a; +identity a = a; -then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c -then f1 f2 x = f2 (f1 x) +then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; +then f1 f2 x = f2 (f1 x); -Remote.transfer : Node -> Remote Unit -Remote.transfer node = Remote.at node unit +flip : ∀ a b c . (a -> b -> c) -> b -> a -> c; +flip f b a = f a b; -Remote.map : - ∀ a b . (a -> b) -> Remote a -> Remote b -Remote.map f = - Remote.bind (f `then` Remote.pure) +Remote.transfer : Node -> Remote Unit; +Remote.transfer node = Remote.at node unit; -Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b -Optional.map f = Optional.fold None (f `then` Some) +Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; +Remote.map f = Remote.bind (f `then` Remote.pure); -Optional.bind : ∀ a b . (a -> Optional b) -> Optional a -> Optional b -Optional.bind f = Optional.fold None f +Remote = + ( Remote.pure : ∀ a . a -> Remote a + , Remote.bind : ∀ a b . (a -> Remote b) -> Remote a -> Remote b); -Optional.pure : ∀ a . a -> Optional a -Optional.pure = Some +Vector.fold-right : ∀ a b . (a -> b -> b) -> b -> Vector a -> b; +Vector.fold-right f z vs = Vector.fold-left (flip f) z (Vector.reverse vs); -Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c -Either.map f = Either.fold Left (f `then` Right) +-- todo: figure out why typechecker bombs when try to annotate this +-- Vector.fold-balanced : ∀ a . a -> (a -> a -> a) -> Vector a -> a; +Vector.fold-balanced zero plus vs = + let rec + go zero plus vs = + if (Vector.size vs <= 2) + (Vector.fold-left zero plus vs) + (let p = Vector.split vs; + go zero plus (1st p) `Vector.concatenate` go zero plus (2nd p);;); + go zero plus vs;; + ; -Either.pure : ∀ a b . b -> Either a b -Either.pure = Right +Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b; +Optional.map f = Optional.fold None (f `then` Some); -Either.bind : ∀ a b c . (b -> Either a c) -> Either a b -> Either a c -Either.bind = Either.fold Left +Optional.bind : ∀ a b . (a -> Optional b) -> Optional a -> Optional b; +Optional.bind f = Optional.fold None f; -Either.swap : ∀ a b . Either a b -> Either b a -Either.swap e = Either.fold Right Left e +Optional.pure : ∀ a . a -> Optional a; +Optional.pure = Some; -const x y = x +Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; +Either.map f = Either.fold Left (f `then` Right); -first : ∀ a b . Pair a b -> a -first p = Pair.fold const p +Either.pure : ∀ a b . b -> Either a b; +Either.pure = Right; -rest : ∀ a b . Pair a b -> b -rest p = Pair.fold (x y -> y) p +Either.bind : ∀ a b c . (b -> Either a c) -> Either a b -> Either a c; +Either.bind = Either.fold Left; -1st = first -2nd = rest `then` first -3rd = rest `then` (rest `then` first) -4th = rest `then` (rest `then` (rest `then` first)) -5th = rest `then` (rest `then` (rest `then` (rest `then` first))) +Either.swap : ∀ a b . Either a b -> Either b a; +Either.swap e = Either.fold Right Left e; + +const x y = x; + +first : ∀ a b . Pair a b -> a; +first p = Pair.fold const p; + +rest : ∀ a b . Pair a b -> b; +rest p = Pair.fold (x y -> y) p; + +1st = first; +2nd = rest `then` first; +3rd = rest `then` (rest `then` first); +4th = rest `then` (rest `then` (rest `then` first)); +5th = rest `then` (rest `then` (rest `then` (rest `then` first))); diff --git a/unison-src/extra.u b/unison-src/extra.u index 09b61814c..cc0fbb3c4 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -1,4 +1,4 @@ -Index.empty : ∀ k v . Remote (Index k v) +Index.empty : ∀ k v . Remote (Index k v); Index.empty = - Remote.map Index.unsafeEmpty Remote.here + Remote.map Index.unsafeEmpty Remote.here; diff --git a/unison-src/index.u b/unison-src/index.u index 69895475c..be95ce9f8 100644 --- a/unison-src/index.u +++ b/unison-src/index.u @@ -1,15 +1,13 @@ -- run from unison root directory -- curl -H "Content-Type: text/plain; charset=UTF-8" --data-binary @node/tests/index.u http://localhost:8081/compute/dummynode909 -Remote { +do Remote n1 := Remote.spawn; n2 := Remote.spawn; - ind := Remote { + ind := do Remote Remote.transfer n1; ind := Index.empty; Index.insert "Unison" "Rulez!!!1" ind; - pure ind; - }; + pure ind;; Remote.transfer n2; - Index.lookup "Unison" ind; -} + Index.lookup "Unison" ind;; From eb7a9fb7851ebc1bca0744c55086c3016db9f0e0 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 22 Aug 2016 17:00:52 -0400 Subject: [PATCH 14/61] more standard library - hashing functions, traversal, sequencing, folds --- node/src/Unison/Runtime/ExtraBuiltins.hs | 96 +++++++++++++++++++----- shared/src/Unison/Node/Builtin.hs | 18 +++++ shared/src/Unison/Parsers.hs | 5 +- shared/tests/Unison/Test/Interpreter.hs | 8 ++ unison-src/base.u | 32 ++++++-- 5 files changed, 130 insertions(+), 29 deletions(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 9372348c6..22b8658bf 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -60,15 +60,16 @@ makeAPI blockStore crypto = do pure (Series cp, Series ud) resourcePool <- RP.make 3 10 (Index.loadEncrypted blockStore crypto) Index.flush pure (\whnf -> map (\(r, o, t, m) -> Builtin r o t m) - [ let r = R.Builtin "Index.unsafeEmpty" + [ -- Index + let r = R.Builtin "Index.empty!" op [self] = do ident <- Note.lift nextID Term.Distributed' (Term.Node self) <- whnf self pure . index self . Term.lit . Term.Text . Index.idToText $ ident - op _ = fail "Index.unsafeEmpty unpossible" + op _ = fail "Index.empty! unpossible" type' = unsafeParseType "forall k v. Node -> Index k v" - in (r, Just (I.Primop 1 op), type', prefix "unsafeEmpty") - , let r = R.Builtin "Index.unsafeLookup" + in (r, Just (I.Primop 1 op), type', prefix "Index.empty!") + , let r = R.Builtin "Index.lookup!" op [key, indexToken] = inject g indexToken key where inject g indexToken key = do i <- whnf indexToken @@ -80,26 +81,26 @@ makeAPI blockStore crypto = do flip finally cleanup $ do result <- atomically $ Index.lookup (SAH.hash' k) db case result >>= (pure . SAH.deserializeTermFromBytes . snd) of - Just (Left s) -> fail ("Index.unsafeLookup could not deserialize: " ++ s) + Just (Left s) -> fail ("Index.lookup! could not deserialize: " ++ s) Just (Right t) -> pure $ some t Nothing -> pure none pure val g s k = pure $ Term.ref r `Term.app` s `Term.app` k - op _ = fail "Index.unsafeLookup unpossible" + op _ = fail "Index.lookup! unpossible" type' = unsafeParseType "forall k v. k -> Index k v -> Optional v" - in (r, Just (I.Primop 2 op), type', prefix "unsafeLookup") + in (r, Just (I.Primop 2 op), type', prefix "Index.lookup!") , let r = R.Builtin "Index.lookup" op [key, index] = do Index' node tok <- whnf index pure $ Term.builtin "Remote.map" `Term.apps` [ - Term.builtin "Index.unsafeLookup" `Term.app` key, + Term.builtin "Index.lookup!" `Term.app` key, Term.builtin "Remote.at" `Term.apps` [Term.node node, Term.text tok] ] op _ = fail "Index.lookup unpossible" type' = unsafeParseType "forall k v. k -> Index k v -> Remote (Optional v)" - in (r, Just (I.Primop 2 op), type', prefix "lookup") - , let r = R.Builtin "Index.unsafeInsert" + in (r, Just (I.Primop 2 op), type', prefix "Index.lookup") + , let r = R.Builtin "Index.insert!" op [k, v, index] = inject g k v index where inject g k v index = do k' <- whnf k @@ -114,20 +115,22 @@ makeAPI blockStore crypto = do >>= atomically pure unitRef g k v index = pure $ Term.ref r `Term.app` k `Term.app` v `Term.app` index - op _ = fail "Index.unsafeInsert unpossible" + op _ = fail "Index.insert! unpossible" type' = unsafeParseType "forall k v. k -> v -> Index k v -> Unit" - in (r, Just (I.Primop 3 op), type', prefix "unsafeInsert") + in (r, Just (I.Primop 3 op), type', prefix "Index.insert!") , let r = R.Builtin "Index.insert" op [key, value, index] = do Index' node tok <- whnf index pure $ Term.builtin "Remote.map" `Term.apps` [ - Term.builtin "Index.unsafeInsert" `Term.apps` [key,value], + Term.builtin "Index.insert!" `Term.apps` [key,value], Term.builtin "Remote.at" `Term.apps` [Term.node node, Term.text tok] ] op _ = fail "Index.insert unpossible" type' = unsafeParseType "forall k v. k -> v -> Index k v -> Remote Unit" - in (r, Just (I.Primop 3 op), type', prefix "insert") + in (r, Just (I.Primop 3 op), type', prefix "Index.insert") + + -- Html , let r = R.Builtin "Html.getLinks" op [html] = do html' <- whnf html @@ -153,7 +156,9 @@ makeAPI blockStore crypto = do x -> Term.ref r `Term.app` x op _ = fail "Html.getDescription unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "Link -> Text", prefix "getDescription") - , let r = R.Builtin "Http.unsafeGetURL" + + -- Http + , let r = R.Builtin "Http.getURL!" op [url] = do url <- whnf url case url of @@ -163,11 +168,64 @@ makeAPI blockStore crypto = do Right x -> right $ Term.text x Left x -> left . Term.text . Text.pack $ show x x -> pure $ Term.ref r `Term.app` x - op _ = fail "Http.unsafeGetURL unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "unsafeGetURL") + op _ = fail "Http.getURL! unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "Http.getURL!") , let r = R.Builtin "Http.getURL" op [url] = pure $ Term.builtin "Remote.pure" `Term.app` - (Term.builtin "Http.unsafeGetURL" `Term.app` url) + (Term.builtin "Http.getURL!" `Term.app` url) op _ = fail "Http.getURL unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Remote (Either Text Text)", prefix "getURL") + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Remote (Either Text Text)", prefix "Http.getURL") + + -- Hashing + -- add erase, comparison functions + , let r = R.Builtin "hash!" + op [e] = do + e <- whnf e + pure $ Term.builtin "Hash" `Term.app` (Term.ref $ SAH.hash e) + op _ = fail "hash" + t = "forall a . a -> Hash a" + in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "hash!") + , let r = R.Builtin "Hash.erase" + op [e] = pure e + op _ = fail "hash" + t = "forall a . Hash a -> Hash Unit" + in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "Hash.erase") + , let r = R.Builtin "Hash.equal" + op [h1,h2] = do + Term.App' _ (Term.Ref' r1) <- whnf h1 + Term.App' _ (Term.Ref' r2) <- whnf h2 + pure $ if r1 == r2 then true else false + op _ = fail "Hash.equal" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.equal") + , let r = R.Builtin "Hash.lessThan" + op [h1,h2] = do + Term.App' _ (Term.Ref' r1) <- whnf h1 + Term.App' _ (Term.Ref' r2) <- whnf h2 + pure $ if r1 < r2 then true else false + op _ = fail "Hash.lessThan" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.lessThan") + , let r = R.Builtin "Hash.lessThanOrEqual" + op [h1,h2] = do + Term.App' _ (Term.Ref' r1) <- whnf h1 + Term.App' _ (Term.Ref' r2) <- whnf h2 + pure $ if r1 <= r2 then true else false + op _ = fail "Hash.lessThanOrEqual" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.lessThanOrEqual") + , let r = R.Builtin "Hash.greaterThan" + op [h1,h2] = do + Term.App' _ (Term.Ref' r1) <- whnf h1 + Term.App' _ (Term.Ref' r2) <- whnf h2 + pure $ if r1 > r2 then true else false + op _ = fail "Hash.greaterThan" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.greaterThan") + , let r = R.Builtin "Hash.greaterThanOrEqual" + op [h1,h2] = do + Term.App' _ (Term.Ref' r1) <- whnf h1 + Term.App' _ (Term.Ref' r2) <- whnf h2 + pure $ if r1 >= r2 then true else false + op _ = fail "Hash.greaterThanOrEqual" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.greaterThanOrEqual") ]) + +hashCompareTyp :: Type V +hashCompareTyp = unsafeParseType "∀ a . Hash a -> Hash a -> Boolean" diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 402a57717..2f0852356 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -70,6 +70,12 @@ makeBuiltins whnf = where g (Term.Text' x) (Term.Text' y) = Term.lit (Term.Text (f x y)) g x y = sym `Term.app` x `Term.app` y _ -> error "unpossible" + string2' :: Term V -> (Text -> Text -> Bool) -> I.Primop (N.Noted IO) V + string2' sym f = I.Primop 2 $ \xs -> case xs of + [x,y] -> g <$> whnf x <*> whnf y + where g (Term.Text' x) (Term.Text' y) = if f x y then true else false + g x y = sym `Term.app` x `Term.app` y + _ -> error "unpossible" in map (\(r, o, t, m) -> Builtin r o t m) [ let r = R.Builtin "()" in (r, Nothing, unitT, prefix "()") @@ -188,6 +194,17 @@ makeBuiltins whnf = -- Text , let r = R.Builtin "Text.concatenate" in (r, Just (string2 (Term.ref r) mappend), strOpTyp, prefixes ["concatenate", "Text"]) + , let r = R.Builtin "Text.equal" + in (r, Just (string2' (Term.ref r) (==)), textCompareTyp, prefix "Text.equal") + , let r = R.Builtin "Text.lessThan" + in (r, Just (string2' (Term.ref r) (<)), textCompareTyp, prefix "Text.lessThan") + , let r = R.Builtin "Text.lessThanOrEqual" + in (r, Just (string2' (Term.ref r) (<=)), textCompareTyp, prefix "Text.lessThanOrEqual") + , let r = R.Builtin "Text.greaterThan" + in (r, Just (string2' (Term.ref r) (>)), textCompareTyp, prefix "Text.greaterThan") + , let r = R.Builtin "Text.greaterThanOrEqual" + in (r, Just (string2' (Term.ref r) (>=)), textCompareTyp, prefix "Text.greaterThanOrEqual") + , let r = R.Builtin "Text.left" in (r, Nothing, alignmentT, prefixes ["left", "Text"]) , let r = R.Builtin "Text.right" @@ -322,6 +339,7 @@ numOpTyp :: Type V numOpTyp = unsafeParseType "Number -> Number -> Number" numCompareTyp :: Type V numCompareTyp = unsafeParseType "Number -> Number -> Boolean" +textCompareTyp = unsafeParseType "Text -> Text -> Boolean" strOpTyp :: Type V strOpTyp = unsafeParseType "Text -> Text -> Text" unitT :: Ord v => Type v diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index e13298685..fae7774f6 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -94,9 +94,6 @@ termBuiltins = (Var.named *** Term.ref) <$> ( ["fork", "receive", "receiveAsync", "pure", "bind", "channel", "send", "here", "at", "spawn"] [] , AliasFromModule "Color" ["rgba"] [] , AliasFromModule "Symbol" ["Symbol"] [] - , AliasFromModule "Index" ["lookup", "unsafeLookup", "insert", "unsafeInsert", "unsafeEmpty"] [] - , AliasFromModule "Html" ["getLinks", "getHref", "getDescription"] [] - , AliasFromModule "Http" ["getURL", "unsafeGetURL"] [] ] >>= unpackAliases) where unpackAliases :: Builtin -> [(Text, R.Reference)] @@ -133,5 +130,7 @@ typeBuiltins = (Var.named *** Type.lit) <$> , builtin "Future" , builtin "Remote" , builtin "Node" + -- hashing + , builtin "Hash" ] where builtin t = (t, Type.Ref $ R.Builtin t) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 259086cf2..00e8e5a97 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -44,6 +44,14 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> "[1,2,3,4,5]" , t "Vector.fold-balanced Vector.concatenate Vector.empty [[1],[2],[3,4],[5]]" "[1,2,3,4,5]" + , t "Vector.fold-balanced (+) 0 [1,2,3]" "6" + , t "if (Text.equal \"hi\" \"hi\") 1 2" "1" + , t "if (Text.lessThan \"hi\" \"hiya\") 1 2" "1" + , t "if (Text.lessThanOrEqual \"hi\" \"hiya\") 1 2" "1" + , t "if (Text.greaterThan \"hiya\" \"hi\") 1 2" "1" + , t "if (Text.greaterThanOrEqual \"hiya\" \"hi\") 1 2" "1" + , t "if (Text.greaterThanOrEqual \"hi\" \"hi\") 1 2" "1" + , t "if (Text.lessThanOrEqual \"hi\" \"hi\") 1 2" "1" ] t uneval eval = testCase (uneval ++ " ⟹ " ++ eval) $ do (node, _, builtins) <- node diff --git a/unison-src/base.u b/unison-src/base.u index 3570d5b11..e1df8c821 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -20,18 +20,36 @@ Remote = Vector.fold-right : ∀ a b . (a -> b -> b) -> b -> Vector a -> b; Vector.fold-right f z vs = Vector.fold-left (flip f) z (Vector.reverse vs); --- todo: figure out why typechecker bombs when try to annotate this --- Vector.fold-balanced : ∀ a . a -> (a -> a -> a) -> Vector a -> a; -Vector.fold-balanced zero plus vs = +Vector.fold-balanced : ∀ a . (a -> a -> a) -> a -> Vector a -> a; +Vector.fold-balanced plus zero vs = let rec - go zero plus vs = + go plus zero vs = if (Vector.size vs <= 2) - (Vector.fold-left zero plus vs) + (Vector.fold-left plus zero vs) (let p = Vector.split vs; - go zero plus (1st p) `Vector.concatenate` go zero plus (2nd p);;); - go zero plus vs;; + plus (go plus zero (1st p)) (go plus zero (2nd p));;); + go plus zero vs;; ; +Remote.lift2 : ∀ a b c . (a -> b -> c) -> Remote a -> Remote b -> Remote c; +Remote.lift2 f a b = do Remote + a := a; + b := b; + pure (f a b);; + ; + +Remote.traverse : ∀ a b . (a -> Remote b) -> Vector a -> Remote (Vector b); +Remote.traverse f vs = + Vector.fold-balanced (Remote.lift2 Vector.concatenate) + (Remote.pure Vector.empty) + (Vector.map (f `then` Remote.map Vector.single) vs); + +Remote.sequence : ∀ a . Vector (Remote a) -> Remote (Vector a); +Remote.sequence vs = + Vector.fold-balanced (Remote.lift2 Vector.concatenate) + (Remote.pure Vector.empty) + (Vector.map (Remote.map Vector.single) vs); + Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b; Optional.map f = Optional.fold None (f `then` Some); From 18d3fcd6dcd362cb35db69f87102defe3bed52c3 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 22 Aug 2016 22:29:44 -0400 Subject: [PATCH 15/61] Vector.range, replicate, replicateM --- shared/src/Unison/Node/Builtin.hs | 9 +++++++++ shared/tests/Unison/Test/Interpreter.hs | 3 +++ unison-src/base.u | 8 +++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 2f0852356..067b37c06 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -276,6 +276,15 @@ makeBuiltins whnf = op [] = pure $ Term.vector mempty op _ = fail "Vector.empty unpossible" in (r, Just (I.Primop 0 op), unsafeParseType "forall a. Vector a", prefix "empty") + , let r = R.Builtin "Vector.range" + op [start,stop] = do + Term.Number' start <- whnf start + Term.Number' stop <- whnf stop + let num = Term.num . fromIntegral + pure $ Term.vector' (Vector.fromList . map num $ [floor start .. floor stop-1]) + op _ = fail "Vector.range unpossible" + typ = unsafeParseType "Number -> Number -> Vector Number" + in (r, Just (I.Primop 2 op), typ, prefix "Vector.range") , let r = R.Builtin "Vector.empty?" op [v] = do Term.Vector' vs <- whnf v diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 00e8e5a97..4256a9ec7 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -52,6 +52,9 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "if (Text.greaterThanOrEqual \"hiya\" \"hi\") 1 2" "1" , t "if (Text.greaterThanOrEqual \"hi\" \"hi\") 1 2" "1" , t "if (Text.lessThanOrEqual \"hi\" \"hi\") 1 2" "1" + , t "Vector.range 0 10" "[0,1,2,3,4,5,6,7,8,9]" + , t "Vector.range 0 0" "[]" + , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" ] t uneval eval = testCase (uneval ++ " ⟹ " ++ eval) $ do (node, _, builtins) <- node diff --git a/unison-src/base.u b/unison-src/base.u index e1df8c821..efeb7d721 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -17,6 +17,12 @@ Remote = ( Remote.pure : ∀ a . a -> Remote a , Remote.bind : ∀ a b . (a -> Remote b) -> Remote a -> Remote b); +Vector.replicate : ∀ a . Number -> a -> Vector a; +Vector.replicate n a = Vector.map (const a) (Vector.range 0 n); + +Remote.replicate : ∀ a . Number -> Remote a -> Remote (Vector a); +Remote.replicate n r = Remote.sequence (Vector.replicate n r); + Vector.fold-right : ∀ a b . (a -> b -> b) -> b -> Vector a -> b; Vector.fold-right f z vs = Vector.fold-left (flip f) z (Vector.reverse vs); @@ -27,7 +33,7 @@ Vector.fold-balanced plus zero vs = if (Vector.size vs <= 2) (Vector.fold-left plus zero vs) (let p = Vector.split vs; - plus (go plus zero (1st p)) (go plus zero (2nd p));;); + go plus zero (1st p) `plus` go plus zero (2nd p);;); go plus zero vs;; ; From fc976fcfaa52620e042652e5fe9c07bd3927c8a3 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 23 Aug 2016 09:38:44 -0400 Subject: [PATCH 16/61] updated docs on do block syntax --- shared/src/Unison/TermParser.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index 43517525d..b079b4bf9 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -68,13 +68,13 @@ tupleOrParenthesized rec = unit = Term.builtin "()" -- | --- do Remote { x := pure 23; y := at node2 23; pure 19 } --- do Remote { action1; action2; } --- do Remote { action1; x = 1 + 1; action2; } +-- do Remote x := pure 23; y := at node2 23; pure 19;; +-- do Remote action1; action2;; +-- do Remote action1; x = 1 + 1; action2;; -- do Remote --- x := pure 23 --- y = 11 --- pure (f x) +-- x := pure 23; +-- y = 11; +-- pure (f x);; effectBlock :: forall v . Var v => Parser (Term v) effectBlock = (token (string "do") *> wordyId) >>= go where go name = do From 968733efb3d5fc6011db774502ed8b015abf9dbd Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 23 Aug 2016 12:58:43 -0400 Subject: [PATCH 17/61] some std lib naming tweaks, modified type parser to allow dots in identifiers --- node/src/Unison/Runtime/ExtraBuiltins.hs | 54 ++++++++++++------------ node/src/Worker.hs | 1 - node/tests/Unison/Test/Html.hs | 13 ++++-- node/tests/Unison/Test/NodeUtil.hs | 37 ++++++++++++++-- shared/src/Unison/Parser.hs | 14 ++++++ shared/src/Unison/Parsers.hs | 3 +- shared/src/Unison/TermParser.hs | 28 +++--------- shared/src/Unison/TypeParser.hs | 27 ++++++++---- shared/tests/Unison/Test/TermParser.hs | 14 +++++- shared/tests/Unison/Test/TypeParser.hs | 2 +- unison-src/base.u | 31 ++++++-------- unison-src/extra.u | 2 +- unison-src/pingpong.u | 19 +++++---- 13 files changed, 149 insertions(+), 96 deletions(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 22b8658bf..56f2ee781 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -32,10 +32,10 @@ index :: Remote.Node -> Term.Term V -> Term.Term V index node h = Term.ref (R.Builtin "Index") `Term.apps` [Term.node node, h] linkT :: Ord v => Type v -linkT = Type.ref (R.Builtin "Link") +linkT = Type.ref (R.Builtin "Html.Link") link :: Term.Term V -> Term.Term V -> Term.Term V -link href description = Term.ref (R.Builtin "Link") `Term.app` href `Term.app` description +link href description = Term.ref (R.Builtin "Html.Link") `Term.app` href `Term.app` description linkToTerm :: Html.Link -> Term.Term V linkToTerm (Html.Link href description) = link (Term.lit $ Term.Text href) @@ -46,7 +46,7 @@ pattern Index' node s <- (Term.Text' s) pattern Link' href description <- - Term.App' (Term.App' (Term.Ref' (R.Builtin "Link")) + Term.App' (Term.App' (Term.Ref' (R.Builtin "Html.Link")) (Term.Text' href)) (Term.Text' description) @@ -61,15 +61,15 @@ makeAPI blockStore crypto = do resourcePool <- RP.make 3 10 (Index.loadEncrypted blockStore crypto) Index.flush pure (\whnf -> map (\(r, o, t, m) -> Builtin r o t m) [ -- Index - let r = R.Builtin "Index.empty!" + let r = R.Builtin "Index.empty#" op [self] = do ident <- Note.lift nextID Term.Distributed' (Term.Node self) <- whnf self pure . index self . Term.lit . Term.Text . Index.idToText $ ident - op _ = fail "Index.empty! unpossible" + op _ = fail "Index.empty# unpossible" type' = unsafeParseType "forall k v. Node -> Index k v" - in (r, Just (I.Primop 1 op), type', prefix "Index.empty!") - , let r = R.Builtin "Index.lookup!" + in (r, Just (I.Primop 1 op), type', prefix "Index.empty#") + , let r = R.Builtin "Index.lookup#" op [key, indexToken] = inject g indexToken key where inject g indexToken key = do i <- whnf indexToken @@ -81,26 +81,26 @@ makeAPI blockStore crypto = do flip finally cleanup $ do result <- atomically $ Index.lookup (SAH.hash' k) db case result >>= (pure . SAH.deserializeTermFromBytes . snd) of - Just (Left s) -> fail ("Index.lookup! could not deserialize: " ++ s) + Just (Left s) -> fail ("Index.lookup# could not deserialize: " ++ s) Just (Right t) -> pure $ some t Nothing -> pure none pure val g s k = pure $ Term.ref r `Term.app` s `Term.app` k - op _ = fail "Index.lookup! unpossible" + op _ = fail "Index.lookup# unpossible" type' = unsafeParseType "forall k v. k -> Index k v -> Optional v" - in (r, Just (I.Primop 2 op), type', prefix "Index.lookup!") + in (r, Just (I.Primop 2 op), type', prefix "Index.lookup#") , let r = R.Builtin "Index.lookup" op [key, index] = do Index' node tok <- whnf index pure $ Term.builtin "Remote.map" `Term.apps` [ - Term.builtin "Index.lookup!" `Term.app` key, + Term.builtin "Index.lookup#" `Term.app` key, Term.builtin "Remote.at" `Term.apps` [Term.node node, Term.text tok] ] op _ = fail "Index.lookup unpossible" type' = unsafeParseType "forall k v. k -> Index k v -> Remote (Optional v)" in (r, Just (I.Primop 2 op), type', prefix "Index.lookup") - , let r = R.Builtin "Index.insert!" + , let r = R.Builtin "Index.insert#" op [k, v, index] = inject g k v index where inject g k v index = do k' <- whnf k @@ -115,15 +115,15 @@ makeAPI blockStore crypto = do >>= atomically pure unitRef g k v index = pure $ Term.ref r `Term.app` k `Term.app` v `Term.app` index - op _ = fail "Index.insert! unpossible" + op _ = fail "Index.insert# unpossible" type' = unsafeParseType "forall k v. k -> v -> Index k v -> Unit" - in (r, Just (I.Primop 3 op), type', prefix "Index.insert!") + in (r, Just (I.Primop 3 op), type', prefix "Index.insert#") , let r = R.Builtin "Index.insert" op [key, value, index] = do Index' node tok <- whnf index pure $ Term.builtin "Remote.map" `Term.apps` [ - Term.builtin "Index.insert!" `Term.apps` [key,value], + Term.builtin "Index.insert#" `Term.apps` [key,value], Term.builtin "Remote.at" `Term.apps` [Term.node node, Term.text tok] ] op _ = fail "Index.insert unpossible" @@ -139,7 +139,7 @@ makeAPI blockStore crypto = do $ Html.getLinks h x -> Term.ref r `Term.app` x op _ = fail "Html.getLinks unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Vector Link", prefix "getLinks") + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Vector Html.Link", prefix "Html.getLinks") , let r = R.Builtin "Html.getHref" op [link] = do link' <- whnf link @@ -147,7 +147,7 @@ makeAPI blockStore crypto = do Link' href _ -> Term.lit (Term.Text href) x -> Term.ref r `Term.app` x op _ = fail "Html.getHref unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Link -> Text", prefix "getHref") + in (r, Just (I.Primop 1 op), unsafeParseType "Html.Link -> Text", prefix "Html.getHref") , let r = R.Builtin "Html.getDescription" op [link] = do link' <- whnf link @@ -155,10 +155,10 @@ makeAPI blockStore crypto = do Link' _ d -> Term.lit (Term.Text d) x -> Term.ref r `Term.app` x op _ = fail "Html.getDescription unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Link -> Text", prefix "getDescription") + in (r, Just (I.Primop 1 op), unsafeParseType "Html.Link -> Text", prefix "Html.getDescription") -- Http - , let r = R.Builtin "Http.getURL!" + , let r = R.Builtin "Http.getUrl#" op [url] = do url <- whnf url case url of @@ -168,23 +168,23 @@ makeAPI blockStore crypto = do Right x -> right $ Term.text x Left x -> left . Term.text . Text.pack $ show x x -> pure $ Term.ref r `Term.app` x - op _ = fail "Http.getURL! unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "Http.getURL!") - , let r = R.Builtin "Http.getURL" + op _ = fail "Http.getUrl# unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "Http.getUrl#") + , let r = R.Builtin "Http.getUrl" op [url] = pure $ Term.builtin "Remote.pure" `Term.app` - (Term.builtin "Http.getURL!" `Term.app` url) - op _ = fail "Http.getURL unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Remote (Either Text Text)", prefix "Http.getURL") + (Term.builtin "Http.getUrl#" `Term.app` url) + op _ = fail "Http.getUrl unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Remote (Either Text Text)", prefix "Http.getUrl") -- Hashing -- add erase, comparison functions - , let r = R.Builtin "hash!" + , let r = R.Builtin "hash#" op [e] = do e <- whnf e pure $ Term.builtin "Hash" `Term.app` (Term.ref $ SAH.hash e) op _ = fail "hash" t = "forall a . a -> Hash a" - in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "hash!") + in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "hash#") , let r = R.Builtin "Hash.erase" op [e] = pure e op _ = fail "hash" diff --git a/node/src/Worker.hs b/node/src/Worker.hs index 70b711058..e122f787b 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -15,7 +15,6 @@ import qualified Data.Text as Text import qualified Data.Text.IO as Text.IO import qualified Unison.Config as Config import qualified Unison.Cryptography as C -import qualified Unison.Eval.Interpreter as I import qualified Unison.Node as Node import qualified Unison.Node.BasicNode as BasicNode import qualified Unison.Node.Builtin as Builtin diff --git a/node/tests/Unison/Test/Html.hs b/node/tests/Unison/Test/Html.hs index 581d3efe1..910c36985 100644 --- a/node/tests/Unison/Test/Html.hs +++ b/node/tests/Unison/Test/Html.hs @@ -39,10 +39,10 @@ tests = testGroup "html" ] -- evaluateTerms :: [(Path, e)] -> Noted m [(Path,e,e)], -unisonEvaluate :: TestNode -> Assertion -unisonEvaluate testNode = do +unisonEvaluate :: (TestNode, String -> TermV) -> Assertion +unisonEvaluate (testNode, parse) = do let inputPath = [P.Fn] - getLinksTerm = unsafeParseTerm $ "getLinks \"" ++ testHTML2 ++ "\"" + getLinksTerm = parse $ "Html.getLinks \"" ++ testHTML2 ++ "\"" linkTerm = EB.link (Term.text "link.html") (Term.text "description") getLink = Term.ref (R.Builtin "Html.getHref") `Term.app` linkTerm getDescription = Term.ref (R.Builtin "Html.getDescription") `Term.app` linkTerm @@ -64,8 +64,13 @@ unisonEvaluate testNode = do , "description match ", show (description == desiredDescription) ] -nodeTests :: TestNode -> TestTree +nodeTests :: (TestNode, String -> TermV) -> TestTree nodeTests testNode = testGroup "html" [ testCase "numlinks" numlinks , testCase "unisonEvaluate" (unisonEvaluate testNode) ] + +main :: IO () +main = do + testNode <- makeTestNode + defaultMain (nodeTests testNode) diff --git a/node/tests/Unison/Test/NodeUtil.hs b/node/tests/Unison/Test/NodeUtil.hs index 0dc4e5b76..1a453b758 100644 --- a/node/tests/Unison/Test/NodeUtil.hs +++ b/node/tests/Unison/Test/NodeUtil.hs @@ -2,6 +2,7 @@ module Unison.Test.NodeUtil where +import Control.Applicative import Unison.Hash (Hash) import Unison.Node (Node) import Unison.Reference (Reference) @@ -10,13 +11,20 @@ import Unison.Symbol (Symbol) import Unison.Term (Term) import Unison.Type (Type) import Unison.Var (Var) +import qualified Data.Text as Text +import qualified Data.Text.IO as Text.IO +import qualified System.FilePath as FP import qualified Unison.ABT as ABT import qualified Unison.BlockStore.MemBlockStore as MBS import qualified Unison.Cryptography as C import qualified Unison.Hash as Hash +import qualified Unison.Node as Node import qualified Unison.Node.BasicNode as BasicNode import qualified Unison.Node.Builtin as Builtin +import qualified Unison.Node.FileStore as FS import qualified Unison.Node.UnisonBlockStore as UBS +import qualified Unison.Note as Note +import qualified Unison.Parsers as Parsers import qualified Unison.Reference as R import qualified Unison.Reference as Reference import qualified Unison.Runtime.ExtraBuiltins as EB @@ -25,6 +33,7 @@ import qualified Unison.View as View type DFO = View.DFO type V = Symbol DFO +type TermV = Term V type TestNode = Node IO V R.Reference (Type V) (Term V) hash :: Var v => Term.Term v -> Reference @@ -34,11 +43,31 @@ hash t = Reference.Derived (ABT.hash t) makeRandomAddress :: C.Cryptography k syk sk skp s h c -> IO Address makeRandomAddress crypt = Address <$> C.randomBytes crypt 64 -makeTestNode :: IO TestNode +loadDeclarations :: FilePath -> Node IO V Reference (Type V) (Term V) -> IO () +loadDeclarations path node = do + -- note - when run from repl current directory is root, but when run via stack test, current + -- directory is the shared subdir - so we check both locations + txt <- Text.IO.readFile path <|> Text.IO.readFile (".." `FP.combine` path) + let str = Text.unpack txt + _ <- Note.run $ Node.declare' Term.ref str node + putStrLn $ "loaded file: " ++ path + +makeTestNode :: IO (TestNode, String -> Term V) makeTestNode = do let crypto = C.noop "dummypublickey" + putStrLn "creating block store..." blockStore <- MBS.make' (makeRandomAddress crypto) makeAddress + putStrLn "created block store, creating Node store..." store' <- UBS.make blockStore - keyValueOps <- EB.makeAPI blockStore crypto - let makeBuiltins whnf = concat [Builtin.makeBuiltins whnf, keyValueOps whnf] - BasicNode.make hash store' makeBuiltins + -- store' <- FS.make "blockstore.file" + putStrLn "created Node store..., building extra builtins" + extraBuiltins <- EB.makeAPI blockStore crypto + putStrLn "extra builtins created" + let makeBuiltins whnf = concat [Builtin.makeBuiltins whnf, extraBuiltins whnf] + node <- BasicNode.make hash store' makeBuiltins + putStrLn "Node created" + loadDeclarations "unison-src/base.u" node + loadDeclarations "unison-src/extra.u" node + builtins <- Note.run $ Node.allTermsByVarName Term.ref node + let parse = Parsers.bindBuiltins builtins [] . Parsers.unsafeParseTerm + pure (node, parse) diff --git a/shared/src/Unison/Parser.hs b/shared/src/Unison/Parser.hs index 1e0d914bc..7239530e4 100644 --- a/shared/src/Unison/Parser.hs +++ b/shared/src/Unison/Parser.hs @@ -95,6 +95,20 @@ identifier' charTests stringTests = do guard (all ($ i) stringTests) pure i +-- a wordyId isn't all digits, and isn't all symbols +wordyId :: [String] -> Parser String +wordyId keywords = token $ f <$> sepBy1 dot id + where + dot = char '.' + id = identifier [any (not . Char.isDigit), any Char.isAlphaNum, (`notElem` keywords)] + f segs = intercalate "." segs + +-- a symbolyId is all symbols +symbolyId :: [String] -> Parser String +symbolyId keywords = token $ identifier' + [notReservedChar, not . Char.isSpace, \c -> Char.isSymbol c || Char.isPunctuation c] + [(`notElem` keywords)] + token :: Parser a -> Parser a token p = p <* ignored diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index fae7774f6..45c265246 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -78,6 +78,7 @@ termBuiltins = (Var.named *** Term.ref) <$> ( , Builtin "False" , Builtin "()" , Alias "unit" "()" + , Alias "Unit" "()" , Alias "Some" "Optional.Some" , Alias "None" "Optional.None" , Alias "Left" "Either.Left" @@ -124,7 +125,7 @@ typeBuiltins = (Var.named *** Type.lit) <$> -- kv store , builtin "Index" -- html - , builtin "Link" + , builtin "Html.Link" -- distributed , builtin "Channel" , builtin "Future" diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index b079b4bf9..7495270ca 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -76,7 +76,7 @@ tupleOrParenthesized rec = -- y = 11; -- pure (f x);; effectBlock :: forall v . Var v => Parser (Term v) -effectBlock = (token (string "do") *> wordyId) >>= go where +effectBlock = (token (string "do") *> wordyId keywords) >>= go where go name = do bindings <- some $ asum [Right <$> binding, Left <$> action] <* semicolon semicolon @@ -96,7 +96,7 @@ effectBlock = (token (string "do") *> wordyId) >>= go where interpretPure = ABT.subst (ABT.v' "pure") qualifiedPure binding :: Parser (v, Term v) binding = scope "binding" $ do - lhs <- ABT.v' . Text.pack <$> token wordyId + lhs <- ABT.v' . Text.pack <$> token (wordyId keywords) eff <- token $ (True <$ string ":=") <|> (False <$ string "=") rhs <- term let rhs' = if eff then interpretPure rhs @@ -181,36 +181,22 @@ bindingEqBody p = eq *> body eq = token (char '=') body = lineErrorUnless "parse error in body of binding" p --- a wordyId isn't all digits, and isn't all symbols -wordyId :: Parser String -wordyId = token $ f <$> id <*> optional ((:) <$> dot <*> wordyId) - where - dot = char '.' - id = identifier [any (not.isDigit), any isAlphaNum, (`notElem` keywords)] - f id rest = maybe id (id++) rest - --- a symbolyId is all symbols -symbolyId :: Parser String -symbolyId = token $ identifier' - [notReservedChar, not . isSpace, \c -> isSymbol c || isPunctuation c] - [(`notElem` keywords)] - infixVar :: Var v => Parser v -infixVar = (Var.named . Text.pack) <$> (backticked <|> symbolyId) +infixVar = (Var.named . Text.pack) <$> (backticked <|> symbolyId keywords) where - backticked = char '`' *> wordyId <* token (char '`') + backticked = char '`' *> wordyId keywords <* token (char '`') prefixVar :: Var v => Parser v prefixVar = (Var.named . Text.pack) <$> prefixOp where prefixOp :: Parser String - prefixOp = wordyId <|> (char '(' *> symbolyId <* token (char ')')) -- no whitespace w/in parens + prefixOp = wordyId keywords <|> (char '(' *> symbolyId keywords <* token (char ')')) -- no whitespace w/in parens prefixTerm :: Var v => Parser (Term v) prefixTerm = Term.var <$> prefixVar -keywords :: Set String -keywords = Set.fromList ["do", "let", "rec", "in", "->", ":", "=", "where"] +keywords :: [String] +keywords = ["do", "let", "rec", "in", "->", ":", "=", "where"] lam :: Var v => Parser (Term v) -> Parser (Term v) lam p = Term.lam'' <$> vars <* arrow <*> body diff --git a/shared/src/Unison/TypeParser.hs b/shared/src/Unison/TypeParser.hs index ce95fd26e..6d5e4649c 100644 --- a/shared/src/Unison/TypeParser.hs +++ b/shared/src/Unison/TypeParser.hs @@ -2,7 +2,7 @@ module Unison.TypeParser where - +import Control.Monad import Control.Applicative ((<|>), some, many) import Data.Char (isUpper, isLower, isAlpha) import Data.Foldable (asum) @@ -55,10 +55,19 @@ forall rec = do pure $ Type.forall' (fmap Text.pack vars) t varName :: Parser String -varName = identifier [isLower.head, all isAlpha] +varName = do + name <- wordyId keywords + guard (isLower . head $ name) + pure name typeName :: Parser String -typeName = identifier [isUpper.head] +typeName = do + name <- wordyId keywords + guard (isUpper . head $ name) + pure name + +keywords :: [String] +keywords = ["forall", "∀"] -- qualifiedTypeName :: Parser String -- qualifiedTypeName = f <$> typeName <*> optional more @@ -68,9 +77,9 @@ typeName = identifier [isUpper.head] -- more = (:) <$> char '.' <*> qualifiedTypeName literal :: Var v => Parser (Type v) -literal = - token $ asum [ Type.lit Type.Number <$ string "Number" - , Type.lit Type.Text <$ string "Text" - , Type.lit Type.Vector <$ string "Vector" - , (Type.v' . Text.pack) <$> typeName - ] +literal = scope "literal" . token $ + asum [ Type.lit Type.Number <$ string "Number" + , Type.lit Type.Text <$ string "Text" + , Type.lit Type.Vector <$ string "Vector" + , (Type.v' . Text.pack) <$> typeName + ] diff --git a/shared/tests/Unison/Test/TermParser.hs b/shared/tests/Unison/Test/TermParser.hs index 2a13fe957..dff496bfa 100644 --- a/shared/tests/Unison/Test/TermParser.hs +++ b/shared/tests/Unison/Test/TermParser.hs @@ -16,6 +16,12 @@ import qualified Unison.Type as T -- import Test.Tasty.SmallCheck as SC -- import Test.Tasty.QuickCheck as QC +parse' :: String -> TestTree +parse' s = testCase ("`" ++ s ++ "`") $ + case parseTerm s of + Fail e _ -> assertFailure $ "parse failure " ++ intercalate "\n" e + Succeed a _ _ -> pure () + parse :: (String, Term (Symbol DFO)) -> TestTree parse (s, expected) = testCase ("`" ++ s ++ "`") $ @@ -31,13 +37,17 @@ parseFail (s, reason) = Succeed _ n _ -> n == length s; tests :: TestTree -tests = testGroup "TermParser" $ (parse <$> shouldPass) ++ (parseFail <$> shouldFail) +tests = testGroup "TermParser" $ (parse <$> shouldPass) + ++ (parse' <$> shouldParse) + ++ (parseFail <$> shouldFail) where shouldFail = [ ("+", "operator needs to be enclosed in parens or between arguments") , ("#V-fXHD3-N0E", "invalid base64url") , ("#V-f/XHD3-N0E", "invalid base64url") ] + shouldParse = + [ "do Remote n1 := Remote.spawn; n2 := Remote.spawn; let rec x = 10; Remote.pure 42;;; ;" ] shouldPass = [ ("1", one) , ("[1,1]", vector [one, one]) @@ -88,7 +98,7 @@ tests = testGroup "TermParser" $ (parse <$> shouldPass) ++ (parseFail <$> should , ("let rec fix f = f (fix f); fix;;", fix) -- fix , ("1 + 2 + 3", num 1 `plus'` num 2 `plus'` num 3) , ("[1, 2, 1 + 1]", vector [num 1, num 2, num 1 `plus'` num 1]) - , ("(id -> let x = id 42; y = id \"hi\"; 43;;) : (forall a.a) -> Number", lam' ["id"] (let1' + , ("(id -> let x = id 42; y = id \"hi\"; 43;;) : (forall a . a) -> Number", lam' ["id"] (let1' [ ("x", var' "id" `app` num 42), ("y", var' "id" `app` text "hi") ] (num 43)) `ann` (T.forall' ["a"] (T.v' "a") `T.arrow` T.lit T.Number)) diff --git a/shared/tests/Unison/Test/TypeParser.hs b/shared/tests/Unison/Test/TypeParser.hs index 9a20700a9..629d0c02d 100644 --- a/shared/tests/Unison/Test/TypeParser.hs +++ b/shared/tests/Unison/Test/TypeParser.hs @@ -37,7 +37,7 @@ tests = testGroup "TypeParser" $ fmap parseV strings , ("Vector Foo", T.vectorOf foo) , ("forall a . a -> a", forall_aa) , ("forall a. a -> a", forall_aa) - , ("(forall a.a) -> Number", T.forall' ["a"] (T.v' "a") `T.arrow` T.lit T.Number) + , ("(forall a . a) -> Number", T.forall' ["a"] (T.v' "a") `T.arrow` T.lit T.Number) ] a = T.v' "a" foo = T.v' "Foo" diff --git a/unison-src/base.u b/unison-src/base.u index efeb7d721..c0c01c3bd 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -1,22 +1,32 @@ identity : ∀ a . a -> a; identity a = a; +const x y = x; + then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; then f1 f2 x = f2 (f1 x); flip : ∀ a b c . (a -> b -> c) -> b -> a -> c; flip f b a = f a b; +first : ∀ a b . Pair a b -> a; +first p = Pair.fold const p; + +rest : ∀ a b . Pair a b -> b; +rest p = Pair.fold (x y -> y) p; + +1st = first; +2nd = rest `then` first; +3rd = rest `then` (rest `then` first); +4th = rest `then` (rest `then` (rest `then` first)); +5th = rest `then` (rest `then` (rest `then` (rest `then` first))); + Remote.transfer : Node -> Remote Unit; Remote.transfer node = Remote.at node unit; Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; Remote.map f = Remote.bind (f `then` Remote.pure); -Remote = - ( Remote.pure : ∀ a . a -> Remote a - , Remote.bind : ∀ a b . (a -> Remote b) -> Remote a -> Remote b); - Vector.replicate : ∀ a . Number -> a -> Vector a; Vector.replicate n a = Vector.map (const a) (Vector.range 0 n); @@ -77,16 +87,3 @@ Either.bind = Either.fold Left; Either.swap : ∀ a b . Either a b -> Either b a; Either.swap e = Either.fold Right Left e; -const x y = x; - -first : ∀ a b . Pair a b -> a; -first p = Pair.fold const p; - -rest : ∀ a b . Pair a b -> b; -rest p = Pair.fold (x y -> y) p; - -1st = first; -2nd = rest `then` first; -3rd = rest `then` (rest `then` first); -4th = rest `then` (rest `then` (rest `then` first)); -5th = rest `then` (rest `then` (rest `then` (rest `then` first))); diff --git a/unison-src/extra.u b/unison-src/extra.u index cc0fbb3c4..8cb037e6c 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -1,4 +1,4 @@ Index.empty : ∀ k v . Remote (Index k v); Index.empty = - Remote.map Index.unsafeEmpty Remote.here; + Remote.map Index.empty# Remote.here; diff --git a/unison-src/pingpong.u b/unison-src/pingpong.u index e80a97412..ec16d914b 100644 --- a/unison-src/pingpong.u +++ b/unison-src/pingpong.u @@ -1,14 +1,17 @@ -- run from unison root directory --- curl -H "Content-Type: text/plain; charset=UTF-8" --data-binary @node/tests/pingpong.u http://localhost:8081/compute/dummynode909 +-- curl -H "Content-Type: text/plain; charset=UTF-8" --data-binary @unison-src/pingpong.u http://localhost:8081/compute/root -Remote { +do Remote n1 := Remote.spawn; n2 := Remote.spawn; let rec - ping i = Remote { + ping i = do Remote i := Remote.at n2 (i + 1); - if (i >= 5) (pure i) (pong i); - }; - pong i = Remote { i := Remote.at n1 (i + 1); ping i; } - in ping 0; -} + if (i >= 5) (pure i) (pong i);; + ; + pong i = do Remote + i := Remote.at n1 (i + 1); + ping i;; + ; + ping 0;; + ;; From 09eaa3db1df6ec86a72f736c5d03ff8780b36889 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 23 Aug 2016 16:10:47 -0400 Subject: [PATCH 18/61] Index.{keys,increment} functions, added better error for unresolved symbols, defined various functions in Unison instead of ExtraBuiltins --- node/src/Unison/Runtime/ExtraBuiltins.hs | 69 +++++++++++++----------- node/src/Unison/Runtime/Index.hs | 14 +++-- node/src/Worker.hs | 1 + shared/src/Unison/Typechecker/Context.hs | 8 ++- unison-src/extra.u | 26 ++++++++- unison-src/html.u | 2 +- unison-src/index.u | 6 ++- 7 files changed, 86 insertions(+), 40 deletions(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 56f2ee781..93285a4b0 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -67,8 +67,44 @@ makeAPI blockStore crypto = do Term.Distributed' (Term.Node self) <- whnf self pure . index self . Term.lit . Term.Text . Index.idToText $ ident op _ = fail "Index.empty# unpossible" - type' = unsafeParseType "forall k v. Node -> Index k v" + type' = unsafeParseType "forall k v . Node -> Index k v" in (r, Just (I.Primop 1 op), type', prefix "Index.empty#") + , let r = R.Builtin "Index.keys#" + op [indexToken] = do + Term.Text' h <- whnf indexToken + Note.lift $ do + (db, cleanup) <- RP.acquire resourcePool . Index.textToId $ h + flip finally cleanup $ do + keyBytes <- atomically $ Index.keys db + case traverse SAH.deserializeTermFromBytes keyBytes of + Left err -> fail ("Index.keys# could not deserialize: " ++ err) + Right terms -> pure $ Term.vector terms + op _ = fail "Index.keys# unpossible" + type' = unsafeParseType "forall k . Text -> Vector k" + in (r, Just (I.Primop 1 op), type', prefix "Index.keys#") + , let r = R.Builtin "Index.increment#" + op [key, indexToken] = do + key <- whnf key + Term.Text' h <- whnf indexToken + Note.lift $ do + (db, cleanup) <- RP.acquire resourcePool . Index.textToId $ h + flip finally cleanup $ do + entry <- atomically $ Index.lookupGT (SAH.hash' key) db + case entry of + Nothing -> pure none + Just (_, (keyBytes, _)) -> case SAH.deserializeTermFromBytes keyBytes of + Left err -> fail ("Index.increment# could not deserialize: " ++ err) + Right term -> pure $ some term + op _ = fail "Index.increment# unpossible" + type' = unsafeParseType "forall k . k -> Text -> Optional k" + in (r, Just (I.Primop 2 op), type', prefix "Index.increment#") + , let r = R.Builtin "Index.representation#" + op [index] = do + Index' node tok <- whnf index + pure $ pair' (Term.node node) (Term.text tok) + op _ = fail "Index.representation# unpossible" + type' = unsafeParseType "forall k v . Index k v -> (Node, Text)" + in (r, Just (I.Primop 1 op), type', prefix "Index.representation#") , let r = R.Builtin "Index.lookup#" op [key, indexToken] = inject g indexToken key where inject g indexToken key = do @@ -87,19 +123,8 @@ makeAPI blockStore crypto = do pure val g s k = pure $ Term.ref r `Term.app` s `Term.app` k op _ = fail "Index.lookup# unpossible" - type' = unsafeParseType "forall k v. k -> Index k v -> Optional v" + type' = unsafeParseType "forall k v . k -> Text -> Optional v" in (r, Just (I.Primop 2 op), type', prefix "Index.lookup#") - , let r = R.Builtin "Index.lookup" - op [key, index] = do - Index' node tok <- whnf index - pure $ - Term.builtin "Remote.map" `Term.apps` [ - Term.builtin "Index.lookup#" `Term.app` key, - Term.builtin "Remote.at" `Term.apps` [Term.node node, Term.text tok] - ] - op _ = fail "Index.lookup unpossible" - type' = unsafeParseType "forall k v. k -> Index k v -> Remote (Optional v)" - in (r, Just (I.Primop 2 op), type', prefix "Index.lookup") , let r = R.Builtin "Index.insert#" op [k, v, index] = inject g k v index where inject g k v index = do @@ -116,19 +141,8 @@ makeAPI blockStore crypto = do pure unitRef g k v index = pure $ Term.ref r `Term.app` k `Term.app` v `Term.app` index op _ = fail "Index.insert# unpossible" - type' = unsafeParseType "forall k v. k -> v -> Index k v -> Unit" + type' = unsafeParseType "forall k v . k -> v -> Text -> Unit" in (r, Just (I.Primop 3 op), type', prefix "Index.insert#") - , let r = R.Builtin "Index.insert" - op [key, value, index] = do - Index' node tok <- whnf index - pure $ - Term.builtin "Remote.map" `Term.apps` [ - Term.builtin "Index.insert#" `Term.apps` [key,value], - Term.builtin "Remote.at" `Term.apps` [Term.node node, Term.text tok] - ] - op _ = fail "Index.insert unpossible" - type' = unsafeParseType "forall k v. k -> v -> Index k v -> Remote Unit" - in (r, Just (I.Primop 3 op), type', prefix "Index.insert") -- Html , let r = R.Builtin "Html.getLinks" @@ -170,11 +184,6 @@ makeAPI blockStore crypto = do x -> pure $ Term.ref r `Term.app` x op _ = fail "Http.getUrl# unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "Http.getUrl#") - , let r = R.Builtin "Http.getUrl" - op [url] = pure $ Term.builtin "Remote.pure" `Term.app` - (Term.builtin "Http.getUrl#" `Term.app` url) - op _ = fail "Http.getUrl unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Remote (Either Text Text)", prefix "Http.getUrl") -- Hashing -- add erase, comparison functions diff --git a/node/src/Unison/Runtime/Index.hs b/node/src/Unison/Runtime/Index.hs index d2d0632f0..1d81f4462 100644 --- a/node/src/Unison/Runtime/Index.hs +++ b/node/src/Unison/Runtime/Index.hs @@ -5,7 +5,9 @@ module Unison.Runtime.Index ,Unison.Runtime.Index.insert ,Unison.Runtime.Index.lookupGT ,Unison.Runtime.Index.flush + ,entries ,idToText + ,keys ,load ,loadEncrypted ,textToId @@ -17,12 +19,12 @@ import Data.ByteString (ByteString) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Unison.Cryptography -import Unison.Runtime.Journal as J -import Unison.Runtime.JournaledMap as JM -import qualified Unison.BlockStore as BS import qualified Data.ByteString as B import qualified Data.ByteString.Base64.URL as Base64 import qualified Data.Map as Map +import qualified Unison.BlockStore as BS +import qualified Unison.Runtime.Journal as J +import qualified Unison.Runtime.JournaledMap as JM type KeyHash = ByteString type Key = ByteString @@ -64,6 +66,12 @@ delete kh (Db journaledMap _) = J.updateNowAsyncFlush (JM.Delete kh) journaledMa lookup :: KeyHash -> Db -> STM (Maybe (Key, Value)) lookup kh (Db journaledMap _) = Map.lookup kh <$> J.get journaledMap +entries :: Db -> STM [(Key, Value)] +entries (Db journaledMap _) = Map.elems <$> J.get journaledMap + +keys :: Db -> STM [Key] +keys db = map fst <$> entries db + -- | Find next key in the Db whose key is greater than the provided key lookupGT :: KeyHash -> Db -> STM (Maybe (KeyHash, (Key, Value))) lookupGT kh (Db journaledMap _) = Map.lookupGT kh <$> J.get journaledMap diff --git a/node/src/Worker.hs b/node/src/Worker.hs index e122f787b..2aa61b9f3 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -62,6 +62,7 @@ main = do -- allprimops = Map.fromList [ (r, op) | Builtin.Builtin r (Just op) _ _ <- allbuiltins ] typecheck e = do bindings <- Note.run $ Node.allTermsByVarName Term.ref backend + L.debug logger $ "known symbols: " ++ show (map fst bindings) let e' = Parsers.bindBuiltins bindings [] e Note.unnote (Node.typeAt backend e' []) >>= \t -> case t of Left note -> pure $ Left (show note) diff --git a/shared/src/Unison/Typechecker/Context.hs b/shared/src/Unison/Typechecker/Context.hs index 25320cc85..7ed9ed168 100644 --- a/shared/src/Unison/Typechecker/Context.hs +++ b/shared/src/Unison/Typechecker/Context.hs @@ -634,15 +634,19 @@ synthesizeClosed synthRef term = do synthesizeClosedAnnotated term synthesizeClosed' :: Var v => Term v -> M v (Type v) -synthesizeClosed' term = case runM (synthesize term) env0 of +synthesizeClosed' term | Set.null (ABT.freeVars term) = case runM (synthesize term) env0 of Left err -> M $ \_ -> Left err Right (t,env) -> pure $ generalizeExistentials (ctx env) t +synthesizeClosed' term = + fail $ "cannot synthesize term with free variables: " ++ show (map Var.name $ Set.toList (ABT.freeVars term)) synthesizeClosedAnnotated :: (Monad f, Var v) => Term v -> Noted f (Type v) -synthesizeClosedAnnotated term = do +synthesizeClosedAnnotated term | Set.null (ABT.freeVars term) = do Note.fromEither $ runM (synthesize term) env0 >>= \(t,env) -> -- we generalize over any remaining unsolved existentials pure $ generalizeExistentials (ctx env) t +synthesizeClosedAnnotated term = + fail $ "cannot synthesize term with free variables: " ++ show (map Var.name $ Set.toList (ABT.freeVars term)) -- boring instances instance Applicative (M v) where diff --git a/unison-src/extra.u b/unison-src/extra.u index 8cb037e6c..44bfcdece 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -1,4 +1,26 @@ Index.empty : ∀ k v . Remote (Index k v); -Index.empty = - Remote.map Index.empty# Remote.here; +Index.empty = Remote.map Index.empty# Remote.here; + +Index.keys : ∀ k v . Index k v -> Remote (Vector k); +Index.keys = Index.fromUnsafe Index.keys#; + +Index.increment : ∀ k v . k -> Index k v -> Remote (Optional k); +Index.increment k = Index.fromUnsafe (Index.increment# k); + +Index.lookup : ∀ k v . k -> Index k v -> Remote (Optional v); +Index.lookup k = Index.fromUnsafe (Index.lookup# k); + +Index.insert : ∀ k v . k -> v -> Index k v -> Remote Unit; +Index.insert k v = Index.fromUnsafe (Index.insert# k v); + +Index.fromUnsafe : ∀ k v r . (Text -> r) -> Index k v -> Remote r; +Index.fromUnsafe f ind = let + p = Index.representation# ind; + Remote.map f (Remote.at (1st p) (2nd p));; +; + +-- todo: Index.delete + +Http.getUrl : Text -> Remote (Either Text Text); +Http.getUrl url = Remote.map Http.getUrl# (Remote.pure url); diff --git a/unison-src/html.u b/unison-src/html.u index 41f96df60..690350aba 100644 --- a/unison-src/html.u +++ b/unison-src/html.u @@ -1,4 +1,4 @@ -- run from unison root directory -- curl -H "Content-Type: text/plain; charset=UTF-8" --data-binary @node/tests/html.u http://localhost:8081/compute/dummynode909 -Http.getURL "http://unisonweb.org" +Http.getUrl "http://unisonweb.org" diff --git a/unison-src/index.u b/unison-src/index.u index be95ce9f8..e6dc933db 100644 --- a/unison-src/index.u +++ b/unison-src/index.u @@ -7,7 +7,9 @@ do Remote ind := do Remote Remote.transfer n1; ind := Index.empty; - Index.insert "Unison" "Rulez!!!1" ind; + Index.insert "Unison" "Rulez!!!1" ind; + Index.insert "Unison1" "Rulez!!!1" ind; pure ind;; + ; Remote.transfer n2; - Index.lookup "Unison" ind;; + Index.keys ind;; From 89ec20d2a71f9fa45a8405a337cfd737092e9352 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 23 Aug 2016 17:27:31 -0400 Subject: [PATCH 19/61] Added Vector.sort and some additional builtins --- node/src/Unison/Runtime/ExtraBuiltins.hs | 2 ++ shared/src/Unison/Node/Builtin.hs | 21 +++++++++++++++++++++ shared/src/Unison/Parsers.hs | 1 + shared/tests/Unison/Test/Interpreter.hs | 1 + 4 files changed, 25 insertions(+) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 93285a4b0..60b5cd484 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -234,6 +234,8 @@ makeAPI blockStore crypto = do pure $ if r1 >= r2 then true else false op _ = fail "Hash.greaterThanOrEqual" in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.greaterThanOrEqual") + , let r = R.Builtin "Hash.Order" + in (r, Nothing, unsafeParseType "∀ a . Order (Hash a)", prefix "Hash.Order") ]) hashCompareTyp :: Type V diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 067b37c06..2dd0029da 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} module Unison.Node.Builtin where +import Data.List import Data.Text (Text) import Unison.Metadata (Metadata(..)) import Unison.Parsers (unsafeParseType) @@ -120,6 +121,8 @@ makeBuiltins whnf = in (r, Just (numericCompare (Term.ref r) (<=)), numCompareTyp, opl 3 "<=") , let r = R.Builtin "Number.equal" in (r, Just (numericCompare (Term.ref r) (==)), numCompareTyp, opl 3 "==") + , let r = R.Builtin "Number.Order" + in (r, Nothing, unsafeParseType "Order Number", prefix "Number.Order") -- Remote , let r = R.Builtin "Remote.at" @@ -204,6 +207,8 @@ makeBuiltins whnf = in (r, Just (string2' (Term.ref r) (>)), textCompareTyp, prefix "Text.greaterThan") , let r = R.Builtin "Text.greaterThanOrEqual" in (r, Just (string2' (Term.ref r) (>=)), textCompareTyp, prefix "Text.greaterThanOrEqual") + , let r = R.Builtin "Text.Order" + in (r, Nothing, unsafeParseType "Order Text", prefix "Text.Order") , let r = R.Builtin "Text.left" in (r, Nothing, alignmentT, prefixes ["left", "Text"]) @@ -291,6 +296,22 @@ makeBuiltins whnf = pure $ if Vector.null vs then true else false op _ = fail "Vector.empty? unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Boolean", prefix "empty?") + , let r = R.Builtin "Vector.sort" + op [ord,f,v] = do + Term.Vector' vs <- whnf v + ks <- traverse (whnf . Term.app f) vs + Term.Builtin' ord <- whnf ord + let + sortableVs = Vector.zip ks vs + f' (Term.Text' x, _) (Term.Text' y, _) = x `compare` y + f' (Term.Number' x, _) (Term.Number' y, _) = x `compare` y + f' (Term.App' (Term.Builtin' "Hash") (Term.Ref' r1), _) + (Term.App' (Term.Builtin' "Hash") (Term.Ref' r2), _) = r1 `compare` r2 + f' x y = error $ "don't know how to compare: " ++ show x ++ " " ++ show y + pure . Term.vector . fmap snd $ sortBy f' (Vector.toList sortableVs) + op _ = fail "Vector.sort unpossible" + typ = "∀ a k . Order k -> (a -> k) -> Vector a -> Vector a" + in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "Vector.sort") , let r = R.Builtin "Vector.size" op [v] = do Term.Vector' vs <- whnf v diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index 45c265246..34dead69d 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -119,6 +119,7 @@ typeBuiltins = (Var.named *** Type.lit) <$> , builtin "Pair" -- ??? , builtin "Symbol" + , builtin "Order" , builtin "Alignment" , builtin "Color" , builtin "Fixity" diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 4256a9ec7..db6bc28f7 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -55,6 +55,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.range 0 10" "[0,1,2,3,4,5,6,7,8,9]" , t "Vector.range 0 0" "[]" , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" + , t "Vector.sort Number.Order identity [5,2,1,3,4]" "[1,2,3,4,5]" ] t uneval eval = testCase (uneval ++ " ⟹ " ++ eval) $ do (node, _, builtins) <- node From 33f83dcfeb543196a7042923cb174a83ad1f5d24 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 23 Aug 2016 21:27:59 -0400 Subject: [PATCH 20/61] Vector.at/take/drop --- shared/src/Unison/Node/Builtin.hs | 26 +++++++++++++++++++++++++ shared/tests/Unison/Test/Interpreter.hs | 18 ++++++++++------- unison-src/base.u | 3 +++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 2dd0029da..225d13ef4 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -334,6 +334,32 @@ makeBuiltins whnf = op _ = fail "Vector.split unpossible" typ = "forall a. Vector a -> (Vector a, Vector a)" in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Vector.split") + , let r = R.Builtin "Vector.at" + op [n,vec] = do + Term.Number' n <- whnf n + Term.Vector' vs <- whnf vec + pure $ case vs Vector.!? (floor n) of + Nothing -> none + Just t -> some t + op _ = fail "Vector.at unpossible" + typ = "forall a . Number -> Vector a -> Optional a" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Vector.at") + , let r = R.Builtin "Vector.take" + op [n,vec] = do + Term.Number' n <- whnf n + Term.Vector' vs <- whnf vec + pure $ Term.vector' (Vector.take (floor n) vs) + op _ = fail "Vector.take unpossible" + typ = "forall a . Number -> Vector a -> Vector a" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Vector.take") + , let r = R.Builtin "Vector.drop" + op [n,vec] = do + Term.Number' n <- whnf n + Term.Vector' vs <- whnf vec + pure $ Term.vector' (Vector.drop (floor n) vs) + op _ = fail "Vector.drop unpossible" + typ = "forall a . Number -> Vector a -> Vector a" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Vector.drop") , let r = R.Builtin "Vector.fold-left" op [f,z,vec] = whnf vec >>= \vec -> case vec of Term.Vector' vs -> Vector.foldM (\acc a -> whnf (f `Term.apps` [acc, a])) z vs diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index db6bc28f7..f2e7f7ea5 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -37,6 +37,13 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "const 41 0" "41" , t "1st (1,2,3,4)" "1" , t "2nd (1,2 + 1,3,4)" "3" + , t "if (Text.equal \"hi\" \"hi\") 1 2" "1" + , t "if (Text.lessThan \"hi\" \"hiya\") 1 2" "1" + , t "if (Text.lessThanOrEqual \"hi\" \"hiya\") 1 2" "1" + , t "if (Text.greaterThan \"hiya\" \"hi\") 1 2" "1" + , t "if (Text.greaterThanOrEqual \"hiya\" \"hi\") 1 2" "1" + , t "if (Text.greaterThanOrEqual \"hi\" \"hi\") 1 2" "1" + , t "if (Text.lessThanOrEqual \"hi\" \"hi\") 1 2" "1" , t "Vector.reverse [1,2,3]" "[3,2,1]" , t "Vector.reverse Vector.empty" "[]" , t "Vector.fold-right Vector.prepend Vector.empty [1,2,3]" "[1,2,3]" @@ -45,17 +52,14 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.fold-balanced Vector.concatenate Vector.empty [[1],[2],[3,4],[5]]" "[1,2,3,4,5]" , t "Vector.fold-balanced (+) 0 [1,2,3]" "6" - , t "if (Text.equal \"hi\" \"hi\") 1 2" "1" - , t "if (Text.lessThan \"hi\" \"hiya\") 1 2" "1" - , t "if (Text.lessThanOrEqual \"hi\" \"hiya\") 1 2" "1" - , t "if (Text.greaterThan \"hiya\" \"hi\") 1 2" "1" - , t "if (Text.greaterThanOrEqual \"hiya\" \"hi\") 1 2" "1" - , t "if (Text.greaterThanOrEqual \"hi\" \"hi\") 1 2" "1" - , t "if (Text.lessThanOrEqual \"hi\" \"hi\") 1 2" "1" , t "Vector.range 0 10" "[0,1,2,3,4,5,6,7,8,9]" , t "Vector.range 0 0" "[]" , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" , t "Vector.sort Number.Order identity [5,2,1,3,4]" "[1,2,3,4,5]" + , t "Optional.getOr 96 (Vector.at 1 [0,1,2,3,4])" "1" + , t "Vector.take 0 [1,2,3]" "[]" + , t "Vector.take 2 [1,2,3]" "[1,2]" + , t "Vector.drop 2 [1,2,3]" "[3]" ] t uneval eval = testCase (uneval ++ " ⟹ " ++ eval) $ do (node, _, builtins) <- node diff --git a/unison-src/base.u b/unison-src/base.u index c0c01c3bd..2961ecc97 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -75,6 +75,9 @@ Optional.bind f = Optional.fold None f; Optional.pure : ∀ a . a -> Optional a; Optional.pure = Some; +Optional.getOr : ∀ a . a -> Optional a -> a; +Optional.getOr a = Optional.fold a identity; + Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; Either.map f = Either.fold Left (f `then` Right); From 1331400b1d2c4ce25513f1ec2bd09697627624cc Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 23 Aug 2016 22:02:08 -0400 Subject: [PATCH 21/61] Vector.zip/bind/pure --- shared/src/Unison/Node/Builtin.hs | 31 ++++++++++++++++--------- shared/tests/Unison/Test/Interpreter.hs | 1 + unison-src/base.u | 5 ++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 225d13ef4..dace8bf30 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -267,7 +267,7 @@ makeBuiltins whnf = Term.Vector' init -> Term.vector' (Vector.snoc init last) init -> Term.ref r `Term.app` last `Term.app` init op _ = fail "Vector.append unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a. a -> Vector a -> Vector a", prefix "append") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a . a -> Vector a -> Vector a", prefix "append") , let r = R.Builtin "Vector.concatenate" op [a,b] = do ar <- whnf a @@ -276,11 +276,11 @@ makeBuiltins whnf = (Term.Vector' a, Term.Vector' b) -> Term.vector' (a `mappend` b) (a,b) -> Term.ref r `Term.app` a `Term.app` b op _ = fail "Vector.concatenate unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a. Vector a -> Vector a -> Vector a", prefix "concatenate") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a . Vector a -> Vector a -> Vector a", prefix "concatenate") , let r = R.Builtin "Vector.empty" op [] = pure $ Term.vector mempty op _ = fail "Vector.empty unpossible" - in (r, Just (I.Primop 0 op), unsafeParseType "forall a. Vector a", prefix "empty") + in (r, Just (I.Primop 0 op), unsafeParseType "forall a . Vector a", prefix "empty") , let r = R.Builtin "Vector.range" op [start,stop] = do Term.Number' start <- whnf start @@ -295,7 +295,15 @@ makeBuiltins whnf = Term.Vector' vs <- whnf v pure $ if Vector.null vs then true else false op _ = fail "Vector.empty? unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Boolean", prefix "empty?") + in (r, Just (I.Primop 1 op), unsafeParseType "forall a . Vector a -> Boolean", prefix "empty?") + , let r = R.Builtin "Vector.zip" + op [v,v2] = do + Term.Vector' vs <- whnf v + Term.Vector' vs2 <- whnf v2 + pure $ Term.vector' (Vector.zipWith pair' vs vs2) + op _ = fail "Vector.zip unpossible" + typ = "∀ a b . Vector a -> Vector b -> Vector (a,b)" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Vector.zip") , let r = R.Builtin "Vector.sort" op [ord,f,v] = do Term.Vector' vs <- whnf v @@ -317,13 +325,13 @@ makeBuiltins whnf = Term.Vector' vs <- whnf v pure $ Term.num (fromIntegral $ Vector.length vs) op _ = fail "Vector.size unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Number", prefix "Vector.size") + in (r, Just (I.Primop 1 op), unsafeParseType "forall a . Vector a -> Number", prefix "Vector.size") , let r = R.Builtin "Vector.reverse" op [v] = do Term.Vector' vs <- whnf v pure $ Term.vector' (Vector.reverse vs) op _ = fail "Vector.reverse unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "forall a. Vector a -> Vector a", prefix "Vector.reverse") + in (r, Just (I.Primop 1 op), unsafeParseType "forall a . Vector a -> Vector a", prefix "Vector.reverse") , let r = R.Builtin "Vector.split" op [v] = do Term.Vector' vs <- whnf v @@ -332,7 +340,7 @@ makeBuiltins whnf = False -> case Vector.splitAt (Vector.length vs `div` 2) vs of (x,y) -> pair' (Term.vector' x) (Term.vector' y) op _ = fail "Vector.split unpossible" - typ = "forall a. Vector a -> (Vector a, Vector a)" + typ = "forall a . Vector a -> (Vector a, Vector a)" in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Vector.split") , let r = R.Builtin "Vector.at" op [n,vec] = do @@ -365,7 +373,8 @@ makeBuiltins whnf = Term.Vector' vs -> Vector.foldM (\acc a -> whnf (f `Term.apps` [acc, a])) z vs _ -> pure $ Term.ref r `Term.app` vec op _ = fail "Vector.fold-left unpossible" - in (r, Just (I.Primop 3 op), unsafeParseType "forall a b. (b -> a -> b) -> b -> Vector a -> b", prefix "fold-left") + typ = "forall a b . (b -> a -> b) -> b -> Vector a -> b" + in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "fold-left") , let r = R.Builtin "Vector.map" op [f,vec] = do vecr <- whnf vec @@ -373,7 +382,7 @@ makeBuiltins whnf = Term.Vector' vs -> Term.vector' (fmap (Term.app f) vs) _ -> Term.ref r `Term.app` vecr op _ = fail "Vector.map unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a b. (a -> b) -> Vector a -> Vector b", prefix "map") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a b . (a -> b) -> Vector a -> Vector b", prefix "Vector.map") , let r = R.Builtin "Vector.prepend" op [hd,tl] = do tlr <- whnf tl @@ -381,11 +390,11 @@ makeBuiltins whnf = Term.Vector' tl -> Term.vector' (Vector.cons hd tl) tl -> Term.ref r `Term.app` hd `Term.app` tl op _ = fail "Vector.prepend unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a. a -> Vector a -> Vector a", prefix "prepend") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a . a -> Vector a -> Vector a", prefix "prepend") , let r = R.Builtin "Vector.single" op [hd] = pure $ Term.vector (pure hd) op _ = fail "Vector.single unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "forall a. a -> Vector a", prefix "Vector.single") + in (r, Just (I.Primop 1 op), unsafeParseType "forall a . a -> Vector a", prefix "Vector.single") ] -- type helpers diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index f2e7f7ea5..72252640a 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -56,6 +56,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.range 0 0" "[]" , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" , t "Vector.sort Number.Order identity [5,2,1,3,4]" "[1,2,3,4,5]" + , t "Vector.bind 2nd (Vector.zip [1,2,3] [[1],[2],[3]])" "[1,2,3]" , t "Optional.getOr 96 (Vector.at 1 [0,1,2,3,4])" "1" , t "Vector.take 0 [1,2,3]" "[]" , t "Vector.take 2 [1,2,3]" "[1,2]" diff --git a/unison-src/base.u b/unison-src/base.u index 2961ecc97..bc7e7b274 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -33,6 +33,11 @@ Vector.replicate n a = Vector.map (const a) (Vector.range 0 n); Remote.replicate : ∀ a . Number -> Remote a -> Remote (Vector a); Remote.replicate n r = Remote.sequence (Vector.replicate n r); +Vector.bind : ∀ a b . (a -> Vector b) -> Vector a -> Vector b; +Vector.bind f v = Vector.fold-balanced Vector.concatenate Vector.empty (Vector.map f v); + +Vector.pure = Vector.single; + Vector.fold-right : ∀ a b . (a -> b -> b) -> b -> Vector a -> b; Vector.fold-right f z vs = Vector.fold-left (flip f) z (Vector.reverse vs); From a886c063b8058e38ec6b7b8179851ed9e1efed72 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 24 Aug 2016 17:16:26 -0400 Subject: [PATCH 22/61] lots of std lib improvements / fixes to support DIndex --- node/src/Unison/Runtime/ExtraBuiltins.hs | 59 +++++++++++--- node/src/Unison/Runtime/Remote.hs | 2 +- node/src/Worker.hs | 1 + shared/src/Unison/Node/Builtin.hs | 49 +++++++++++- shared/src/Unison/Parsers.hs | 1 + shared/src/Unison/Typechecker/Context.hs | 26 +++---- shared/tests/Unison/Test/Interpreter.hs | 14 ++++ unison-src/base.u | 80 +++++++++++++++---- unison-src/dindex.u | 97 ++++++++++++++++++++++++ unison-src/extra.u | 11 ++- 10 files changed, 296 insertions(+), 44 deletions(-) create mode 100644 unison-src/dindex.u diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 60b5cd484..a03369c0a 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -13,6 +13,7 @@ import Unison.Type (Type) import qualified Data.Text as Text import qualified Data.Vector as Vector import qualified Unison.Cryptography as C +import qualified Unison.Hash as Hash import qualified Unison.Eval.Interpreter as I import qualified Unison.Note as Note import qualified Unison.Reference as R @@ -82,6 +83,21 @@ makeAPI blockStore crypto = do op _ = fail "Index.keys# unpossible" type' = unsafeParseType "forall k . Text -> Vector k" in (r, Just (I.Primop 1 op), type', prefix "Index.keys#") + , let r = R.Builtin "Index.1st-key#" + op [indexToken] = do + Term.Text' h <- whnf indexToken + Note.lift $ do + (db, cleanup) <- RP.acquire resourcePool . Index.textToId $ h + flip finally cleanup $ do + keyBytes <- atomically $ Index.keys db + case keyBytes of + [] -> pure none + (keyBytes:_) -> case SAH.deserializeTermFromBytes keyBytes of + Left err -> fail ("Index.1st-key# could not deserialize: " ++ err) + Right terms -> pure $ some terms + op _ = fail "Index.1st-key# unpossible" + type' = unsafeParseType "forall k . Text -> Optional k" + in (r, Just (I.Primop 1 op), type', prefix "Index.1st-key#") , let r = R.Builtin "Index.increment#" op [key, indexToken] = do key <- whnf key @@ -125,6 +141,17 @@ makeAPI blockStore crypto = do op _ = fail "Index.lookup# unpossible" type' = unsafeParseType "forall k v . k -> Text -> Optional v" in (r, Just (I.Primop 2 op), type', prefix "Index.lookup#") + , let r = R.Builtin "Index.delete#" + op [key, indexToken] = do + Term.Text' indexToken <- whnf indexToken + key <- whnf key + (db, cleanup) <- Note.lift . RP.acquire resourcePool . Index.textToId $ indexToken + Note.lift . flip finally cleanup $ do + _ <- atomically $ Index.delete (SAH.hash' key) db + pure unitRef + op _ = fail "Index.delete# unpossible" + type' = unsafeParseType "forall k . k -> Text -> Unit" + in (r, Just (I.Primop 2 op), type', prefix "Index.delete#") , let r = R.Builtin "Index.insert#" op [k, v, index] = inject g k v index where inject g k v index = do @@ -190,47 +217,55 @@ makeAPI blockStore crypto = do , let r = R.Builtin "hash#" op [e] = do e <- whnf e - pure $ Term.builtin "Hash" `Term.app` (Term.ref $ SAH.hash e) + let h = Hash.base64 . Hash.fromBytes . SAH.hash' $ e + pure $ Term.builtin "Hash" `Term.app` (Term.text h) op _ = fail "hash" t = "forall a . a -> Hash a" in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "hash#") + , let r = R.Builtin "Hash.base64" + op [e] = do + Term.App' _ (Term.Text' r1) <- whnf e + pure (Term.text r1) + op _ = fail "Hash.base64" + t = "forall a . Hash a -> Text" + in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "Hash.base64") , let r = R.Builtin "Hash.erase" op [e] = pure e - op _ = fail "hash" + op _ = fail "Hash.erase" t = "forall a . Hash a -> Hash Unit" in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "Hash.erase") , let r = R.Builtin "Hash.equal" op [h1,h2] = do - Term.App' _ (Term.Ref' r1) <- whnf h1 - Term.App' _ (Term.Ref' r2) <- whnf h2 + Term.App' _ (Term.Text' r1) <- whnf h1 + Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 == r2 then true else false op _ = fail "Hash.equal" in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.equal") , let r = R.Builtin "Hash.lessThan" op [h1,h2] = do - Term.App' _ (Term.Ref' r1) <- whnf h1 - Term.App' _ (Term.Ref' r2) <- whnf h2 + Term.App' _ (Term.Text' r1) <- whnf h1 + Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 < r2 then true else false op _ = fail "Hash.lessThan" in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.lessThan") , let r = R.Builtin "Hash.lessThanOrEqual" op [h1,h2] = do - Term.App' _ (Term.Ref' r1) <- whnf h1 - Term.App' _ (Term.Ref' r2) <- whnf h2 + Term.App' _ (Term.Text' r1) <- whnf h1 + Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 <= r2 then true else false op _ = fail "Hash.lessThanOrEqual" in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.lessThanOrEqual") , let r = R.Builtin "Hash.greaterThan" op [h1,h2] = do - Term.App' _ (Term.Ref' r1) <- whnf h1 - Term.App' _ (Term.Ref' r2) <- whnf h2 + Term.App' _ (Term.Text' r1) <- whnf h1 + Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 > r2 then true else false op _ = fail "Hash.greaterThan" in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.greaterThan") , let r = R.Builtin "Hash.greaterThanOrEqual" op [h1,h2] = do - Term.App' _ (Term.Ref' r1) <- whnf h1 - Term.App' _ (Term.Ref' r2) <- whnf h2 + Term.App' _ (Term.Text' r1) <- whnf h1 + Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 >= r2 then true else false op _ = fail "Hash.greaterThanOrEqual" in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.greaterThanOrEqual") diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index a47050592..b79aeeb88 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -120,7 +120,7 @@ server crypto allow env lang p = do -- guard $ Put.runPutS (serialize peerKey) == publicKey peer Mux.scope "Remote.server" . Mux.repeatWhile $ do r <- recv - Mux.info $ "eval " ++ show r + Mux.debug $ "eval " ++ show r case r of Nothing -> pure False Just (r, ackChan) -> do diff --git a/node/src/Worker.hs b/node/src/Worker.hs index 2aa61b9f3..14d1b1363 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -41,6 +41,7 @@ main = do backend <- BasicNode.make SAH.hash store (\whnf -> b0 whnf ++ b1 whnf) loadDeclarations "unison-src/base.u" backend loadDeclarations "unison-src/extra.u" backend + loadDeclarations "unison-src/dindex.u" backend initialized <- STM.atomically $ newTVar False pure $ go backend initialized where diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index dace8bf30..3553f9730 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -9,6 +9,7 @@ import Unison.Symbol (Symbol) import Unison.Term (Term) import Unison.Type (Type) import Unison.Typechecker.Context (remoteSignatureOf) +import Control.Concurrent (threadDelay) import qualified Data.Vector as Vector import qualified Data.Text as Text import qualified Unison.ABT as ABT @@ -89,6 +90,33 @@ makeBuiltins whnf = in (r, Nothing, Type.builtin "Boolean", prefix "True") , let r = R.Builtin "False"; in (r, Nothing, Type.builtin "Boolean", prefix "False") + , let r = R.Builtin "Boolean.and"; + op [b1,b2] = do + Term.Builtin' b1 <- whnf b1 + Term.Builtin' b2 <- whnf b2 + pure $ case (b1,b2) of + _ | Text.head b1 /= Text.head b2 -> false + | otherwise -> if Text.head b1 == 'T' then true else false + op _ = error "unpossible" + typ = "Boolean -> Boolean -> Boolean" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "and") + , let r = R.Builtin "Boolean.or"; + op [b1,b2] = do + Term.Builtin' b1 <- whnf b1 + Term.Builtin' b2 <- whnf b2 + pure $ case (b1,b2) of + _ | Text.head b1 /= Text.head b2 -> true + | otherwise -> if Text.head b1 == 'F' then false else true + op _ = error "unpossible" + typ = "Boolean -> Boolean -> Boolean" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "or") + , let r = R.Builtin "Boolean.not"; + op [b1] = do + Term.Builtin' b1 <- whnf b1 + pure $ if Text.head b1 == 'T' then false else true + op _ = error "unpossible" + typ = "Boolean -> Boolean" + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "not") , let r = R.Builtin "Boolean.if"; op [cond,t,f] = do cond <- whnf cond @@ -124,7 +152,22 @@ makeBuiltins whnf = , let r = R.Builtin "Number.Order" in (r, Nothing, unsafeParseType "Order Number", prefix "Number.Order") + -- Duration + , let r = R.Builtin "Duration.seconds" + op [n] = do + Term.Number' n <- whnf n + pure $ Term.num n + op _ = fail "Duration.seconds unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Number -> Duration", prefix "Duration.seconds") + -- Remote + , let r = R.Builtin "Remote.delay" + op [seconds] = do + Term.Number' seconds <- whnf seconds + N.lift $ threadDelay (floor $ seconds * 1000 * 1000) + pure $ Term.remote (Remote.Step (Remote.Local (Remote.Pure unitRef))) + op _ = fail "Remote.at unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Duration -> Remote Unit", prefix "Remote.delay") , let r = R.Builtin "Remote.at" op [node,term] = do Term.Distributed' (Term.Node node) <- whnf node @@ -171,19 +214,19 @@ makeBuiltins whnf = `Term.app` r op _ = fail "unpossible" in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.map", prefix "map") - , let r = R.Builtin "Remote.receiveAsync" + , let r = R.Builtin "Remote.receive-async" op [chan, timeout] = do Term.Number' seconds <- whnf timeout Term.Distributed' (Term.Channel chan) <- whnf chan pure $ Term.remote (Remote.Step (Remote.Local (Remote.ReceiveAsync chan (Remote.Seconds seconds)))) op _ = fail "unpossible" - in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.receiveAsync", prefix "receiveAsync") + in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.receive-async", prefix "Remote.receive-async") , let r = R.Builtin "Remote.receive" op [chan] = do Term.Distributed' (Term.Channel chan) <- whnf chan pure $ Term.remote (Remote.Step (Remote.Local (Remote.Receive chan))) op _ = fail "unpossible" - in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.receive", prefix "receive") + in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.receive", prefix "Remote.receive") , let r = R.Builtin "Remote.fork" op [r] = do Term.Distributed' (Term.Remote r) <- whnf r diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index 34dead69d..b60ea7efc 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -129,6 +129,7 @@ typeBuiltins = (Var.named *** Type.lit) <$> , builtin "Html.Link" -- distributed , builtin "Channel" + , builtin "Duration" , builtin "Future" , builtin "Remote" , builtin "Node" diff --git a/shared/src/Unison/Typechecker/Context.hs b/shared/src/Unison/Typechecker/Context.hs index 7ed9ed168..09445defa 100644 --- a/shared/src/Unison/Typechecker/Context.hs +++ b/shared/src/Unison/Typechecker/Context.hs @@ -594,22 +594,22 @@ remoteSignatureOf k = fromMaybe (error "unknown symbol") (Map.lookup k remoteSig remoteSignatures :: forall v . Var v => Map.Map Text.Text (Type.Type v) remoteSignatures = Map.fromList - [ ("Remote.at", Type.forall' ["a"] (Type.builtin "Node" --> v' "a" --> remote' (v' "a"))) - , ("Remote.fork", Type.forall' ["a"] (remote' (v' "a") --> remote' unitT)) - , ("Remote.here", remote' (Type.builtin "Node")) - , ("Remote.spawn", remote' (Type.builtin "Node")) - , ("Remote.send", Type.forall' ["a"] (channel (v' "a") --> v' "a" --> remote' unitT)) - , ("Remote.channel", Type.forall' ["a"] (remote' (channel (v' "a")))) - , ("Remote.map", Type.forall' ["a","b"] ((v' "a" --> v' "b") --> remote' (v' "a") --> remote' (v' "b"))) - , ("Remote.bind", Type.forall' ["a","b"] ((v' "a" --> remote' (v' "b")) --> remote' (v' "a") --> remote' (v' "b"))) - , ("Remote.pure", Type.forall' ["a"] (v' "a" --> remote' (v' "a"))) - , ("Remote.receiveAsync", Type.forall' ["a"] (channel (v' "a") --> timeoutT --> remote' (remote' (v' "a")))) - , ("Remote.receive", Type.forall' ["a"] (channel (v' "a") --> remote' (v' "a"))) ] + [ ("Remote.at", Type.forall' ["a"] (Type.builtin "Node" --> v' "a" --> remote (v' "a"))) + , ("Remote.fork", Type.forall' ["a"] (remote (v' "a") --> remote unitT)) + , ("Remote.here", remote (Type.builtin "Node")) + , ("Remote.spawn", remote (Type.builtin "Node")) + , ("Remote.send", Type.forall' ["a"] (channel (v' "a") --> v' "a" --> remote unitT)) + , ("Remote.channel", Type.forall' ["a"] (remote (channel (v' "a")))) + , ("Remote.map", Type.forall' ["a","b"] ((v' "a" --> v' "b") --> remote (v' "a") --> remote (v' "b"))) + , ("Remote.bind", Type.forall' ["a","b"] ((v' "a" --> remote (v' "b")) --> remote (v' "a") --> remote (v' "b"))) + , ("Remote.pure", Type.forall' ["a"] (v' "a" --> remote (v' "a"))) + , ("Remote.receive-async", Type.forall' ["a"] (channel (v' "a") --> timeoutT --> remote (remote (v' "a")))) + , ("Remote.receive", Type.forall' ["a"] (channel (v' "a") --> remote (v' "a"))) ] where v' = Type.v' - timeoutT = Type.builtin "Remote.Timeout" + timeoutT = Type.builtin "Duration" unitT = Type.builtin "Unit" - remote' t = Type.builtin "Remote" `Type.app` t + remote t = Type.builtin "Remote" `Type.app` t channel t = Type.builtin "Channel" `Type.app` t -- | For purposes of typechecking, we translate `[x,y,z]` to the term diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 72252640a..d62a8b272 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -23,6 +23,16 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "1 < 2" "True" , t "1 <= 1" "True" , t "1 >= 1" "True" + , t "True `or` False" "True" + , t "False `or` True" "True" + , t "True `or` True" "True" + , t "False `or` False" "False" + , t "True `and` True" "True" + , t "True `and` False" "False" + , t "False `and` True" "False" + , t "False `and` False" "False" + , t "not False" "True" + , t "not True" "False" , t "let rec fac n = if (n == 0) 1 (n * fac (n - 1)); fac 5;;" "120" , t "let rec ping n = if (n >= 10) n (pong (n + 1)); pong n = ping (n + 1); ping 0;;" "10" @@ -37,6 +47,8 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "const 41 0" "41" , t "1st (1,2,3,4)" "1" , t "2nd (1,2 + 1,3,4)" "3" + , t "identity <| (1 + 1)" "2" + , t "(1 + 1) |> identity" "2" , t "if (Text.equal \"hi\" \"hi\") 1 2" "1" , t "if (Text.lessThan \"hi\" \"hiya\") 1 2" "1" , t "if (Text.lessThanOrEqual \"hi\" \"hiya\") 1 2" "1" @@ -57,6 +69,8 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" , t "Vector.sort Number.Order identity [5,2,1,3,4]" "[1,2,3,4,5]" , t "Vector.bind 2nd (Vector.zip [1,2,3] [[1],[2],[3]])" "[1,2,3]" + , t "Vector.all? identity [True,True,True,True]" "True" + , t "Vector.all? identity [True,False,True,True]" "False" , t "Optional.getOr 96 (Vector.at 1 [0,1,2,3,4])" "1" , t "Vector.take 0 [1,2,3]" "[]" , t "Vector.take 2 [1,2,3]" "[1,2]" diff --git a/unison-src/base.u b/unison-src/base.u index bc7e7b274..43ee42139 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -6,6 +6,12 @@ const x y = x; then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; then f1 f2 x = f2 (f1 x); +(|>) : ∀ a b . a -> (a -> b) -> b; +a |> f = f a; + +(<|) : ∀ a b . (a -> b) -> a -> b; +f <| a = f a; + flip : ∀ a b c . (a -> b -> c) -> b -> a -> c; flip f b a = f a b; @@ -21,23 +27,14 @@ rest p = Pair.fold (x y -> y) p; 4th = rest `then` (rest `then` (rest `then` first)); 5th = rest `then` (rest `then` (rest `then` (rest `then` first))); -Remote.transfer : Node -> Remote Unit; -Remote.transfer node = Remote.at node unit; - -Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; -Remote.map f = Remote.bind (f `then` Remote.pure); - -Vector.replicate : ∀ a . Number -> a -> Vector a; -Vector.replicate n a = Vector.map (const a) (Vector.range 0 n); - -Remote.replicate : ∀ a . Number -> Remote a -> Remote (Vector a); -Remote.replicate n r = Remote.sequence (Vector.replicate n r); - Vector.bind : ∀ a b . (a -> Vector b) -> Vector a -> Vector b; Vector.bind f v = Vector.fold-balanced Vector.concatenate Vector.empty (Vector.map f v); Vector.pure = Vector.single; +Vector.replicate : ∀ a . Number -> a -> Vector a; +Vector.replicate n a = Vector.map (const a) (Vector.range 0 n); + Vector.fold-right : ∀ a b . (a -> b -> b) -> b -> Vector a -> b; Vector.fold-right f z vs = Vector.fold-left (flip f) z (Vector.reverse vs); @@ -52,6 +49,38 @@ Vector.fold-balanced plus zero vs = go plus zero vs;; ; +Vector.all? : ∀ a . (a -> Boolean) -> Vector a -> Boolean; +Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); + +Remote.transfer : Node -> Remote Unit; +Remote.transfer node = Remote.at node unit; + +Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; +Remote.map f = Remote.bind (f `then` Remote.pure); + +Remote.replicate : ∀ a . Number -> Remote a -> Remote (Vector a); +Remote.replicate n r = Remote.sequence (Vector.replicate n r); + +Remote.race : ∀ a . Duration -> Vector (Remote a) -> Remote a; +Remote.race timeout rs = do Remote + here := Remote.here; + c := Remote.channel; + result := Remote.receive-async c timeout; + Remote.traverse + (r -> Remote.fork <| do Remote a := r; Remote.transfer here; Remote.send c a;;) + rs; + result;; +; + +-- Returns `None` if no response within the provided `timeout`, +-- which cannot exceed 500 seconds +Remote.timeout : ∀ a . Duration -> Remote a -> Remote (Optional a); +Remote.timeout timeout r = + Remote.race (Duration.seconds 501) [ + Remote.map Some r, + do Remote Remote.delay timeout; pure None;; + ]; + Remote.lift2 : ∀ a b c . (a -> b -> c) -> Remote a -> Remote b -> Remote c; Remote.lift2 f a b = do Remote a := a; @@ -59,6 +88,18 @@ Remote.lift2 f a b = do Remote pure (f a b);; ; +Remote.at' : ∀ a . Node -> Remote a -> Remote a; +Remote.at' node r = do Remote Remote.transfer node; r;;; + +Remote.start : ∀ a . Duration -> Remote a -> Remote (Remote a); +Remote.start timeout r = do Remote + here := Remote.here; + c := Remote.channel; + result := Remote.receive-async c timeout; + Remote.fork (Remote.at' here (r |> Remote.bind (Remote.send c))); + pure result;; +; + Remote.traverse : ∀ a b . (a -> Remote b) -> Vector a -> Remote (Vector b); Remote.traverse f vs = Vector.fold-balanced (Remote.lift2 Vector.concatenate) @@ -71,6 +112,17 @@ Remote.sequence vs = (Remote.pure Vector.empty) (Vector.map (Remote.map Vector.single) vs); +Remote.parallel-traverse : ∀ a b . Duration -> (a -> Remote b) -> Vector a -> Remote (Vector b); +Remote.parallel-traverse timeout f vs = do Remote + futures := Remote.traverse (f `then` Remote.start timeout) vs; + Remote.sequence futures;; +; + +-- Run several remote computations in parallel, returning once `n` equivalent +-- replies come back. Equivalence is based on result of `hash!`. +Remote.quorum : ∀ a b . Duration -> Number -> (a -> Remote b) -> Vector a -> Remote b; +Remote.quorum timeout n = _; -- todo + Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b; Optional.map f = Optional.fold None (f `then` Some); @@ -83,6 +135,9 @@ Optional.pure = Some; Optional.getOr : ∀ a . a -> Optional a -> a; Optional.getOr a = Optional.fold a identity; +Optional.somes : ∀ a . Vector (Optional a) -> Vector a; +Optional.somes = Vector.bind (Optional.fold Vector.empty Vector.single); + Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; Either.map f = Either.fold Left (f `then` Right); @@ -94,4 +149,3 @@ Either.bind = Either.fold Left; Either.swap : ∀ a b . Either a b -> Either b a; Either.swap e = Either.fold Right Left e; - diff --git a/unison-src/dindex.u b/unison-src/dindex.u new file mode 100644 index 000000000..2c7bd45a3 --- /dev/null +++ b/unison-src/dindex.u @@ -0,0 +1,97 @@ +-- A distributed index, using Highest Random Weight (HRW) hashing +-- to pick which nodes are responsible for which keys. See: +-- https://en.wikipedia.org/wiki/Rendezvous_hashing + +DIndex.Replication-Factor = 3; +DIndex.Timeout = Duration.seconds 10; +DIndex.Max-Timeout = Duration.seconds 500; + +DIndex.empty : ∀ k v . Remote (Index Node (Index k v)); +DIndex.empty = Index.empty; + +-- Pick the nodes responsible for a key, using HRW hashing +DIndex.nodesForKey : ∀ k v . k -> Index Node (Index k v) -> Remote (Vector Node); +DIndex.nodesForKey k ind = do Remote + nodes := Index.keys ind; + hashes := Remote.traverse (node -> hash! (node, k)) nodes; + (nodes `Vector.zip` hashes) + |> Vector.sort Hash.Order 2nd + |> Vector.take DIndex.Replication-Factor + |> Vector.map 1st + |> pure;; +; + +DIndex.lookup : ∀ k v . k -> Index Node (Index k v) -> Remote (Optional v); +DIndex.lookup k ind = do Remote + nodes := DIndex.nodesForKey k ind; + localLookup = node -> (do Remote + nind := Index.lookup node ind; + -- on slim chance that a Node is removed from the cluster just before + -- we do the lookup, it gets treated like a timeout + Optional.fold (Remote.map (const None) (Remote.delay DIndex.Timeout)) + (Index.lookup k) + nind;;) + ; + -- todo: use Remote.quorum here + -- Remote.race DIndex.Timeout <| Vector.map localLookup nodes;; + rs := Remote.traverse localLookup nodes; + pure (Vector.at 0 rs |> Optional.bind identity);; +; + +DIndex.insert : ∀ k v . k -> v -> Index Node (Index k v) -> Remote Unit; +DIndex.insert k v ind = do Remote + nodes := DIndex.nodesForKey k ind; + localInsert = node -> (do Remote + nind := Index.lookup node ind; + Optional.fold (Remote.map (const Unit) (Remote.delay DIndex.Timeout)) + (Index.insert k v) + nind;;) + ; + Remote.race DIndex.Timeout <| Vector.map localInsert nodes;; +; + +DIndex.join : ∀ k v . Index Node (Index k v) -> Remote Unit; +DIndex.join ind = do Remote + here := Remote.here; + localInd := Index.empty; + Index.insert here localInd ind;; +; + +DIndex.indicesForKey : ∀ k v . k -> Index Node (Index k v) -> Remote (Vector (Index k v)); +DIndex.indicesForKey k ind = do Remote + nodes := DIndex.nodesForKey k ind; + indices := Remote.traverse (node -> Index.lookup node ind) nodes; + pure (Optional.somes indices);; +; + +DIndex.rebalance : ∀ k v . k -> Index Node (Index k v) -> Remote Unit; +DIndex.rebalance k ind = do Remote + indices := DIndex.indicesForKey k ind; + t = DIndex.Timeout; + results := Remote.parallel-traverse DIndex.Max-Timeout (Index.lookup k `then` Remote.timeout t) indices; + resultsHashes := Remote.traverse hash! results; + uh := hash! None; + hd = uh `Optional.getOr` Vector.at 0 resultsHashes; + eq = h1 h2 -> Hash.equal (Hash.erase h1) (Hash.erase h2); + if (Vector.all? (eq hd) resultsHashes) + -- all results matched, we're good + (pure Unit) + -- not all results matched, reinsert + (do Remote + ov := DIndex.lookup k ind; + Optional.fold (pure Unit) + (v -> DIndex.insert k v ind) + ov;;) + ;; +; +DIndex.leave : ∀ k v . Node -> Index Node (Index k v) -> Remote Unit; +DIndex.leave node ind = do Remote + local-ind := Index.lookup node ind; + Index.delete node ind; + Optional.fold + (pure Unit) + (local-ind -> do Remote + keys := Index.keys local-ind; + Remote.fork <| Remote.traverse (k -> DIndex.rebalance k ind) keys;;) + local-ind;; +; diff --git a/unison-src/extra.u b/unison-src/extra.u index 44bfcdece..78feec933 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -5,12 +5,18 @@ Index.empty = Remote.map Index.empty# Remote.here; Index.keys : ∀ k v . Index k v -> Remote (Vector k); Index.keys = Index.fromUnsafe Index.keys#; +Index.1st-key : ∀ k v . Index k v -> Remote (Optional k); +Index.1st-key = Index.fromUnsafe Index.1st-key#; + Index.increment : ∀ k v . k -> Index k v -> Remote (Optional k); Index.increment k = Index.fromUnsafe (Index.increment# k); Index.lookup : ∀ k v . k -> Index k v -> Remote (Optional v); Index.lookup k = Index.fromUnsafe (Index.lookup# k); +Index.delete : ∀ k v . k -> Index k v -> Remote Unit; +Index.delete k = Index.fromUnsafe (Index.delete# k); + Index.insert : ∀ k v . k -> v -> Index k v -> Remote Unit; Index.insert k v = Index.fromUnsafe (Index.insert# k v); @@ -20,7 +26,8 @@ Index.fromUnsafe f ind = let Remote.map f (Remote.at (1st p) (2nd p));; ; --- todo: Index.delete - Http.getUrl : Text -> Remote (Either Text Text); Http.getUrl url = Remote.map Http.getUrl# (Remote.pure url); + +hash! : ∀ a . a -> Remote (Hash a); +hash! a = Remote.map hash# (Remote.pure a); From 9ecadfb976e5e0100a85f9027c471b85da42eb21 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 24 Aug 2016 21:41:48 -0400 Subject: [PATCH 23/61] Debug.log and Debug.watch builtins --- node/src/Node.hs | 6 ++++-- node/src/Unison/Runtime/ExtraBuiltins.hs | 11 ++++++---- node/src/Worker.hs | 4 ++-- shared/src/Unison/Node/Builtin.hs | 26 ++++++++++++++++++++++-- shared/src/Unison/Node/MemNode.hs | 9 ++++---- shared/src/Unison/Util/Logger.hs | 8 +++++++- shared/tests/Unison/Test/Common.hs | 4 +++- 7 files changed, 52 insertions(+), 16 deletions(-) diff --git a/node/src/Node.hs b/node/src/Node.hs index 8c6acaf7b..32c57bc8e 100644 --- a/node/src/Node.hs +++ b/node/src/Node.hs @@ -26,6 +26,7 @@ import qualified Unison.Runtime.ExtraBuiltins as EB import qualified Unison.Symbol as Symbol import qualified Unison.Term as Term import qualified Unison.View as View +import qualified Unison.Util.Logger as L hash :: Var v => Term.Term v -> Reference hash (Term.Ref' r) = r @@ -44,9 +45,10 @@ makeRandomAddress crypt = Address <$> C.randomBytes crypt 64 main :: IO () main = do store' <- store + logger <- L.atomic (L.atInfo L.toStandardError) let crypto = C.noop "dummypublickey" blockStore <- FBS.make' (makeRandomAddress crypto) makeAddress "Index" - keyValueOps <- EB.makeAPI blockStore crypto - let makeBuiltins whnf = concat [Builtin.makeBuiltins whnf, keyValueOps whnf] + keyValueOps <- EB.make logger blockStore crypto + let makeBuiltins whnf = concat [Builtin.makeBuiltins logger whnf, keyValueOps whnf] node <- BasicNode.make hash store' makeBuiltins NodeServer.server 8080 node diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index a03369c0a..876ec7b40 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -10,11 +10,12 @@ import Unison.BlockStore (Series(..), BlockStore) import Unison.Node.Builtin import Unison.Parsers (unsafeParseType) import Unison.Type (Type) +import Unison.Util.Logger (Logger) import qualified Data.Text as Text import qualified Data.Vector as Vector import qualified Unison.Cryptography as C -import qualified Unison.Hash as Hash import qualified Unison.Eval.Interpreter as I +import qualified Unison.Hash as Hash import qualified Unison.Note as Note import qualified Unison.Reference as R import qualified Unison.Remote as Remote @@ -25,6 +26,7 @@ import qualified Unison.Runtime.ResourcePool as RP import qualified Unison.SerializationAndHashing as SAH import qualified Unison.Term as Term import qualified Unison.Type as Type +import qualified Unison.Util.Logger as L indexT :: Ord v => Type v -> Type v -> Type v indexT k v = Type.ref (R.Builtin "Index") `Type.app` k `Type.app` v @@ -52,9 +54,10 @@ pattern Link' href description <- (Term.Text' description) -- TODO rewrite builtins not to use unsafe code -makeAPI :: Eq a => BlockStore a -> C.Cryptography k syk sk skp s h ByteString - -> IO (WHNFEval -> [Builtin]) -makeAPI blockStore crypto = do +make :: Eq a + => Logger -> BlockStore a -> C.Cryptography k syk sk skp s h ByteString + -> IO (WHNFEval -> [Builtin]) +make logger blockStore crypto = do let nextID = do cp <- C.randomBytes crypto 64 ud <- C.randomBytes crypto 64 diff --git a/node/src/Worker.hs b/node/src/Worker.hs index 14d1b1363..8fb2903a4 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -35,8 +35,8 @@ main = do W.make protocol crypto (pure $ lang logger) where crypto keypair = C.noop (W.public keypair) lang logger crypto blockstore = do - let b0 = Builtin.makeBuiltins - b1 <- ExtraBuiltins.makeAPI blockstore crypto + let b0 = Builtin.makeBuiltins logger + b1 <- ExtraBuiltins.make logger blockstore crypto store <- Store.make backend <- BasicNode.make SAH.hash store (\whnf -> b0 whnf ++ b1 whnf) loadDeclarations "unison-src/base.u" backend diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 3553f9730..7a32ad96d 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -9,6 +9,7 @@ import Unison.Symbol (Symbol) import Unison.Term (Term) import Unison.Type (Type) import Unison.Typechecker.Context (remoteSignatureOf) +import Unison.Util.Logger (Logger) import Control.Concurrent (threadDelay) import qualified Data.Vector as Vector import qualified Data.Text as Text @@ -23,6 +24,7 @@ import qualified Unison.Term as Term import qualified Unison.Type as Type import qualified Unison.Var as Var import qualified Unison.View as View +import qualified Unison.Util.Logger as L type DFO = View.DFO type V = Symbol DFO @@ -45,8 +47,8 @@ pair = Term.builtin "Pair" pair' :: Ord v => Term v -> Term v -> Term v pair' t1 t2 = pair `Term.app` t1 `Term.app` (pair `Term.app` t2 `Term.app` unitRef) -makeBuiltins :: WHNFEval -> [Builtin] -makeBuiltins whnf = +makeBuiltins :: Logger -> WHNFEval -> [Builtin] +makeBuiltins logger whnf = let numeric2 :: Term V -> (Double -> Double -> Double) -> I.Primop (N.Noted IO) V numeric2 sym f = I.Primop 2 $ \xs -> case xs of @@ -82,6 +84,26 @@ makeBuiltins whnf = [ let r = R.Builtin "()" in (r, Nothing, unitT, prefix "()") + , let r = R.Builtin "Debug.log"; + op [msg,logged,a] = do + Term.Text' msg <- whnf msg + logged <- whnf logged + N.lift $ L.error logger (Text.unpack msg ++ ": " ++ show logged) + whnf a + op _ = error "unpossible" + typ = "∀ a b . Text -> a -> b -> b" + in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "Debug.log") + + , let r = R.Builtin "Debug.watch"; + op [msg,a] = do + Term.Text' msg <- whnf msg + a <- whnf a + N.lift $ L.error logger (Text.unpack msg ++ ": " ++ show a) + pure a + op _ = error "unpossible" + typ = "∀ a . Text -> a -> a" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Debug.watch") + , let r = R.Builtin "Color.rgba" in (r, strict r 4, unsafeParseType "Number -> Number -> Number -> Number -> Color", prefix "rgba") diff --git a/shared/src/Unison/Node/MemNode.hs b/shared/src/Unison/Node/MemNode.hs index 748e2bf1e..a786983c7 100644 --- a/shared/src/Unison/Node/MemNode.hs +++ b/shared/src/Unison/Node/MemNode.hs @@ -9,9 +9,10 @@ import Unison.Node.Store (Store) import Unison.Reference (Reference(Derived)) import Unison.Term (Term) import Unison.Type (Type) +import Unison.Util.Logger (Logger) import Unison.Var (Var) -import qualified Data.ByteString.Lazy as LB import qualified Data.ByteString.Builder as Builder +import qualified Data.ByteString.Lazy as LB import qualified Data.Digest.Murmur64 as Murmur import qualified Data.Text as Text import qualified Data.Text.Encoding as Encoding @@ -44,7 +45,7 @@ instance Hashable.Accumulate Hash where type V = Symbol.Symbol View.DFO -make :: IO (Node IO V Reference (Type V) (Term V)) -make = do +make :: Logger -> IO (Node IO V Reference (Type V) (Term V)) +make logger = do store <- MemStore.make :: IO (Store IO V) - BasicNode.make hash store Builtin.makeBuiltins + BasicNode.make hash store (Builtin.makeBuiltins logger) diff --git a/shared/src/Unison/Util/Logger.hs b/shared/src/Unison/Util/Logger.hs index 898985fde..7e98712fd 100644 --- a/shared/src/Unison/Util/Logger.hs +++ b/shared/src/Unison/Util/Logger.hs @@ -17,7 +17,7 @@ import Control.Concurrent.MVar import Control.Exception (finally, try) import Control.Monad import Data.List -import System.IO (Handle, hPutStrLn, hGetLine) +import System.IO (Handle, hPutStrLn, hGetLine, stdout, stderr) import System.IO.Error (isEOFError) type Level = Int @@ -40,6 +40,12 @@ atomic logger = do toHandle :: Handle -> Logger toHandle h = logger (hPutStrLn h) +toStandardError :: Logger +toStandardError = toHandle stderr + +toStandardOut :: Logger +toStandardOut = toHandle stdout + logHandleAt :: Logger -> Level -> Handle -> IO () logHandleAt logger lvl h | lvl > getLevel logger = pure () diff --git a/shared/tests/Unison/Test/Common.hs b/shared/tests/Unison/Test/Common.hs index 4098f9893..26aab4190 100644 --- a/shared/tests/Unison/Test/Common.hs +++ b/shared/tests/Unison/Test/Common.hs @@ -21,6 +21,7 @@ import qualified Unison.Node.MemNode as MemNode import qualified Unison.Note as Note import qualified Unison.Term as Term import qualified Unison.View as View +import qualified Unison.Util.Logger as L type V = Symbol View.DFO -- A Node for testing @@ -37,7 +38,8 @@ loadDeclarations path node = do node :: IO TNode node = do - node <- MemNode.make + logger <- L.atomic (L.atInfo L.toStandardOut) + node <- MemNode.make logger loadDeclarations "unison-src/base.u" node symbols <- liftIO . Note.run $ Map.fromList . Node.references <$> Node.search node Term.blank [] 1000 (Metadata.Query "") Nothing From 39f52aa34c8875d56e6ca9fe583b4f24b8d62bb2 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 24 Aug 2016 22:01:07 -0400 Subject: [PATCH 24/61] fix node tests to propagate logger --- node/tests/Unison/Test/NodeUtil.hs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/node/tests/Unison/Test/NodeUtil.hs b/node/tests/Unison/Test/NodeUtil.hs index 1a453b758..b3e60cbaf 100644 --- a/node/tests/Unison/Test/NodeUtil.hs +++ b/node/tests/Unison/Test/NodeUtil.hs @@ -30,6 +30,7 @@ import qualified Unison.Reference as Reference import qualified Unison.Runtime.ExtraBuiltins as EB import qualified Unison.Term as Term import qualified Unison.View as View +import qualified Unison.Util.Logger as L type DFO = View.DFO type V = Symbol DFO @@ -43,17 +44,18 @@ hash t = Reference.Derived (ABT.hash t) makeRandomAddress :: C.Cryptography k syk sk skp s h c -> IO Address makeRandomAddress crypt = Address <$> C.randomBytes crypt 64 -loadDeclarations :: FilePath -> Node IO V Reference (Type V) (Term V) -> IO () -loadDeclarations path node = do +loadDeclarations :: L.Logger -> FilePath -> Node IO V Reference (Type V) (Term V) -> IO () +loadDeclarations logger path node = do -- note - when run from repl current directory is root, but when run via stack test, current -- directory is the shared subdir - so we check both locations txt <- Text.IO.readFile path <|> Text.IO.readFile (".." `FP.combine` path) let str = Text.unpack txt _ <- Note.run $ Node.declare' Term.ref str node - putStrLn $ "loaded file: " ++ path + L.info logger $ "loaded file: " ++ path makeTestNode :: IO (TestNode, String -> Term V) makeTestNode = do + logger <- L.atomic (L.atInfo L.toStandardOut) let crypto = C.noop "dummypublickey" putStrLn "creating block store..." blockStore <- MBS.make' (makeRandomAddress crypto) makeAddress @@ -61,13 +63,13 @@ makeTestNode = do store' <- UBS.make blockStore -- store' <- FS.make "blockstore.file" putStrLn "created Node store..., building extra builtins" - extraBuiltins <- EB.makeAPI blockStore crypto + extraBuiltins <- EB.make logger blockStore crypto putStrLn "extra builtins created" - let makeBuiltins whnf = concat [Builtin.makeBuiltins whnf, extraBuiltins whnf] + let makeBuiltins whnf = concat [Builtin.makeBuiltins logger whnf, extraBuiltins whnf] node <- BasicNode.make hash store' makeBuiltins - putStrLn "Node created" - loadDeclarations "unison-src/base.u" node - loadDeclarations "unison-src/extra.u" node + L.info logger "Node created" + loadDeclarations logger "unison-src/base.u" node + loadDeclarations logger "unison-src/extra.u" node builtins <- Note.run $ Node.allTermsByVarName Term.ref node let parse = Parsers.bindBuiltins builtins [] . Parsers.unsafeParseTerm pure (node, parse) From 58dc81e2c2f89802eef20ac077c6849fc66c4beb Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 24 Aug 2016 23:02:58 -0400 Subject: [PATCH 25/61] simplify Index.insert# --- node/src/Unison/Runtime/ExtraBuiltins.hs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 876ec7b40..773fe0e9a 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -156,20 +156,16 @@ make logger blockStore crypto = do type' = unsafeParseType "forall k . k -> Text -> Unit" in (r, Just (I.Primop 2 op), type', prefix "Index.delete#") , let r = R.Builtin "Index.insert#" - op [k, v, index] = inject g k v index where - inject g k v index = do - k' <- whnf k - v' <- whnf v - s <- whnf index - g k' v' s - g k v (Term.Text' h) = do - Note.lift $ do - (db, cleanup) <- RP.acquire resourcePool . Index.textToId $ h - flip finally cleanup $ atomically - (Index.insert (SAH.hash' k) (SAH.serializeTerm k, SAH.serializeTerm v) db) - >>= atomically + op [k, v, index] = do + k <- whnf k + v <- whnf v + Term.Text' indexToken <- whnf index + Note.lift $ do + (db, cleanup) <- RP.acquire resourcePool . Index.textToId $ indexToken + flip finally cleanup $ atomically + (Index.insert (SAH.hash' k) (SAH.serializeTerm k, SAH.serializeTerm v) db) + >>= atomically pure unitRef - g k v index = pure $ Term.ref r `Term.app` k `Term.app` v `Term.app` index op _ = fail "Index.insert# unpossible" type' = unsafeParseType "forall k v . k -> v -> Text -> Unit" in (r, Just (I.Primop 3 op), type', prefix "Index.insert#") From 221079d7d0fa22f8109bcf28cb4c6bb259888bb3 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 25 Aug 2016 01:46:29 -0400 Subject: [PATCH 26/61] added save/restoreReceive, needed for implementation of Remote.receive-async --- node/src/Unison/Runtime/Multiplex.hs | 51 +++++++++++++++++++++------- node/src/Unison/Runtime/Remote.hs | 20 ++++++----- shared/src/Unison/Remote.hs | 5 ++- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/node/src/Unison/Runtime/Multiplex.hs b/node/src/Unison/Runtime/Multiplex.hs index aa1ff3f42..73e96c86b 100644 --- a/node/src/Unison/Runtime/Multiplex.hs +++ b/node/src/Unison/Runtime/Multiplex.hs @@ -53,7 +53,12 @@ type IsSubscription = Bool data Callbacks = Callbacks (M.Map B.ByteString (B.ByteString -> IO ())) (TVar Word64) -type Env = (STM Packet -> STM (), Callbacks, IO B.ByteString, L.Logger) +type Env = + ( STM Packet -> STM () + , Callbacks + , IO B.ByteString + , M.Map B.ByteString (Multiplex B.ByteString) + , L.Logger) newtype Multiplex a = Multiplex (ReaderT Env IO a) deriving (Applicative, Alternative, Functor, Monad, MonadIO, MonadPlus, MonadReader Env) @@ -78,7 +83,8 @@ runStandardIO logger sleepAfter rem interrupt m = do output <- atomically Q.empty :: IO (Q.Queue (Maybe Packet)) input <- atomically newTQueue :: IO (TQueue (Maybe Packet)) cb0@(Callbacks cbm cba) <- Callbacks <$> atomically M.new <*> atomically (newTVar 0) - let env = (Q.enqueue output . (Just <$>), cb0, fresh, logger) + recvs0 <- atomically M.new + let env = (Q.enqueue output . (Just <$>), cb0, fresh, recvs0, logger) activity <- atomically $ newTVar 0 let bump = atomically $ modifyTVar' activity (1+) _ <- Async.async $ do @@ -159,7 +165,7 @@ ask = Multiplex Reader.ask bumpActivity :: Multiplex () bumpActivity = do - (_, Callbacks _ cba, _, _) <- ask + (_, Callbacks _ cba, _, _, _) <- ask liftIO $ bumpActivity' cba bumpActivity' :: TVar Word64 -> IO () @@ -167,12 +173,12 @@ bumpActivity' cba = atomically $ modifyTVar' cba (1+) logger :: Multiplex L.Logger logger = do - ~(_, _, _, logger) <- ask + ~(_, _, _, _, logger) <- ask pure logger scope :: String -> Multiplex a -> Multiplex a scope msg = local tweak where - tweak (a,b,c,logger) = (a,b,c,L.scope msg logger) + tweak (a,b,c,d,logger) = (a,b,c,d,L.scope msg logger) -- | Crash with a message. Include the current logging scope. crash :: String -> Multiplex a @@ -187,7 +193,7 @@ debug msg = logger >>= \logger -> liftIO $ L.debug logger msg process :: IO (Maybe Packet) -> Multiplex () process recv = scope "Mux.process" $ do - (_, Callbacks cbs cba, _, logger) <- ask + (_, Callbacks cbs cba, _, _, logger) <- ask liftIO . repeatWhile $ do packet <- recv case packet of @@ -199,7 +205,7 @@ process recv = scope "Mux.process" $ do L.warn logger $ "dropped packet @ " ++ show (Base64.encode destination) pure True Just callback -> do - L.debug logger $ "packet delivered @ " ++ show (Base64.encode destination) + L.warn logger $ "packet delivered @ " ++ show (Base64.encode destination) bumpActivity' cba callback content pure True @@ -337,13 +343,13 @@ fork m = do nest :: Serial k => k -> Multiplex a -> Multiplex a nest outer m = Reader.local tweak m where - tweak (send,cbs,fresh,log) = (send' send,cbs,fresh,log) + tweak (send,cbs,fresh,recvs,log) = (send' send,cbs,fresh,recvs,log) kbytes = Put.runPutS (serialize outer) send' send p = send $ (\p -> Packet kbytes (Put.runPutS (serialize p))) <$> p channel :: Multiplex (Channel a) channel = do - ~(_,_,fresh,_) <- ask + ~(_,_,fresh,_,_) <- ask Channel Type <$> liftIO fresh send :: Serial a => Channel a -> a -> Multiplex () @@ -351,12 +357,12 @@ send chan a = send' chan (pure a) send' :: Serial a => Channel a -> STM a -> Multiplex () send' (Channel _ key) a = do - ~(send,_,_,_) <- ask + ~(send,_,_,_,_) <- ask liftIO . atomically $ send (Packet key . Put.runPutS . serialize <$> a) receiveCancellable :: Serial a => Channel a -> Multiplex (Multiplex a, String -> Multiplex ()) receiveCancellable (Channel _ key) = do - (_,Callbacks cbs cba,_,_) <- ask + (_,Callbacks cbs cba,_,_,_) <- ask result <- liftIO newEmptyMVar liftIO . atomically $ M.insert (putMVar result . Right) key cbs liftIO $ bumpActivity' cba @@ -378,6 +384,27 @@ receiveTimed msg micros chan = do run env (cancel $ "receiveTimed timeout during " ++ msg) pure $ scope "receiveTimed" (force <* liftIO (C.killThread watchdog) <* cancel ("receiveTimed completed" ++ msg)) +-- Save a receive future as part of +saveReceive :: Microseconds + -> B.ByteString -> Multiplex B.ByteString -> Multiplex () +saveReceive micros chan force = do + (_,_,_,recvs,_) <- ask + tid <- liftIO . C.forkIO $ do + C.threadDelay micros + atomically $ M.delete chan recvs + let force' = do + liftIO $ C.killThread tid + liftIO $ atomically (M.delete chan recvs) + force + liftIO . atomically $ M.insert force' chan recvs + +restoreReceive :: B.ByteString -> Multiplex B.ByteString +restoreReceive chan = do + (_,_,_,recvs,_) <- ask + o <- liftIO . atomically $ M.lookup chan recvs + fromMaybe (crash $ "chan could not be restored: " ++ show (Base64.encode chan)) + o + timeout' :: Microseconds -> a -> Multiplex a -> Multiplex a timeout' micros onTimeout m = fromMaybe onTimeout <$> timeout micros m @@ -422,7 +449,7 @@ subscribeTimed micros chan = do subscribe :: Serial a => Channel a -> Multiplex (Multiplex a, Multiplex ()) subscribe (Channel _ key) = scope "subscribe" $ do - (_, Callbacks cbs cba, _, _) <- ask + (_, Callbacks cbs cba, _, _, _) <- ask q <- liftIO . atomically $ newTQueue liftIO . atomically $ M.insert (atomically . writeTQueue q) key cbs liftIO $ bumpActivity' cba diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index b79aeeb88..43457ca54 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -193,19 +193,23 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of runLocal (Pure t) = do Mux.debug $ "runLocal Pure" liftIO $ eval lang t - runLocal (Send (Channel cid) a) = do - Mux.debug $ "runLocal Send " ++ show cid + runLocal (Send c@(Channel cid) a) = do + Mux.warn $ "runLocal Send " ++ show c Mux.process1 (Mux.Packet cid (Put.runPutS (serialize a))) pure (unit lang) runLocal (ReceiveAsync chan@(Channel cid) (Seconds seconds)) = do Mux.debug $ "runLocal ReceiveAsync " ++ show (seconds, cid) - _ <- Mux.receiveTimed ("receiveAsync on " ++ show chan) - (floor $ seconds * 1000 * 1000) ((Mux.Channel Mux.Type cid) :: Mux.Channel (Maybe B.ByteString)) - pure (remote lang (Step (Local (Receive chan)))) + forceChan <- Mux.channel + Mux.warn $ "ReceiveAsync force channel " ++ show forceChan + let micros = floor $ seconds * 1000 * 1000 + force <- Mux.receiveTimed ("receiveAsync on " ++ show chan) + micros ((Mux.Channel Mux.Type cid) :: Mux.Channel B.ByteString) + Mux.saveReceive micros (Mux.channelId forceChan) force + pure (remote lang (Step (Local (Receive (Channel $ Mux.channelId forceChan))))) runLocal (Receive (Channel cid)) = do - Mux.debug $ "runLocal Receive " ++ show cid - (recv,_) <- Mux.receiveCancellable (Mux.Channel Mux.Type cid) - bytes <- recv + Mux.warn $ "runLocal Receive " ++ show cid + bytes <- Mux.restoreReceive cid + Mux.warn $ "runLocal Receive got bytes " ++ show cid case Get.runGetS deserialize bytes of Left err -> fail err Right r -> pure r diff --git a/shared/src/Unison/Remote.hs b/shared/src/Unison/Remote.hs index af2817196..5bcfd6367 100644 --- a/shared/src/Unison/Remote.hs +++ b/shared/src/Unison/Remote.hs @@ -168,7 +168,10 @@ instance Hashable Node where instance Show Node where show (Node host key) = "http://" ++ Text.unpack host ++ "/" ++ Text.unpack (decodeUtf8 (Base64.encode key)) -newtype Channel = Channel ByteString deriving (Eq,Ord,Generic,Show) +newtype Channel = Channel ByteString deriving (Eq,Ord,Generic) +instance Show Channel where + show (Channel id) = Text.unpack (decodeUtf8 (Base64.encode id)) + instance ToJSON Channel where toJSON (Channel c) = toJSON (decodeUtf8 (Base64.encode c)) instance FromJSON Channel where From f65d1500c730b2ee6bbe1a99497f049f32e75390 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Fri, 26 Aug 2016 16:18:39 -0400 Subject: [PATCH 27/61] Modify parser to support user state, get rid of committed success combinators --- node/unison-node.cabal | 2 + shared/src/Unison/Node.hs | 3 +- shared/src/Unison/Parser.hs | 184 +++++++++++++------------ shared/src/Unison/Parsers.hs | 17 ++- shared/src/Unison/TermParser.hs | 70 +++++----- shared/src/Unison/TypeParser.hs | 26 ++-- shared/tests/Unison/Test/TermParser.hs | 2 +- 7 files changed, 163 insertions(+), 141 deletions(-) diff --git a/node/unison-node.cabal b/node/unison-node.cabal index d40bc5afd..7d8e421f1 100644 --- a/node/unison-node.cabal +++ b/node/unison-node.cabal @@ -50,6 +50,7 @@ library Unison.Hash.Extra Unison.Kind.Extra Unison.Metadata.Extra + Unison.Node.FileStore Unison.Node.UnisonBlockStore Unison.NodeContainer Unison.NodeServer @@ -310,6 +311,7 @@ test-suite tests ctrie, curl, directory, + filepath, hashable, random, stm, diff --git a/shared/src/Unison/Node.hs b/shared/src/Unison/Node.hs index e038f9188..65834bcd8 100644 --- a/shared/src/Unison/Node.hs +++ b/shared/src/Unison/Node.hs @@ -32,6 +32,7 @@ import qualified Unison.Reference as Reference import qualified Unison.Term as Term import qualified Unison.TermEdit as TermEdit import qualified Unison.TermParser as TermParser +import qualified Unison.TypeParser as TypeParser import qualified Unison.Typechecker as Typechecker import qualified Unison.Typechecker.Components as Components -- import Debug.Trace @@ -267,7 +268,7 @@ declare ref bindings node = do -- | Like `declare`, but takes a `String` declare' :: (Monad m, Var v) => (h -> Term v) -> String -> Node m v h (Type v) (Term v) -> Noted m () declare' ref bindings node = do - bs <- case Parser.run TermParser.moduleBindings bindings of + bs <- case Parser.run TermParser.moduleBindings bindings TypeParser.s0 of Parser.Fail err _ -> Noted (pure $ Left (Note err)) Parser.Succeed bs _ _ -> pure bs declare ref bs node diff --git a/shared/src/Unison/Parser.hs b/shared/src/Unison/Parser.hs index 7239530e4..15732b955 100644 --- a/shared/src/Unison/Parser.hs +++ b/shared/src/Unison/Parser.hs @@ -14,65 +14,68 @@ import qualified Data.Char as Char import qualified Prelude import Debug.Trace -type InLayout = Bool -newtype Parser a = Parser { run' :: (String,InLayout) -> Result a } +data Env s = + Env { overallInput :: String + , offset :: !Int + , state :: !s + , currentInput :: String } -- always just `drop offset overallInput` -root :: Parser a -> Parser a +newtype Parser s a = Parser { run' :: Env s -> Result s a } + +root :: Parser s a -> Parser s a root p = ignored *> (p <* (optional semicolon <* eof)) -semicolon :: Parser () +semicolon :: Parser s () semicolon = void $ token (char ';') -semicolon2 :: Parser () +semicolon2 :: Parser s () semicolon2 = semicolon *> semicolon -eof :: Parser () -eof = Parser $ \(s,_) -> case s of - [] -> Succeed () 0 False - _ -> Fail [Prelude.takeWhile (/= '\n') s, "expected eof, got"] False +eof :: Parser s () +eof = Parser $ \env -> case (currentInput env) of + [] -> Succeed () (state env) 0 + _ -> Fail [Prelude.takeWhile (/= '\n') (currentInput env), "expected eof"] False -attempt :: Parser a -> Parser a +attempt :: Parser s a -> Parser s a attempt p = Parser $ \s -> case run' p s of Fail stack _ -> Fail stack False - Succeed a n _ -> Succeed a n False + succeed -> succeed -run :: Parser a -> String -> Result a --- run p s = run' p (watch "layoutized" $ layoutize s, False) -run p s = run' p (s, False) - where watch msg a = trace (msg ++ ":\n" ++ a) a +run :: Parser s a -> String -> s -> Result s a +run p s s0 = run' p (Env s 0 s0 s) -unsafeRun :: Parser a -> String -> a -unsafeRun p s = case toEither $ run p s of +unsafeRun :: Parser s a -> String -> s -> a +unsafeRun p s s0 = case toEither $ run p s s0 of Right a -> a Left e -> error ("Parse error:\n" ++ e) -unsafeGetSucceed :: Result a -> a +unsafeGetSucceed :: Result s a -> a unsafeGetSucceed r = case r of Succeed a _ _ -> a Fail e _ -> error (unlines ("Parse error:":e)) -string :: String -> Parser String -string s = Parser $ \(input,_) -> - if s `isPrefixOf` input then Succeed s (length s) False - else Fail ["expected '" ++ s ++ "', got " ++ takeLine input] False +string :: String -> Parser s String +string s = Parser $ \env -> + if s `isPrefixOf` (currentInput env) then Succeed s (state env) (length s) + else Fail ["expected '" ++ s ++ "', got " ++ takeLine (currentInput env)] False takeLine :: String -> String takeLine = Prelude.takeWhile (/= '\n') -char :: Char -> Parser Char -char c = Parser $ \(input,_) -> - if listToMaybe input == Just c then Succeed c 1 False - else Fail ["expected " ++ show c ++ " near " ++ takeLine input] False +char :: Char -> Parser s Char +char c = Parser $ \env -> + if listToMaybe (currentInput env) == Just c then Succeed c (state env) 1 + else Fail ["expected '" ++ show c ++ "', got " ++ takeLine (currentInput env)] False -one :: (Char -> Bool) -> Parser Char -one f = Parser $ \(s,_) -> case s of - (h:_) | f h -> Succeed h 1 False +one :: (Char -> Bool) -> Parser s Char +one f = Parser $ \env -> case (currentInput env) of + (h:_) | f h -> Succeed h (state env) 1 _ -> Fail [] False -base64string' :: String -> Parser String +base64string' :: String -> Parser s String base64string' alphabet = concat <$> many base64group where - base64group :: Parser String + base64group :: Parser s String base64group = do chars <- some $ one (`elem` alphabet) padding <- sequenceA (replicate (padCount $ length chars) (char '=')) @@ -80,23 +83,23 @@ base64string' alphabet = concat <$> many base64group padCount :: Int -> Int padCount len = case len `mod` 4 of 0 -> 0; n -> 4 - n -base64urlstring :: Parser String +base64urlstring :: Parser s String base64urlstring = base64string' $ ['A' .. 'Z'] ++ ['a' .. 'z'] ++ ['0' .. '9'] ++ "-_" notReservedChar :: Char -> Bool notReservedChar = (`notElem` "\".,`[]{}:;()") -identifier :: [String -> Bool] -> Parser String +identifier :: [String -> Bool] -> Parser s String identifier = identifier' [not . isSpace, notReservedChar] -identifier' :: [Char -> Bool] -> [String -> Bool] -> Parser String +identifier' :: [Char -> Bool] -> [String -> Bool] -> Parser s String identifier' charTests stringTests = do i <- takeWhile1 "identifier" (\c -> all ($ c) charTests) guard (all ($ i) stringTests) pure i -- a wordyId isn't all digits, and isn't all symbols -wordyId :: [String] -> Parser String +wordyId :: [String] -> Parser s String wordyId keywords = token $ f <$> sepBy1 dot id where dot = char '.' @@ -104,115 +107,120 @@ wordyId keywords = token $ f <$> sepBy1 dot id f segs = intercalate "." segs -- a symbolyId is all symbols -symbolyId :: [String] -> Parser String +symbolyId :: [String] -> Parser s String symbolyId keywords = token $ identifier' [notReservedChar, not . Char.isSpace, \c -> Char.isSymbol c || Char.isPunctuation c] [(`notElem` keywords)] -token :: Parser a -> Parser a +token :: Parser s a -> Parser s a token p = p <* ignored -haskellLineComment :: Parser () +haskellLineComment :: Parser s () haskellLineComment = void $ string "--" *> takeWhile "-- comment" (/= '\n') -lineErrorUnless :: String -> Parser a -> Parser a -lineErrorUnless s p = commitFail $ Parser $ \input -> case run' p input of - Fail e b -> Fail (s:m:e) b - where m = "near \'" ++ Prelude.takeWhile (/= '\n') (fst input) ++ "\'" - ok -> ok +lineErrorUnless :: String -> Parser s a -> Parser s a +lineErrorUnless s = commitFail . scope s -parenthesized :: Parser a -> Parser a +currentLine :: Parser s String +currentLine = + Parser $ \(Env overall i s cur) -> + let + -- this grabs the current line up to current offset, i + l = reverse . Prelude.takeWhile (/= '\n') . reverse . take i $ overall + restOfLine = Prelude.takeWhile (/= '\n') cur + in Succeed (l ++ restOfLine) s 0 + +parenthesized :: Parser s a -> Parser s a parenthesized p = lp *> body <* rp where lp = token (char '(') body = p rp = lineErrorUnless "missing )" $ token (char ')') -takeWhile :: String -> (Char -> Bool) -> Parser String -takeWhile msg f = scope msg . Parser $ \(s,_) -> - let hd = Prelude.takeWhile f s - in Succeed hd (length hd) False +takeWhile :: String -> (Char -> Bool) -> Parser s String +takeWhile msg f = scope msg . Parser $ \(Env _ _ s cur) -> + let hd = Prelude.takeWhile f cur + in Succeed hd s (length hd) -takeWhile1 :: String -> (Char -> Bool) -> Parser String -takeWhile1 msg f = scope msg . Parser $ \(s,_) -> - let hd = Prelude.takeWhile f s - in if null hd then Fail ["takeWhile1 empty: " ++ take 20 s] False - else Succeed hd (length hd) False +takeWhile1 :: String -> (Char -> Bool) -> Parser s String +takeWhile1 msg f = scope msg . Parser $ \(Env _ _ s cur) -> + let hd = Prelude.takeWhile f cur + in if null hd then Fail [] False + else Succeed hd s (length hd) -whitespace :: Parser () +whitespace :: Parser s () whitespace = void $ takeWhile "whitespace" Char.isSpace -whitespace1 :: Parser () +whitespace1 :: Parser s () whitespace1 = void $ takeWhile1 "whitespace1" Char.isSpace -nonempty :: Parser a -> Parser a +nonempty :: Parser s a -> Parser s a nonempty p = Parser $ \s -> case run' p s of - Succeed _ 0 b -> Fail [] b + Succeed _ _ 0 -> Fail [] False ok -> ok -scope :: String -> Parser a -> Parser a +scope :: String -> Parser s a -> Parser s a scope s p = Parser $ \input -> case run' p input of Fail e b -> Fail (s:e) b ok -> ok -commitSuccess :: Parser a -> Parser a -commitSuccess p = Parser $ \input -> case run' p input of - Fail e b -> Fail e b - Succeed a n _ -> Succeed a n True - -commitFail :: Parser a -> Parser a +commitFail :: Parser s a -> Parser s a commitFail p = Parser $ \input -> case run' p input of Fail e _ -> Fail e True - Succeed a n b -> Succeed a n b + Succeed a s n -> Succeed a s n -commit' :: Parser () -commit' = commitSuccess (pure ()) - -failWith :: String -> Parser a +failWith :: String -> Parser s a failWith error = Parser . const $ Fail [error] False -sepBy :: Parser a -> Parser b -> Parser [b] +sepBy :: Parser s a -> Parser s b -> Parser s [b] sepBy sep pb = f <$> optional (sepBy1 sep pb) where f Nothing = [] f (Just l) = l -sepBy1 :: Parser a -> Parser b -> Parser [b] +sepBy1 :: Parser s a -> Parser s b -> Parser s [b] sepBy1 sep pb = (:) <$> pb <*> many (sep *> pb) -ignored :: Parser () +ignored :: Parser s () ignored = void $ many (whitespace1 <|> haskellLineComment) -toEither :: Result a -> Either String a +toEither :: Result s a -> Either String a toEither (Fail e _) = Left (intercalate "\n" e) toEither (Succeed a _ _) = Right a -data Result a - = Fail [String] Bool - | Succeed a Int Bool +data Result s a + = Fail [String] !Bool + | Succeed a s !Int deriving (Show,Functor,Foldable,Traversable) -instance Functor Parser where +get :: Parser s s +get = Parser (\env -> Succeed (state env) (state env) 0) + +set :: s -> Parser s () +set s = Parser (\env -> Succeed () s 0) + +instance Functor (Parser s) where fmap = liftM -instance Applicative Parser where +instance Applicative (Parser s) where pure = return (<*>) = ap -instance Alternative Parser where +instance Alternative (Parser s) where empty = mzero (<|>) = mplus -instance Monad Parser where - return a = Parser $ \_ -> Succeed a 0 False - Parser p >>= f = Parser $ \s -> case p s of - Succeed a n committed -> case run' (f a) (drop n (fst s), snd s) of - Succeed b m c2 -> Succeed b (n+m) (committed || c2) - Fail e b -> Fail e (committed || b) +instance Monad (Parser s) where + return a = Parser $ \env -> Succeed a (state env) 0 + Parser p >>= f = Parser $ \env@(Env overall i s cur) -> case p env of + Succeed a s n -> + case run' (f a) (Env overall (i+n) s (drop n cur)) of + Succeed b s m -> Succeed b s (n+m) + Fail e b -> Fail e b Fail e b -> Fail e b -instance MonadPlus Parser where +instance MonadPlus (Parser s) where mzero = Parser $ \_ -> Fail [] False - mplus p1 p2 = Parser $ \s -> case run' p1 s of - Fail _ False -> run' p2 s + mplus p1 p2 = Parser $ \env -> case run' p1 env of + Fail _ False -> run' p2 env ok -> ok diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index b60ea7efc..0f546a4d5 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -21,23 +21,28 @@ import qualified Unison.Reference as R import qualified Unison.Var as Var type V = Symbol DFO +type S = TypeParser.S V -parseTerm :: String -> Result (Term V) +s0 :: S +s0 = TypeParser.s0 + +parseTerm :: String -> Result S (Term V) parseTerm = parseTerm' termBuiltins typeBuiltins -parseType :: String -> Result (Type V) +parseType :: String -> Result S (Type V) parseType = parseType' typeBuiltins -parseTerm' :: [(V, Term V)] -> [(V, Type V)] -> String -> Result (Term V) +parseTerm' :: [(V, Term V)] -> [(V, Type V)] -> String -> Result S (Term V) parseTerm' termBuiltins typeBuiltins s = - bindBuiltins termBuiltins typeBuiltins <$> run (Parser.root TermParser.term) s + bindBuiltins termBuiltins typeBuiltins <$> run (Parser.root TermParser.term) s s0 bindBuiltins :: Var v => [(v, Term v)] -> [(v, Type v)] -> Term v -> Term v bindBuiltins termBuiltins typeBuiltins = Term.typeMap (ABT.substs typeBuiltins) . ABT.substs termBuiltins -parseType' :: [(V, Type V)] -> String -> Result (Type V) -parseType' typeBuiltins s = ABT.substs typeBuiltins <$> run (Parser.root TypeParser.type_) s +parseType' :: [(V, Type V)] -> String -> Result S (Type V) +parseType' typeBuiltins s = + ABT.substs typeBuiltins <$> run (Parser.root TypeParser.type_) s s0 unsafeParseTerm :: String -> Term V unsafeParseTerm = unsafeGetSucceed . parseTerm diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index 7495270ca..1992a5b75 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -32,16 +32,18 @@ operator characters (like empty? or fold-left). Sections / partial application of infix operators is not implemented. -} -term :: Var v => Parser (Term v) +type S = TypeParser.S + +term :: Var v => Parser (S v) (Term v) term = possiblyAnnotated term2 -term2 :: Var v => Parser (Term v) +term2 :: Var v => Parser (S v) (Term v) term2 = let_ term3 <|> term3 -term3 :: Var v => Parser (Term v) +term3 :: Var v => Parser (S v) (Term v) term3 = infixApp term4 <|> term4 -infixApp :: Var v => Parser (Term v) -> Parser (Term v) +infixApp :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) infixApp p = f <$> arg <*> some ((,) <$> infixVar <*> arg) where arg = p @@ -50,16 +52,16 @@ infixApp p = f <$> arg <*> some ((,) <$> infixVar <*> arg) g :: Ord v => Term v -> (v, Term v) -> Term v g lhs (op, rhs) = Term.apps (Term.var op) [lhs,rhs] -term4 :: Var v => Parser (Term v) +term4 :: Var v => Parser (S v) (Term v) term4 = prefixApp term5 -term5 :: Var v => Parser (Term v) +term5 :: Var v => Parser (S v) (Term v) term5 = lam term <|> effectBlock <|> termLeaf -termLeaf :: Var v => Parser (Term v) +termLeaf :: Var v => Parser (S v) (Term v) termLeaf = asum [hashLit, prefixTerm, lit, tupleOrParenthesized term, blank, vector term] -tupleOrParenthesized :: Var v => Parser (Term v) -> Parser (Term v) +tupleOrParenthesized :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) tupleOrParenthesized rec = parenthesized $ go <$> sepBy1 (token $ string ",") rec where go [t] = t -- was just a parenthesized term @@ -75,7 +77,7 @@ tupleOrParenthesized rec = -- x := pure 23; -- y = 11; -- pure (f x);; -effectBlock :: forall v . Var v => Parser (Term v) +effectBlock :: forall v . Var v => Parser (S v) (Term v) effectBlock = (token (string "do") *> wordyId keywords) >>= go where go name = do bindings <- some $ asum [Right <$> binding, Left <$> action] <* semicolon @@ -94,7 +96,7 @@ effectBlock = (token (string "do") *> wordyId keywords) >>= go where go (Left action) _ = Just action interpretPure :: Term v -> Term v interpretPure = ABT.subst (ABT.v' "pure") qualifiedPure - binding :: Parser (v, Term v) + binding :: Parser (S v) (v, Term v) binding = scope "binding" $ do lhs <- ABT.v' . Text.pack <$> token (wordyId keywords) eff <- token $ (True <$ string ":=") <|> (False <$ string "=") @@ -102,18 +104,18 @@ effectBlock = (token (string "do") *> wordyId keywords) >>= go where let rhs' = if eff then interpretPure rhs else qualifiedPure `Term.app` rhs pure (lhs, rhs') - action :: Parser (Term v) + action :: Parser (S v) (Term v) action = scope "action" $ (interpretPure <$> term) -text' :: Parser Literal +text' :: Parser s Literal text' = token $ fmap (Term.Text . Text.pack) ps where ps = char '"' *> Unison.Parser.takeWhile "text literal" (/= '"') <* char '"' -text :: Ord v => Parser (Term v) +text :: Ord v => Parser s (Term v) text = Term.lit <$> text' -number' :: Parser Literal +number' :: Parser s Literal number' = token (f <$> digits <*> optional ((:) <$> char '.' <*> digits)) where digits = nonempty (takeWhile "number" isDigit) @@ -121,26 +123,26 @@ number' = token (f <$> digits <*> optional ((:) <$> char '.' <*> digits)) f whole part = (Term.Number . read) $ maybe whole (whole++) part -hashLit :: Ord v => Parser (Term v) +hashLit :: Ord v => Parser s (Term v) hashLit = token (f <$> (mark *> hash)) where f = Term.derived' . Text.pack mark = char '#' hash = lineErrorUnless "error parsing base64url hash" base64urlstring -number :: Ord v => Parser (Term v) +number :: Ord v => Parser (S v) (Term v) number = Term.lit <$> number' -lit' :: Parser Literal +lit' :: Parser s Literal lit' = text' <|> number' -lit :: Ord v => Parser (Term v) +lit :: Ord v => Parser (S v) (Term v) lit = Term.lit <$> lit' -blank :: Ord v => Parser (Term v) +blank :: Ord v => Parser (S v) (Term v) blank = token (char '_') $> Term.blank -vector :: Ord v => Parser (Term v) -> Parser (Term v) +vector :: Ord v => Parser (S v) (Term v) -> Parser (S v) (Term v) vector p = Term.vector <$> (lbracket *> elements <* rbracket) where lbracket = token (char '[') @@ -148,17 +150,17 @@ vector p = Term.vector <$> (lbracket *> elements <* rbracket) comma = token (char ',') rbracket = lineErrorUnless "syntax error" $ token (char ']') -possiblyAnnotated :: Var v => Parser (Term v) -> Parser (Term v) +possiblyAnnotated :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) possiblyAnnotated p = f <$> p <*> optional ann'' where f t (Just y) = Term.ann t y f t Nothing = t -ann'' :: Var v => Parser (Type v) +ann'' :: Var v => Parser (S v) (Type v) ann'' = token (char ':') *> TypeParser.type_ --let server = _; blah = _ in _ -let_ :: Var v => Parser (Term v) -> Parser (Term v) +let_ :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) let_ p = f <$> (let_ *> optional rec_) <*> bindings' where let_ = token (string "let") @@ -172,50 +174,50 @@ let_ p = f <$> (let_ *> optional rec_) <*> bindings' f Nothing (bindings, body) = Term.let1 bindings body f (Just _) (bindings, body) = Term.letRec bindings body -typedecl :: Var v => Parser (v, Type v) +typedecl :: Var v => Parser (S v) (v, Type v) typedecl = (,) <$> prefixVar <*> ann'' -bindingEqBody :: Parser (Term v) -> Parser (Term v) +bindingEqBody :: Parser (S v) (Term v) -> Parser (S v) (Term v) bindingEqBody p = eq *> body where eq = token (char '=') body = lineErrorUnless "parse error in body of binding" p -infixVar :: Var v => Parser v +infixVar :: Var v => Parser s v infixVar = (Var.named . Text.pack) <$> (backticked <|> symbolyId keywords) where backticked = char '`' *> wordyId keywords <* token (char '`') -prefixVar :: Var v => Parser v +prefixVar :: Var v => Parser s v prefixVar = (Var.named . Text.pack) <$> prefixOp where - prefixOp :: Parser String prefixOp = wordyId keywords <|> (char '(' *> symbolyId keywords <* token (char ')')) -- no whitespace w/in parens -prefixTerm :: Var v => Parser (Term v) +prefixTerm :: Var v => Parser (S v) (Term v) prefixTerm = Term.var <$> prefixVar keywords :: [String] keywords = ["do", "let", "rec", "in", "->", ":", "=", "where"] -lam :: Var v => Parser (Term v) -> Parser (Term v) +lam :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) lam p = Term.lam'' <$> vars <* arrow <*> body where vars = some prefixVar arrow = token (string "->") body = p -prefixApp :: Ord v => Parser (Term v) -> Parser (Term v) +prefixApp :: Ord v => Parser (S v) (Term v) -> Parser (S v) (Term v) prefixApp p = f <$> some p where f (func:args) = Term.apps func args f [] = error "'some' shouldn't produce an empty list" -bindings :: Var v => Parser (Term v) -> Parser [(v, Term v)] +bindings :: Var v => Parser (S v) (Term v) -> Parser (S v) [(v, Term v)] bindings p = some (binding <* semicolon) where binding = do typ <- optional (typedecl <* semicolon) - (name, args) <- ((\arg1 op arg2 -> (op,[arg1,arg2])) <$> prefixVar <*> infixVar <*> prefixVar) + (name, args) <- ( (\arg1 op arg2 -> (op,[arg1,arg2])) + <$> prefixVar <*> infixVar <*> prefixVar) <|> ((,) <$> prefixVar <*> many prefixVar) body <- bindingEqBody term case typ of @@ -227,5 +229,5 @@ bindings p = some (binding <* semicolon) where mkBinding f [] body = (f, body) mkBinding f args body = (f, Term.lam'' args body) -moduleBindings :: Var v => Parser [(v, Term v)] +moduleBindings :: Var v => Parser (S v) [(v, Term v)] moduleBindings = root (bindings term3) diff --git a/shared/src/Unison/TypeParser.hs b/shared/src/Unison/TypeParser.hs index 6d5e4649c..7ac6a718c 100644 --- a/shared/src/Unison/TypeParser.hs +++ b/shared/src/Unison/TypeParser.hs @@ -14,17 +14,21 @@ import Unison.Var (Var) import qualified Data.Text as Text import qualified Unison.Type as Type -type_ :: Var v => Parser (Type v) +newtype S v = Aliases [(v, [Type v] -> Type v)] +s0 :: S v +s0 = Aliases [] + +type_ :: Var v => Parser (S v) (Type v) type_ = forall type1 <|> type1 -typeLeaf :: Var v => Parser (Type v) +typeLeaf :: Var v => Parser (S v) (Type v) typeLeaf = asum [ literal , tupleOrParenthesized type_ , fmap (Type.v' . Text.pack) (token varName) ] -tupleOrParenthesized :: Ord v => Parser (Type v) -> Parser (Type v) +tupleOrParenthesized :: Ord v => Parser (S v) (Type v) -> Parser (S v) (Type v) tupleOrParenthesized rec = parenthesized $ go <$> sepBy1 (token $ string ",") rec where go [t] = t @@ -32,21 +36,21 @@ tupleOrParenthesized rec = pair t1 t2 = Type.builtin "Pair" `Type.app` t1 `Type.app` t2 unit = Type.builtin "Unit" -type1 :: Var v => Parser (Type v) +type1 :: Var v => Parser (S v) (Type v) type1 = arrow type2 -type2 :: Var v => Parser (Type v) +type2 :: Var v => Parser (S v) (Type v) type2 = app typeLeaf -- "TypeA TypeB TypeC" -app :: Ord v => Parser (Type v) -> Parser (Type v) +app :: Ord v => Parser (S v) (Type v) -> Parser (S v) (Type v) app rec = fmap (foldl1' Type.app) (some rec) -arrow :: Ord v => Parser (Type v) -> Parser (Type v) +arrow :: Ord v => Parser (S v) (Type v) -> Parser (S v) (Type v) arrow rec = foldr1 Type.arrow <$> sepBy1 (token $ string "->") rec -- "forall a b . List a -> List b -> Maybe Text" -forall :: Var v => Parser (Type v) -> Parser (Type v) +forall :: Var v => Parser (S v) (Type v) -> Parser (S v) (Type v) forall rec = do (void . token $ string "forall") <|> void (token (char '∀')) vars <- some $ token varName @@ -54,13 +58,13 @@ forall rec = do t <- rec pure $ Type.forall' (fmap Text.pack vars) t -varName :: Parser String +varName :: Parser s String varName = do name <- wordyId keywords guard (isLower . head $ name) pure name -typeName :: Parser String +typeName :: Parser s String typeName = do name <- wordyId keywords guard (isUpper . head $ name) @@ -76,7 +80,7 @@ keywords = ["forall", "∀"] -- f first more = maybe first (first++) more -- more = (:) <$> char '.' <*> qualifiedTypeName -literal :: Var v => Parser (Type v) +literal :: Var v => Parser (S v) (Type v) literal = scope "literal" . token $ asum [ Type.lit Type.Number <$ string "Number" , Type.lit Type.Text <$ string "Text" diff --git a/shared/tests/Unison/Test/TermParser.hs b/shared/tests/Unison/Test/TermParser.hs index dff496bfa..7b7129c9f 100644 --- a/shared/tests/Unison/Test/TermParser.hs +++ b/shared/tests/Unison/Test/TermParser.hs @@ -34,7 +34,7 @@ parseFail (s, reason) = testCase ("`" ++ s ++ "` shouldn't parse: " ++ reason) $ assertBool "should not have parsed" $ case parseTerm s of Fail {} -> True; - Succeed _ n _ -> n == length s; + Succeed _ _ n -> n == length s; tests :: TestTree tests = testGroup "TermParser" $ (parse <$> shouldPass) From cef44c2daa7d9db51110fda3dbbb10ed9fb57e52 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Fri, 26 Aug 2016 21:35:43 -0400 Subject: [PATCH 28/61] parser now supports type aliases in lists of bindings, syntax `alias Nums = Vector Numbers;` --- shared/src/Unison/Parser.hs | 37 ++++++++++++++---------- shared/src/Unison/TermParser.hs | 25 +++++++++++++--- shared/src/Unison/Type.hs | 4 +++ shared/src/Unison/TypeParser.hs | 10 +++++-- shared/src/Unison/Typechecker/Context.hs | 15 +++++++--- 5 files changed, 66 insertions(+), 25 deletions(-) diff --git a/shared/src/Unison/Parser.hs b/shared/src/Unison/Parser.hs index 15732b955..a1c9942e1 100644 --- a/shared/src/Unison/Parser.hs +++ b/shared/src/Unison/Parser.hs @@ -119,16 +119,16 @@ haskellLineComment :: Parser s () haskellLineComment = void $ string "--" *> takeWhile "-- comment" (/= '\n') lineErrorUnless :: String -> Parser s a -> Parser s a -lineErrorUnless s = commitFail . scope s +lineErrorUnless s = commit . scope s + +currentLine' :: Env s -> String +currentLine' (Env overall i s cur) = before ++ restOfLine where + -- this grabs the current line up to current offset, i + before = reverse . Prelude.takeWhile (/= '\n') . reverse . take i $ overall + restOfLine = Prelude.takeWhile (/= '\n') cur currentLine :: Parser s String -currentLine = - Parser $ \(Env overall i s cur) -> - let - -- this grabs the current line up to current offset, i - l = reverse . Prelude.takeWhile (/= '\n') . reverse . take i $ overall - restOfLine = Prelude.takeWhile (/= '\n') cur - in Succeed (l ++ restOfLine) s 0 +currentLine = Parser $ \env -> Succeed (currentLine' env) (state env) 0 parenthesized :: Parser s a -> Parser s a parenthesized p = lp *> body <* rp @@ -148,6 +148,15 @@ takeWhile1 msg f = scope msg . Parser $ \(Env _ _ s cur) -> in if null hd then Fail [] False else Succeed hd s (length hd) +-- todo: newline not immediately preceded by semicolon is an error +-- unless following line indentation level is greater +-- foo +-- x y z +-- (not allowed, must format as) +-- foo +-- x y z +-- or if semicolon is needed - foo; x y z + whitespace :: Parser s () whitespace = void $ takeWhile "whitespace" Char.isSpace @@ -160,18 +169,15 @@ nonempty p = Parser $ \s -> case run' p s of ok -> ok scope :: String -> Parser s a -> Parser s a -scope s p = Parser $ \input -> case run' p input of - Fail e b -> Fail (s:e) b +scope s p = Parser $ \env -> case run' p env of + Fail e b -> Fail (currentLine' env : s:e) b ok -> ok -commitFail :: Parser s a -> Parser s a -commitFail p = Parser $ \input -> case run' p input of +commit :: Parser s a -> Parser s a +commit p = Parser $ \input -> case run' p input of Fail e _ -> Fail e True Succeed a s n -> Succeed a s n -failWith :: String -> Parser s a -failWith error = Parser . const $ Fail [error] False - sepBy :: Parser s a -> Parser s b -> Parser s [b] sepBy sep pb = f <$> optional (sepBy1 sep pb) where @@ -218,6 +224,7 @@ instance Monad (Parser s) where Succeed b s m -> Succeed b s (n+m) Fail e b -> Fail e b Fail e b -> Fail e b + fail msg = Parser $ const (Fail [msg] False) instance MonadPlus (Parser s) where mzero = Parser $ \_ -> Fail [] False diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index 1992a5b75..0656775de 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -19,6 +19,7 @@ import qualified Data.Set as Set import qualified Data.Text as Text import qualified Unison.ABT as ABT import qualified Unison.Term as Term +import qualified Unison.Type as Type import qualified Unison.TypeParser as TypeParser import qualified Unison.Var as Var @@ -100,7 +101,7 @@ effectBlock = (token (string "do") *> wordyId keywords) >>= go where binding = scope "binding" $ do lhs <- ABT.v' . Text.pack <$> token (wordyId keywords) eff <- token $ (True <$ string ":=") <|> (False <$ string "=") - rhs <- term + rhs <- commit term let rhs' = if eff then interpretPure rhs else qualifiedPure `Term.app` rhs pure (lhs, rhs') @@ -197,7 +198,7 @@ prefixTerm :: Var v => Parser (S v) (Term v) prefixTerm = Term.var <$> prefixVar keywords :: [String] -keywords = ["do", "let", "rec", "in", "->", ":", "=", "where"] +keywords = ["alias", "do", "let", "rec", "in", "->", ":", "=", "where"] lam :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) lam p = Term.lam'' <$> vars <* arrow <*> body @@ -212,9 +213,25 @@ prefixApp p = f <$> some p f (func:args) = Term.apps func args f [] = error "'some' shouldn't produce an empty list" +alias :: Var v => Parser (S v) () +alias = do + _ <- token (string "alias") + scope "alias" . commit $ do + (fn:params) <- some (Var.named . Text.pack <$> wordyId keywords) + _ <- token (string "=") + body <- TypeParser.type_ + semicolon + TypeParser.Aliases s <- get + let s' = (fn, apply) + apply args | length args <= length params = ABT.substs (params `zip` args) body + apply args = apply (take n args) `Type.apps` drop n args + n = length params + set (TypeParser.Aliases (s':s)) + bindings :: Var v => Parser (S v) (Term v) -> Parser (S v) [(v, Term v)] -bindings p = some (binding <* semicolon) where +bindings p = do s0 <- get; some (binding <* semicolon) <* set s0 where binding = do + _ <- optional alias typ <- optional (typedecl <* semicolon) (name, args) <- ( (\arg1 op arg2 -> (op,[arg1,arg2])) <$> prefixVar <*> infixVar <*> prefixVar) @@ -224,7 +241,7 @@ bindings p = some (binding <* semicolon) where Nothing -> pure $ mkBinding name args body Just (nameT, typ) | name == nameT -> case mkBinding name args body of (v,body) -> pure (v, Term.ann body typ) - | otherwise -> failWith ("The type signature for ‘" ++ show (Var.name nameT) ++ "’ lacks an accompanying binding") + | otherwise -> fail ("The type signature for ‘" ++ show (Var.name nameT) ++ "’ lacks an accompanying binding") mkBinding f [] body = (f, body) mkBinding f args body = (f, Term.lam'' args body) diff --git a/shared/src/Unison/Type.hs b/shared/src/Unison/Type.hs index 556dbb677..33f6a157c 100644 --- a/shared/src/Unison/Type.hs +++ b/shared/src/Unison/Type.hs @@ -12,6 +12,7 @@ module Unison.Type where import Data.Aeson (ToJSON(..), FromJSON(..)) import Data.Aeson.TH +import Data.List import Data.Set (Set) import Data.Text (Text) import GHC.Generics @@ -143,6 +144,9 @@ builtin = ref . Reference.Builtin app :: Ord v => Type v -> Type v -> Type v app f arg = ABT.tm (App f arg) +apps :: Ord v => Type v -> [Type v] -> Type v +apps f = foldl' app f + arrow :: Ord v => Type v -> Type v -> Type v arrow i o = ABT.tm (Arrow i o) diff --git a/shared/src/Unison/TypeParser.hs b/shared/src/Unison/TypeParser.hs index 7ac6a718c..e6195d281 100644 --- a/shared/src/Unison/TypeParser.hs +++ b/shared/src/Unison/TypeParser.hs @@ -7,7 +7,7 @@ import Control.Applicative ((<|>), some, many) import Data.Char (isUpper, isLower, isAlpha) import Data.Foldable (asum) import Data.Functor -import Data.List (foldl1') +import Data.List import Unison.Parser import Unison.Type (Type) import Unison.Var (Var) @@ -44,7 +44,13 @@ type2 = app typeLeaf -- "TypeA TypeB TypeC" app :: Ord v => Parser (S v) (Type v) -> Parser (S v) (Type v) -app rec = fmap (foldl1' Type.app) (some rec) +app rec = get >>= \(Aliases aliases) -> do + (hd:tl) <- some rec + pure $ case hd of + Type.Var' v -> case lookup v aliases of + Nothing -> foldl' Type.app hd tl + Just apply -> apply tl + _ -> foldl' Type.app hd tl arrow :: Ord v => Parser (S v) (Type v) -> Parser (S v) (Type v) arrow rec = foldr1 Type.arrow <$> sepBy1 (token $ string "->") rec diff --git a/shared/src/Unison/Typechecker/Context.hs b/shared/src/Unison/Typechecker/Context.hs index 09445defa..6474a3b6b 100644 --- a/shared/src/Unison/Typechecker/Context.hs +++ b/shared/src/Unison/Typechecker/Context.hs @@ -491,11 +491,18 @@ synthesize e = scope ("synth: " ++ show e) $ go e where go Term.Blank' = do v <- freshVar pure $ Type.forall (TypeVar.Universal v) (Type.universal v) - go (Term.Ann' (Term.Ref' _) t) = - -- innermost Ref annotation assumed to be correctly provided by `synthesizeClosed` - pure (ABT.vmap TypeVar.Universal t) + go (Term.Ann' (Term.Ref' _) t) = case ABT.freeVars t of + s | Set.null s -> + -- innermost Ref annotation assumed to be correctly provided by `synthesizeClosed` + pure (ABT.vmap TypeVar.Universal t) + s | otherwise -> + fail $ "type annotation contains free variables " ++ show (map Var.name (Set.toList s)) go (Term.Ref' h) = fail $ "unannotated reference: " ++ show h - go (Term.Ann' e' t) = case ABT.vmap TypeVar.Universal t of t -> t <$ check e' t -- Anno + go (Term.Ann' e' t) = case ABT.freeVars t of + s | Set.null s -> + case ABT.vmap TypeVar.Universal t of t -> t <$ check e' t -- Anno + s | otherwise -> + fail $ "type annotation contains free variables " ++ show (map Var.name (Set.toList s)) go (Term.Lit' l) = pure (synthLit l) -- 1I=> go (Term.App' f arg) = do -- ->E ft <- synthesize f; ctx <- getContext From 50367dd5c6024fcf57b02a33052b3380eed8b6ad Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 28 Aug 2016 00:05:09 -0400 Subject: [PATCH 29/61] qualified operator infix syntax - `a ==_Text b`, and removed use of camel case in Unison --- node/src/Unison/Runtime/ExtraBuiltins.hs | 50 +++++------ node/tests/Unison/Test/Html.hs | 6 +- shared/src/Unison/Node/Builtin.hs | 108 ++++++++++------------- shared/src/Unison/Parser.hs | 29 +++--- shared/src/Unison/Parsers.hs | 39 ++------ shared/tests/Unison/Test/Interpreter.hs | 36 ++++---- shared/tests/Unison/Test/TermParser.hs | 9 +- unison-src/base.u | 9 +- unison-src/extra.u | 20 ++--- 9 files changed, 136 insertions(+), 170 deletions(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 773fe0e9a..fd70759da 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -171,34 +171,34 @@ make logger blockStore crypto = do in (r, Just (I.Primop 3 op), type', prefix "Index.insert#") -- Html - , let r = R.Builtin "Html.getLinks" + , let r = R.Builtin "Html.get-links" op [html] = do html' <- whnf html pure $ case html' of Term.Text' h -> Term.vector' . Vector.fromList . map linkToTerm $ Html.getLinks h x -> Term.ref r `Term.app` x - op _ = fail "Html.getLinks unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Vector Html.Link", prefix "Html.getLinks") - , let r = R.Builtin "Html.getHref" + op _ = fail "Html.get-links unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Vector Html.Link", prefix "Html.get-links") + , let r = R.Builtin "Html.get-href" op [link] = do link' <- whnf link pure $ case link' of Link' href _ -> Term.lit (Term.Text href) x -> Term.ref r `Term.app` x - op _ = fail "Html.getHref unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Html.Link -> Text", prefix "Html.getHref") - , let r = R.Builtin "Html.getDescription" + op _ = fail "Html.get-href unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Html.Link -> Text", prefix "Html.get-href") + , let r = R.Builtin "Html.get-description" op [link] = do link' <- whnf link pure $ case link' of Link' _ d -> Term.lit (Term.Text d) x -> Term.ref r `Term.app` x - op _ = fail "Html.getDescription unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Html.Link -> Text", prefix "Html.getDescription") + op _ = fail "Html.get-description unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Html.Link -> Text", prefix "Html.get-description") -- Http - , let r = R.Builtin "Http.getUrl#" + , let r = R.Builtin "Http.get-url#" op [url] = do url <- whnf url case url of @@ -233,41 +233,41 @@ make logger blockStore crypto = do op _ = fail "Hash.erase" t = "forall a . Hash a -> Hash Unit" in (r, Just (I.Primop 1 op), unsafeParseType t, prefix "Hash.erase") - , let r = R.Builtin "Hash.equal" + , let r = R.Builtin "Hash.==" op [h1,h2] = do Term.App' _ (Term.Text' r1) <- whnf h1 Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 == r2 then true else false - op _ = fail "Hash.equal" - in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.equal") - , let r = R.Builtin "Hash.lessThan" + op _ = fail "Hash.==" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.==") + , let r = R.Builtin "Hash.<" op [h1,h2] = do Term.App' _ (Term.Text' r1) <- whnf h1 Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 < r2 then true else false - op _ = fail "Hash.lessThan" - in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.lessThan") - , let r = R.Builtin "Hash.lessThanOrEqual" + op _ = fail "Hash.<" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.<") + , let r = R.Builtin "Hash.<=" op [h1,h2] = do Term.App' _ (Term.Text' r1) <- whnf h1 Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 <= r2 then true else false - op _ = fail "Hash.lessThanOrEqual" - in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.lessThanOrEqual") - , let r = R.Builtin "Hash.greaterThan" + op _ = fail "Hash.<=" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.<=") + , let r = R.Builtin "Hash.>" op [h1,h2] = do Term.App' _ (Term.Text' r1) <- whnf h1 Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 > r2 then true else false - op _ = fail "Hash.greaterThan" - in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.greaterThan") - , let r = R.Builtin "Hash.greaterThanOrEqual" + op _ = fail "Hash.>" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.>") + , let r = R.Builtin "Hash.>=" op [h1,h2] = do Term.App' _ (Term.Text' r1) <- whnf h1 Term.App' _ (Term.Text' r2) <- whnf h2 pure $ if r1 >= r2 then true else false - op _ = fail "Hash.greaterThanOrEqual" - in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.greaterThanOrEqual") + op _ = fail "Hash.>=" + in (r, Just (I.Primop 2 op), hashCompareTyp, prefix "Hash.>=") , let r = R.Builtin "Hash.Order" in (r, Nothing, unsafeParseType "∀ a . Order (Hash a)", prefix "Hash.Order") ]) diff --git a/node/tests/Unison/Test/Html.hs b/node/tests/Unison/Test/Html.hs index 910c36985..4e967db32 100644 --- a/node/tests/Unison/Test/Html.hs +++ b/node/tests/Unison/Test/Html.hs @@ -42,10 +42,10 @@ tests = testGroup "html" unisonEvaluate :: (TestNode, String -> TermV) -> Assertion unisonEvaluate (testNode, parse) = do let inputPath = [P.Fn] - getLinksTerm = parse $ "Html.getLinks \"" ++ testHTML2 ++ "\"" + getLinksTerm = parse $ "Html.get-links \"" ++ testHTML2 ++ "\"" linkTerm = EB.link (Term.text "link.html") (Term.text "description") - getLink = Term.ref (R.Builtin "Html.getHref") `Term.app` linkTerm - getDescription = Term.ref (R.Builtin "Html.getDescription") `Term.app` linkTerm + getLink = Term.ref (R.Builtin "Html.get-href") `Term.app` linkTerm + getDescription = Term.ref (R.Builtin "Html.get-description") `Term.app` linkTerm desiredLinks = Term.vector [linkTerm] desiredHref = Term.text "link.html" desiredDescription = Term.text "description" diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 7a32ad96d..c58015556 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -64,10 +64,6 @@ makeBuiltins logger whnf = True -> true g x y = sym `Term.app` x `Term.app` y _ -> error "unpossible" - strict r n = Just (I.Primop n f) - where f args = reapply <$> traverse whnf (take n args) - where reapply args' = Term.ref r `apps` args' `apps` drop n args - apps f args = foldl Term.app f args string2 :: Term V -> (Text -> Text -> Text) -> I.Primop (N.Noted IO) V string2 sym f = I.Primop 2 $ \xs -> case xs of [x,y] -> g <$> whnf x <*> whnf y @@ -104,9 +100,6 @@ makeBuiltins logger whnf = typ = "∀ a . Text -> a -> a" in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Debug.watch") - , let r = R.Builtin "Color.rgba" - in (r, strict r 4, unsafeParseType "Number -> Number -> Number -> Number -> Color", prefix "rgba") - -- Boolean , let r = R.Builtin "True" in (r, Nothing, Type.builtin "Boolean", prefix "True") @@ -153,24 +146,24 @@ makeBuiltins logger whnf = in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "if") -- Number - , let r = R.Builtin "Number.plus" + , let r = R.Builtin "Number.+" in (r, Just (numeric2 (Term.ref r) (+)), numOpTyp, assoc 4 "+") - , let r = R.Builtin "Number.minus" + , let r = R.Builtin "Number.-" in (r, Just (numeric2 (Term.ref r) (-)), numOpTyp, opl 4 "-") - , let r = R.Builtin "Number.times" + , let r = R.Builtin "Number.*" in (r, Just (numeric2 (Term.ref r) (*)), numOpTyp, assoc 5 "*") - , let r = R.Builtin "Number.divide" + , let r = R.Builtin "Number./" in (r, Just (numeric2 (Term.ref r) (/)), numOpTyp, opl 5 "/") - , let r = R.Builtin "Number.greaterThan" - in (r, Just (numericCompare (Term.ref r) (>)), numCompareTyp, opl 3 ">") - , let r = R.Builtin "Number.lessThan" - in (r, Just (numericCompare (Term.ref r) (<)), numCompareTyp, opl 3 "<") - , let r = R.Builtin "Number.greaterThanOrEqual" - in (r, Just (numericCompare (Term.ref r) (>=)), numCompareTyp, opl 3 ">=") - , let r = R.Builtin "Number.lessThanOrEqual" - in (r, Just (numericCompare (Term.ref r) (<=)), numCompareTyp, opl 3 "<=") - , let r = R.Builtin "Number.equal" - in (r, Just (numericCompare (Term.ref r) (==)), numCompareTyp, opl 3 "==") + , let r = R.Builtin "Number.>" + in (r, Just (numericCompare (Term.ref r) (>)), numCompareTyp, opl 3 "Number.>") + , let r = R.Builtin "Number.<" + in (r, Just (numericCompare (Term.ref r) (<)), numCompareTyp, opl 3 "Number.<") + , let r = R.Builtin "Number.>=" + in (r, Just (numericCompare (Term.ref r) (>=)), numCompareTyp, opl 3 "Number.>=") + , let r = R.Builtin "Number.<=" + in (r, Just (numericCompare (Term.ref r) (<=)), numCompareTyp, opl 3 "Number.<=") + , let r = R.Builtin "Number.==" + in (r, Just (numericCompare (Term.ref r) (==)), numCompareTyp, opl 3 "Number.==") , let r = R.Builtin "Number.Order" in (r, Nothing, unsafeParseType "Order Number", prefix "Number.Order") @@ -195,25 +188,25 @@ makeBuiltins logger whnf = Term.Distributed' (Term.Node node) <- whnf node pure $ Term.remote (Remote.Step (Remote.At node term)) op _ = fail "Remote.at unpossible" - in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.at", prefix "at") + in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.at", prefix "Remote.at") , let r = R.Builtin "Remote.here" op [] = pure $ Term.remote (Remote.Step (Remote.Local (Remote.Here))) op _ = fail "Remote.here unpossible" - in (r, Just (I.Primop 0 op), remoteSignatureOf "Remote.here", prefix "here") + in (r, Just (I.Primop 0 op), remoteSignatureOf "Remote.here", prefix "Remote.here") , let r = R.Builtin "Remote.spawn" op [] = pure $ Term.remote (Remote.Step (Remote.Local Remote.Spawn)) op _ = fail "Remote.spawn unpossible" - in (r, Just (I.Primop 0 op), remoteSignatureOf "Remote.spawn", prefix "spawn") + in (r, Just (I.Primop 0 op), remoteSignatureOf "Remote.spawn", prefix "Remote.spawn") , let r = R.Builtin "Remote.send" op [c, v] = do Term.Distributed' (Term.Channel c) <- whnf c pure $ Term.remote (Remote.Step (Remote.Local (Remote.Send c v))) op _ = fail "Remote.send unpossible" - in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.send", prefix "send") + in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.send", prefix "Remote.send") , let r = R.Builtin "Remote.channel" op [] = pure $ Term.remote (Remote.Step (Remote.Local Remote.CreateChannel)) op _ = fail "Remote.channel unpossible" - in (r, Just (I.Primop 0 op), remoteSignatureOf "Remote.channel", prefix "channel") + in (r, Just (I.Primop 0 op), remoteSignatureOf "Remote.channel", prefix "Remote.channel") , let r = R.Builtin "Remote.bind" op [g, r] = do r <- whnf r @@ -224,18 +217,18 @@ makeBuiltins logger whnf = Term.Distributed' (Term.Remote (Remote.Bind s f)) -> pure $ Term.remote (Remote.Bind s (kcomp f g)) _ -> fail $ "Remote.bind given a value that was not a Remote: " ++ show r op _ = fail "Remote.bind unpossible" - in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.bind", prefix "bind") + in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.bind", prefix "Remote.bind") , let r = R.Builtin "Remote.pure" op [a] = pure $ Term.remote (Remote.Step (Remote.Local (Remote.Pure a))) op _ = fail "unpossible" - in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.pure", prefix "pure") + in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.pure", prefix "Remote.pure") , let r = R.Builtin "Remote.map" op [f, r] = pure $ Term.builtin "Remote.bind" `Term.app` (Term.lam' ["x"] $ Term.remote (Remote.Step . Remote.Local . Remote.Pure $ f `Term.app` Term.var' "x")) `Term.app` r op _ = fail "unpossible" - in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.map", prefix "map") + in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.map", prefix "Remote.map") , let r = R.Builtin "Remote.receive-async" op [chan, timeout] = do Term.Number' seconds <- whnf timeout @@ -254,36 +247,24 @@ makeBuiltins logger whnf = Term.Distributed' (Term.Remote r) <- whnf r pure $ Term.remote (Remote.Step (Remote.Local (Remote.Fork r))) op _ = fail "unpossible" - in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.fork", prefix "fork") - - , let r = R.Builtin "Symbol.Symbol" - in (r, Nothing, unsafeParseType "Text -> Fixity -> Number -> Symbol", prefix "Symbol") + in (r, Just (I.Primop 1 op), remoteSignatureOf "Remote.fork", prefix "Remote.fork") -- Text , let r = R.Builtin "Text.concatenate" in (r, Just (string2 (Term.ref r) mappend), strOpTyp, prefixes ["concatenate", "Text"]) - , let r = R.Builtin "Text.equal" - in (r, Just (string2' (Term.ref r) (==)), textCompareTyp, prefix "Text.equal") - , let r = R.Builtin "Text.lessThan" - in (r, Just (string2' (Term.ref r) (<)), textCompareTyp, prefix "Text.lessThan") - , let r = R.Builtin "Text.lessThanOrEqual" - in (r, Just (string2' (Term.ref r) (<=)), textCompareTyp, prefix "Text.lessThanOrEqual") - , let r = R.Builtin "Text.greaterThan" - in (r, Just (string2' (Term.ref r) (>)), textCompareTyp, prefix "Text.greaterThan") - , let r = R.Builtin "Text.greaterThanOrEqual" - in (r, Just (string2' (Term.ref r) (>=)), textCompareTyp, prefix "Text.greaterThanOrEqual") + , let r = R.Builtin "Text.==" + in (r, Just (string2' (Term.ref r) (==)), textCompareTyp, prefix "Text.==") + , let r = R.Builtin "Text.<" + in (r, Just (string2' (Term.ref r) (<)), textCompareTyp, prefix "Text.<") + , let r = R.Builtin "Text.<=" + in (r, Just (string2' (Term.ref r) (<=)), textCompareTyp, prefix "Text.<=") + , let r = R.Builtin "Text.>" + in (r, Just (string2' (Term.ref r) (>)), textCompareTyp, prefix "Text.>") + , let r = R.Builtin "Text.>=" + in (r, Just (string2' (Term.ref r) (>=)), textCompareTyp, prefix "Text.>=") , let r = R.Builtin "Text.Order" in (r, Nothing, unsafeParseType "Order Text", prefix "Text.Order") - , let r = R.Builtin "Text.left" - in (r, Nothing, alignmentT, prefixes ["left", "Text"]) - , let r = R.Builtin "Text.right" - in (r, Nothing, alignmentT, prefixes ["right", "Text"]) - , let r = R.Builtin "Text.center" - in (r, Nothing, alignmentT, prefixes ["center", "Text"]) - , let r = R.Builtin "Text.justify" - in (r, Nothing, alignmentT, prefixes ["justify", "Text"]) - -- Pair , let r = R.Builtin "Pair" in (r, Nothing, unsafeParseType "forall a b . a -> b -> Pair a b", prefix "Pair") @@ -294,7 +275,7 @@ makeBuiltins logger whnf = Term.Apps' (Term.Builtin' "Pair") [a,b] -> whnf (f `Term.apps` [a,b]) p -> fail $ "expected pair, got: " ++ show p op _ = error "Pair.fold unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a b c . (a -> b -> c) -> Pair a b -> c", prefix "fold") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a b c . (a -> b -> c) -> Pair a b -> c", prefix "Pair.fold") -- Either , let r = R.Builtin "Either.Left" @@ -309,7 +290,7 @@ makeBuiltins logger whnf = | tag == "Either.Right" -> whnf (fb `Term.app` aOrB) | otherwise -> error "type errror" op _ = error "Either.fold unpossible" - in (r, Just (I.Primop 3 op), unsafeParseType "forall a b r . (a -> r) -> (b -> r) -> Either a b -> r", prefix "fold") + in (r, Just (I.Primop 3 op), unsafeParseType "forall a b r . (a -> r) -> (b -> r) -> Either a b -> r", prefix "Either.fold") -- Optional , let r = R.Builtin "Optional.None" @@ -341,17 +322,18 @@ makeBuiltins logger whnf = (Term.Vector' a, Term.Vector' b) -> Term.vector' (a `mappend` b) (a,b) -> Term.ref r `Term.app` a `Term.app` b op _ = fail "Vector.concatenate unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a . Vector a -> Vector a -> Vector a", prefix "concatenate") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a . Vector a -> Vector a -> Vector a", prefix "Vector.concatenate") , let r = R.Builtin "Vector.empty" op [] = pure $ Term.vector mempty op _ = fail "Vector.empty unpossible" - in (r, Just (I.Primop 0 op), unsafeParseType "forall a . Vector a", prefix "empty") + in (r, Just (I.Primop 0 op), unsafeParseType "forall a . Vector a", prefix "Vector.empty") , let r = R.Builtin "Vector.range" op [start,stop] = do Term.Number' start <- whnf start Term.Number' stop <- whnf stop let num = Term.num . fromIntegral - pure $ Term.vector' (Vector.fromList . map num $ [floor start .. floor stop-1]) + ns = [floor start .. floor stop - (1 :: Int)] + pure $ Term.vector' (Vector.fromList . map num $ ns) op _ = fail "Vector.range unpossible" typ = unsafeParseType "Number -> Number -> Vector Number" in (r, Just (I.Primop 2 op), typ, prefix "Vector.range") @@ -370,16 +352,15 @@ makeBuiltins logger whnf = typ = "∀ a b . Vector a -> Vector b -> Vector (a,b)" in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Vector.zip") , let r = R.Builtin "Vector.sort" - op [ord,f,v] = do + op [_,f,v] = do Term.Vector' vs <- whnf v ks <- traverse (whnf . Term.app f) vs - Term.Builtin' ord <- whnf ord let sortableVs = Vector.zip ks vs f' (Term.Text' x, _) (Term.Text' y, _) = x `compare` y f' (Term.Number' x, _) (Term.Number' y, _) = x `compare` y - f' (Term.App' (Term.Builtin' "Hash") (Term.Ref' r1), _) - (Term.App' (Term.Builtin' "Hash") (Term.Ref' r2), _) = r1 `compare` r2 + f' (Term.App' (Term.Builtin' "Hash") (Term.Text' r1), _) + (Term.App' (Term.Builtin' "Hash") (Term.Text' r2), _) = r1 `compare` r2 f' x y = error $ "don't know how to compare: " ++ show x ++ " " ++ show y pure . Term.vector . fmap snd $ sortBy f' (Vector.toList sortableVs) op _ = fail "Vector.sort unpossible" @@ -439,7 +420,7 @@ makeBuiltins logger whnf = _ -> pure $ Term.ref r `Term.app` vec op _ = fail "Vector.fold-left unpossible" typ = "forall a b . (b -> a -> b) -> b -> Vector a -> b" - in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "fold-left") + in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "Vector.fold-left") , let r = R.Builtin "Vector.map" op [f,vec] = do vecr <- whnf vec @@ -455,7 +436,7 @@ makeBuiltins logger whnf = Term.Vector' tl -> Term.vector' (Vector.cons hd tl) tl -> Term.ref r `Term.app` hd `Term.app` tl op _ = fail "Vector.prepend unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a . a -> Vector a -> Vector a", prefix "prepend") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a . a -> Vector a -> Vector a", prefix "Vector.prepend") , let r = R.Builtin "Vector.single" op [hd] = pure $ Term.vector (pure hd) op _ = fail "Vector.single unpossible" @@ -469,6 +450,7 @@ numOpTyp :: Type V numOpTyp = unsafeParseType "Number -> Number -> Number" numCompareTyp :: Type V numCompareTyp = unsafeParseType "Number -> Number -> Boolean" +textCompareTyp :: Type V textCompareTyp = unsafeParseType "Text -> Text -> Boolean" strOpTyp :: Type V strOpTyp = unsafeParseType "Text -> Text -> Text" diff --git a/shared/src/Unison/Parser.hs b/shared/src/Unison/Parser.hs index a1c9942e1..7e18c870b 100644 --- a/shared/src/Unison/Parser.hs +++ b/shared/src/Unison/Parser.hs @@ -57,7 +57,7 @@ unsafeGetSucceed r = case r of string :: String -> Parser s String string s = Parser $ \env -> if s `isPrefixOf` (currentInput env) then Succeed s (state env) (length s) - else Fail ["expected '" ++ s ++ "', got " ++ takeLine (currentInput env)] False + else Fail ["expected " ++ s ++ ", got " ++ takeLine (currentInput env)] False takeLine :: String -> String takeLine = Prelude.takeWhile (/= '\n') @@ -65,7 +65,7 @@ takeLine = Prelude.takeWhile (/= '\n') char :: Char -> Parser s Char char c = Parser $ \env -> if listToMaybe (currentInput env) == Just c then Succeed c (state env) 1 - else Fail ["expected '" ++ show c ++ "', got " ++ takeLine (currentInput env)] False + else Fail ["expected " ++ show c ++ ", got " ++ takeLine (currentInput env)] False one :: (Char -> Bool) -> Parser s Char one f = Parser $ \env -> case (currentInput env) of @@ -98,9 +98,12 @@ identifier' charTests stringTests = do guard (all ($ i) stringTests) pure i --- a wordyId isn't all digits, and isn't all symbols +-- a wordyId isn't all digits, isn't all symbols, and isn't a symbolyId wordyId :: [String] -> Parser s String -wordyId keywords = token $ f <$> sepBy1 dot id +wordyId keywords = do + op <- (False <$ symbolyId keywords) <|> pure True + guard op + token $ f <$> sepBy1 dot id where dot = char '.' id = identifier [any (not . Char.isDigit), any Char.isAlphaNum, (`notElem` keywords)] @@ -108,9 +111,12 @@ wordyId keywords = token $ f <$> sepBy1 dot id -- a symbolyId is all symbols symbolyId :: [String] -> Parser s String -symbolyId keywords = token $ identifier' - [notReservedChar, not . Char.isSpace, \c -> Char.isSymbol c || Char.isPunctuation c] - [(`notElem` keywords)] +symbolyId keywords = scope "operator" . token $ do + op <- identifier' + [notReservedChar, (/= '_'), not . Char.isSpace, \c -> Char.isSymbol c || Char.isPunctuation c] + [(`notElem` keywords)] + qual <- optional (char '_' *> wordyId keywords) + pure $ maybe op (\qual -> qual ++ "." ++ op) qual token :: Parser s a -> Parser s a token p = p <* ignored @@ -148,15 +154,6 @@ takeWhile1 msg f = scope msg . Parser $ \(Env _ _ s cur) -> in if null hd then Fail [] False else Succeed hd s (length hd) --- todo: newline not immediately preceded by semicolon is an error --- unless following line indentation level is greater --- foo --- x y z --- (not allowed, must format as) --- foo --- x y z --- or if semicolon is needed - foo; x y z - whitespace :: Parser s () whitespace = void $ takeWhile "whitespace" Char.isSpace diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index 0f546a4d5..ec597e5ab 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -69,37 +69,21 @@ data Builtin = Builtin Text -- e.g. Builtin "()" -- aka default imports termBuiltins :: Var v => [(v, Term v)] termBuiltins = (Var.named *** Term.ref) <$> ( - [ Alias "+" "Number.plus" - , Alias "-" "Number.minus" - , Alias "*" "Number.times" - , Alias "/" "Number.divide" - , Alias ">" "Number.greaterThan" - , Alias "<" "Number.lessThan" - , Alias ">=" "Number.greaterThanOrEqual" - , Alias "<=" "Number.lessThanOrEqual" - , Alias "==" "Number.equal" - , Alias "if" "Boolean.if" - , Builtin "True" + [ Builtin "True" , Builtin "False" , Builtin "()" + , Builtin "Either.Right" + , Builtin "Either.Left" , Alias "unit" "()" , Alias "Unit" "()" , Alias "Some" "Optional.Some" , Alias "None" "Optional.None" - , Alias "Left" "Either.Left" - , Alias "Right" "Either.Right" - , Builtin "Either.fold" - , Builtin "Optional.fold" - , Builtin "Pair.fold" - , Builtin "Pair" - , AliasFromModule "Vector" - ["single", "prepend", "map", "fold-left", "concatenate", "append", "empty"] [] - , AliasFromModule "Text" - ["concatenate", "left", "right", "center", "justify"] [] - , AliasFromModule "Remote" - ["fork", "receive", "receiveAsync", "pure", "bind", "channel", "send", "here", "at", "spawn"] [] - , AliasFromModule "Color" ["rgba"] [] - , AliasFromModule "Symbol" ["Symbol"] [] + , Alias "+" "Number.+" + , Alias "-" "Number.-" + , Alias "*" "Number.*" + , Alias "/" "Number./" + , AliasFromModule "Vector" ["single"] [] + , AliasFromModule "Remote" ["pure", "bind", "pure", "fork"] [] ] >>= unpackAliases) where unpackAliases :: Builtin -> [(Text, R.Reference)] @@ -122,12 +106,7 @@ typeBuiltins = (Var.named *** Type.lit) <$> , ("Optional", Type.Optional) , builtin "Either" , builtin "Pair" - -- ??? - , builtin "Symbol" , builtin "Order" - , builtin "Alignment" - , builtin "Color" - , builtin "Fixity" -- kv store , builtin "Index" -- html diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index d62a8b272..065a63fe8 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -17,12 +17,12 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "let x = 2; y = 3 ; x + y;;" "5" , t "if False 0 1" "1" , t "if True 12 13" "12" - , t "1 > 0" "True" - , t "1 == 1" "True" - , t "2 == 0" "False" - , t "1 < 2" "True" - , t "1 <= 1" "True" - , t "1 >= 1" "True" + , t "1 >_Number 0" "True" + , t "1 ==_Number 1" "True" + , t "2 ==_Number 0" "False" + , t "1 <_Number 2" "True" + , t "1 <=_Number 1" "True" + , t "1 >=_Number 1" "True" , t "True `or` False" "True" , t "False `or` True" "True" , t "True `or` True" "True" @@ -33,15 +33,15 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "False `and` False" "False" , t "not False" "True" , t "not True" "False" - , t "let rec fac n = if (n == 0) 1 (n * fac (n - 1)); fac 5;;" "120" - , t "let rec ping n = if (n >= 10) n (pong (n + 1)); pong n = ping (n + 1); ping 0;;" + , t "let rec fac n = if (n ==_Number 0) 1 (n * fac (n - 1)); fac 5;;" "120" + , t "let rec ping n = if (n >=_Number 10) n (pong (n + 1)); pong n = ping (n + 1); ping 0;;" "10" , t "let id x = x; g = id 42; p = id \"hi\" ; g;;" "42" , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" ; g;;" "42" , t "(let id x = x; id;; : forall a . a -> a) 42" "42" - , t "Optional.map ((+) 1) (Some 1)" "Optional.Some (1 + 1)" - , t "Either.fold ((+) 1) ((+) 2) (Either.Left 1)" "2" - , t "Either.fold ((+) 1) ((+) 2) (Either.Right 1)" "3" + , t "Optional.map ((+) 1) (Some 1)" "Some (1 + 1)" + , t "Either.fold ((+) 1) ((+) 2) (Left 1)" "2" + , t "Either.fold ((+) 1) ((+) 2) (Right 1)" "3" , t "Either.swap (Left 1)" "Either.Right 1" , t "Pair.fold (x y -> x) (1, 2)" "1" , t "const 41 0" "41" @@ -49,13 +49,13 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "2nd (1,2 + 1,3,4)" "3" , t "identity <| (1 + 1)" "2" , t "(1 + 1) |> identity" "2" - , t "if (Text.equal \"hi\" \"hi\") 1 2" "1" - , t "if (Text.lessThan \"hi\" \"hiya\") 1 2" "1" - , t "if (Text.lessThanOrEqual \"hi\" \"hiya\") 1 2" "1" - , t "if (Text.greaterThan \"hiya\" \"hi\") 1 2" "1" - , t "if (Text.greaterThanOrEqual \"hiya\" \"hi\") 1 2" "1" - , t "if (Text.greaterThanOrEqual \"hi\" \"hi\") 1 2" "1" - , t "if (Text.lessThanOrEqual \"hi\" \"hi\") 1 2" "1" + , t "if (\"hi\" ==_Text \"hi\") 1 2" "1" + , t "if (\"hi\" <_Text \"hiya\") 1 2" "1" + , t "if (\"hi\" <=_Text \"hiya\") 1 2" "1" + , t "if (\"hiya\" >_Text \"hi\") 1 2" "1" + , t "if (\"hiya\" >=_Text \"hi\") 1 2" "1" + , t "if (\"hi\" >=_Text \"hi\") 1 2" "1" + , t "if (\"hi\" <=_Text \"hi\") 1 2" "1" , t "Vector.reverse [1,2,3]" "[3,2,1]" , t "Vector.reverse Vector.empty" "[]" , t "Vector.fold-right Vector.prepend Vector.empty [1,2,3]" "[1,2,3]" diff --git a/shared/tests/Unison/Test/TermParser.hs b/shared/tests/Unison/Test/TermParser.hs index 7b7129c9f..170c4d74b 100644 --- a/shared/tests/Unison/Test/TermParser.hs +++ b/shared/tests/Unison/Test/TermParser.hs @@ -62,12 +62,13 @@ tests = testGroup "TermParser" $ (parse <$> shouldPass) , ("1+1", onenone) , ("1+1", onenone) , ("1+ 1", app (var' "1+") one) - , ("1 +1", app one (var' "+1")) + -- todo: failing + -- , ("1 +1", app one (var' "+1")) , ("[1+1]", vector [onenone]) , ("\"hello\"", hello) , ("_", blank) , ("a", a) - , ("Number.plus", numberplus) + , ("(+_Number)", numberplus) , ("Number.Other.plus", var' "Number.Other.plus") , ("f -> Remote.bind (#V-fXHD3-N0E= Remote.pure f)", remoteMap) , ("1:Int", ann one int) @@ -139,8 +140,8 @@ tests = testGroup "TermParser" $ (parse <$> shouldPass) f = var' "f" g = var' "g" plus = var' "+" - plus' x y = builtin "Number.plus" `app` x `app` y - numberplus = builtin "Number.plus" + plus' x y = builtin "Number.+" `app` x `app` y + numberplus = builtin "Number.+" remotepure = builtin "Remote.pure" remoteMap = lam' ["f"] (builtin "Remote.bind" `app` (derived' sampleHash64 `app` remotepure `app` var' "f")) onenone = var' "1+1" diff --git a/unison-src/base.u b/unison-src/base.u index 43ee42139..8594af8e5 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -42,7 +42,7 @@ Vector.fold-balanced : ∀ a . (a -> a -> a) -> a -> Vector a -> a; Vector.fold-balanced plus zero vs = let rec go plus zero vs = - if (Vector.size vs <= 2) + if (Vector.size vs <=_Number 2) (Vector.fold-left plus zero vs) (let p = Vector.split vs; go plus zero (1st p) `plus` go plus zero (2nd p);;); @@ -138,6 +138,13 @@ Optional.getOr a = Optional.fold a identity; Optional.somes : ∀ a . Vector (Optional a) -> Vector a; Optional.somes = Vector.bind (Optional.fold Vector.empty Vector.single); +Optional.lift2 : ∀ a b c . (a -> b -> c) -> Optional a -> Optional b -> Optional c; +Optional.lift2 f a b = do Optional + a := a; + b := b; + pure (f a b);; +; + Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; Either.map f = Either.fold Left (f `then` Right); diff --git a/unison-src/extra.u b/unison-src/extra.u index 78feec933..c234b943b 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -3,31 +3,31 @@ Index.empty : ∀ k v . Remote (Index k v); Index.empty = Remote.map Index.empty# Remote.here; Index.keys : ∀ k v . Index k v -> Remote (Vector k); -Index.keys = Index.fromUnsafe Index.keys#; +Index.keys = Index.from-unsafe Index.keys#; Index.1st-key : ∀ k v . Index k v -> Remote (Optional k); -Index.1st-key = Index.fromUnsafe Index.1st-key#; +Index.1st-key = Index.from-unsafe Index.1st-key#; Index.increment : ∀ k v . k -> Index k v -> Remote (Optional k); -Index.increment k = Index.fromUnsafe (Index.increment# k); +Index.increment k = Index.from-unsafe (Index.increment# k); Index.lookup : ∀ k v . k -> Index k v -> Remote (Optional v); -Index.lookup k = Index.fromUnsafe (Index.lookup# k); +Index.lookup k = Index.from-unsafe (Index.lookup# k); Index.delete : ∀ k v . k -> Index k v -> Remote Unit; -Index.delete k = Index.fromUnsafe (Index.delete# k); +Index.delete k = Index.from-unsafe (Index.delete# k); Index.insert : ∀ k v . k -> v -> Index k v -> Remote Unit; -Index.insert k v = Index.fromUnsafe (Index.insert# k v); +Index.insert k v = Index.from-unsafe (Index.insert# k v); -Index.fromUnsafe : ∀ k v r . (Text -> r) -> Index k v -> Remote r; -Index.fromUnsafe f ind = let +Index.from-unsafe : ∀ k v r . (Text -> r) -> Index k v -> Remote r; +Index.from-unsafe f ind = let p = Index.representation# ind; Remote.map f (Remote.at (1st p) (2nd p));; ; -Http.getUrl : Text -> Remote (Either Text Text); -Http.getUrl url = Remote.map Http.getUrl# (Remote.pure url); +Http.get-url : Text -> Remote (Either Text Text); +Http.get-url url = Remote.map Http.getUrl# (Remote.pure url); hash! : ∀ a . a -> Remote (Hash a); hash! a = Remote.map hash# (Remote.pure a); From 30d9d89fe1e4d5739e7ecf4f2dcb073ae3df3c77 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 28 Aug 2016 15:40:36 -0400 Subject: [PATCH 30/61] Much nicer ordering support, including arbitrary pairs, inverting orders, etc --- shared/src/Unison/Node/Builtin.hs | 115 +++++++++++++++++++++--- shared/src/Unison/Parsers.hs | 6 +- shared/tests/Unison/Test/Interpreter.hs | 6 ++ unison-src/base.u | 12 +++ 4 files changed, 125 insertions(+), 14 deletions(-) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index c58015556..eb91074c7 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -77,9 +77,13 @@ makeBuiltins logger whnf = g x y = sym `Term.app` x `Term.app` y _ -> error "unpossible" in map (\(r, o, t, m) -> Builtin r o t m) - [ let r = R.Builtin "()" + [ -- Unit type + let r = R.Builtin "()" in (r, Nothing, unitT, prefix "()") + , let r = R.Builtin "Unit.Order" + in (r, Nothing, unsafeParseType "Order Unit", prefix "Unit.Order") + -- debugging printlns , let r = R.Builtin "Debug.log"; op [msg,logged,a] = do Term.Text' msg <- whnf msg @@ -276,6 +280,8 @@ makeBuiltins logger whnf = p -> fail $ "expected pair, got: " ++ show p op _ = error "Pair.fold unpossible" in (r, Just (I.Primop 2 op), unsafeParseType "forall a b c . (a -> b -> c) -> Pair a b -> c", prefix "Pair.fold") + , let r = R.Builtin "Pair.Order" + in (r, Nothing, unsafeParseType "forall a b . Order a -> Order b -> Order (Pair a b)", prefix "Pair.Order") -- Either , let r = R.Builtin "Either.Left" @@ -351,21 +357,17 @@ makeBuiltins logger whnf = op _ = fail "Vector.zip unpossible" typ = "∀ a b . Vector a -> Vector b -> Vector (a,b)" in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Vector.zip") - , let r = R.Builtin "Vector.sort" - op [_,f,v] = do + , let r = R.Builtin "Vector.sort-keyed" + op [f,v] = do Term.Vector' vs <- whnf v ks <- traverse (whnf . Term.app f) vs - let - sortableVs = Vector.zip ks vs - f' (Term.Text' x, _) (Term.Text' y, _) = x `compare` y - f' (Term.Number' x, _) (Term.Number' y, _) = x `compare` y - f' (Term.App' (Term.Builtin' "Hash") (Term.Text' r1), _) - (Term.App' (Term.Builtin' "Hash") (Term.Text' r2), _) = r1 `compare` r2 - f' x y = error $ "don't know how to compare: " ++ show x ++ " " ++ show y + ks <- pure $ fmap extractKey ks + let sortableVs = Vector.zip ks vs + f' (a, _) (b, _) = a `compare` b pure . Term.vector . fmap snd $ sortBy f' (Vector.toList sortableVs) - op _ = fail "Vector.sort unpossible" - typ = "∀ a k . Order k -> (a -> k) -> Vector a -> Vector a" - in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "Vector.sort") + op _ = fail "Vector.sort-keyed unpossible" + typ = "∀ a k . (a -> Order.Key k) -> Vector a -> Vector a" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Vector.sort-keyed") , let r = R.Builtin "Vector.size" op [v] = do Term.Vector' vs <- whnf v @@ -441,8 +443,95 @@ makeBuiltins logger whnf = op [hd] = pure $ Term.vector (pure hd) op _ = fail "Vector.single unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "forall a . a -> Vector a", prefix "Vector.single") + + , let r = R.Builtin "Order.invert" + in (r, Nothing, unsafeParseType "forall a . Order a -> Order a", prefix "Order.invert") + + , let r = R.Builtin "Less" + in (r, Nothing, unsafeParseType "Comparison", prefix "Less") + , let r = R.Builtin "Greater" + in (r, Nothing, unsafeParseType "Comparison", prefix "Greater") + , let r = R.Builtin "Equal" + in (r, Nothing, unsafeParseType "Comparison", prefix "Equal") + , let r = R.Builtin "Comparison.fold" + op [lt,eq,gt,c] = do + Term.Builtin' c <- whnf c + case Text.head c of + 'L' -> whnf lt + 'E' -> whnf eq + 'G' -> whnf gt + _ -> fail $ "Comparison.fold not one of {Less,Equal,Greater}" ++ show c + op _ = error "Comparison.fold unpossible" + in (r, Just (I.Primop 4 op), unsafeParseType "∀ r . r -> r -> r -> Comparison -> r", prefix "Comparison.fold") + + , let r = R.Builtin "Order.Key.compare" + op [a,b] = do + a <- whnf a + b <- whnf b + pure $ case compareKeys a b of + LT -> Term.builtin "Less" + EQ -> Term.builtin "Equal" + GT -> Term.builtin "Greater" + op _ = error "Order.Key.compare unpossible" + typ = "∀ a . Order.Key a -> Order.Key a -> Comparison" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Order.Key.compare") + + , let r = R.Builtin "Order.key" + flip ts = (map neg (ts []) ++) where + neg (Term.Text' t) = Term.text (Text.reverse t) + neg (Term.Number' n) = Term.num (negate n) + neg t@(Term.Builtin' _) = t + neg t = error $ "don't know how to negate " ++ show t + op' ord a = do + ord <- whnf ord + case ord of + Term.App' (Term.Builtin' invert) ord + | invert == "Order.invert" -> flip <$> op' ord a + Term.Builtin' b + | b == "Text.Order" -> do a <- whnf a; pure (a:) + | b == "Number.Order" -> do a <- whnf a; pure (a:) + | b == "Hash.Order" -> do Term.App' _ a <- whnf a; pure (a:) + | b == "Unit.Order" -> do a <- whnf a; pure (a:) + | otherwise -> fail $ "unrecognized order type: " ++ Text.unpack b + Term.Apps' (Term.Builtin' pair) [ord1, ord2] + | pair == "Pair.Order" -> do + Term.Apps' _ [a,b] <- whnf a + (.) <$> op' ord1 a <*> op' ord2 b + | otherwise -> fail $ "unrecognized order type: " ++ Text.unpack pair + op [ord,a] = Term.app (Term.builtin "Order.Key") + . foldr Term.app unitRef + . ($ []) + <$> op' ord a + op _ = fail "Order.key unpossible" + in (r, Just (I.Primop 2 op), unsafeParseType "forall a . Order a -> a -> Order.Key a", prefix "Order.key") ] +extractKey :: Term V -> [Either Double Text] +extractKey (Term.App' _ t1) = go t1 where + go (Term.Builtin' u) = [] + go (Term.App' (Term.Text' t) tl) = Right t : go tl + go (Term.App' (Term.Number' n) tl) = Left n : go tl + go (Term.App' (Term.Builtin' b) tl) = Right b : go tl + go _ = error $ "don't know what to do with this in extractKey: " ++ show t1 +extractKey t = error $ "not a key: " ++ show t + +compareKeys :: Term V -> Term V -> Ordering +compareKeys (Term.App' _ t1) (Term.App' _ t2) = go t1 t2 where + go (Term.Builtin' u) (Term.Builtin' u2) = u `compare` u2 + go (Term.App' h1 t1) (Term.App' h2 t2) = + let go' :: Ord a => a -> a -> Ordering + go' a a2 = case a `compare` a2 of + EQ -> go t1 t2 + done -> done + in + case (h1,h2) of + (Term.Text' h1, Term.Text' h2) -> go' h1 h2 + (Term.Number' h1, Term.Number' h2) -> go' h1 h2 + (Term.Builtin' h1, Term.Builtin' h2) -> go' h1 h2 + go (Term.App' _ _) _ = GT + go _ _ = LT +compareKeys _ _ = error "not a key" + -- type helpers alignmentT :: Ord v => Type v alignmentT = Type.ref (R.Builtin "Alignment") diff --git a/shared/src/Unison/Parsers.hs b/shared/src/Unison/Parsers.hs index ec597e5ab..03e499d0b 100644 --- a/shared/src/Unison/Parsers.hs +++ b/shared/src/Unison/Parsers.hs @@ -74,6 +74,9 @@ termBuiltins = (Var.named *** Term.ref) <$> ( , Builtin "()" , Builtin "Either.Right" , Builtin "Either.Left" + , Builtin "Greater" + , Builtin "Less" + , Builtin "Equal" , Alias "unit" "()" , Alias "Unit" "()" , Alias "Some" "Optional.Some" @@ -107,6 +110,8 @@ typeBuiltins = (Var.named *** Type.lit) <$> , builtin "Either" , builtin "Pair" , builtin "Order" + , builtin "Comparison" + , builtin "Order.Key" -- kv store , builtin "Index" -- html @@ -114,7 +119,6 @@ typeBuiltins = (Var.named *** Type.lit) <$> -- distributed , builtin "Channel" , builtin "Duration" - , builtin "Future" , builtin "Remote" , builtin "Node" -- hashing diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 065a63fe8..0fd129ac2 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -23,6 +23,11 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "1 <_Number 2" "True" , t "1 <=_Number 1" "True" , t "1 >=_Number 1" "True" + , t "Comparison.fold 1 0 0 Less" "1" + , t "Comparison.fold 0 1 0 Equal" "1" + , t "Comparison.fold 0 0 1 Greater" "1" + , t "Order.compare (Order.invert <| Order.tuple2 Number.Order Number.Order) (1,2) (1,3)" "Greater" + , t "Order.compare (Order.invert <| Order.tuple2 Number.Order Number.Order) (2,1) (1,3)" "Less" , t "True `or` False" "True" , t "False `or` True" "True" , t "True `or` True" "True" @@ -68,6 +73,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.range 0 0" "[]" , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" , t "Vector.sort Number.Order identity [5,2,1,3,4]" "[1,2,3,4,5]" + , t "Vector.sort (Order.invert Number.Order) identity [5,2,1,3,4]" "[5,4,3,2,1]" , t "Vector.bind 2nd (Vector.zip [1,2,3] [[1],[2],[3]])" "[1,2,3]" , t "Vector.all? identity [True,True,True,True]" "True" , t "Vector.all? identity [True,False,True,True]" "False" diff --git a/unison-src/base.u b/unison-src/base.u index 8594af8e5..09e72bb48 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -27,6 +27,15 @@ rest p = Pair.fold (x y -> y) p; 4th = rest `then` (rest `then` (rest `then` first)); 5th = rest `then` (rest `then` (rest `then` (rest `then` first))); +Order.compare : ∀ a . Order a -> a -> a -> Comparison; +Order.compare o a1 a2 = Order.Key.compare (Order.key o a1) (Order.key o a2); + +Order.tuple2 : ∀ a b . Order a -> Order b -> Order (a,b); +Order.tuple2 a b = Pair.Order a (Pair.Order b Unit.Order); + +Order.tuple3 : ∀ a b c . Order a -> Order b -> Order c -> Order (a,b,c); +Order.tuple3 a b c = Pair.Order a (Pair.Order b (Pair.Order c Unit.Order)); + Vector.bind : ∀ a b . (a -> Vector b) -> Vector a -> Vector b; Vector.bind f v = Vector.fold-balanced Vector.concatenate Vector.empty (Vector.map f v); @@ -52,6 +61,9 @@ Vector.fold-balanced plus zero vs = Vector.all? : ∀ a . (a -> Boolean) -> Vector a -> Boolean; Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); +Vector.sort : ∀ k a . Order k -> (a -> k) -> Vector a -> Vector a; +Vector.sort ok f v = Vector.sort-keyed (f `then` Order.key ok) v; + Remote.transfer : Node -> Remote Unit; Remote.transfer node = Remote.at node unit; From 45e6615b9cd18ea4d640c8a65954c48dddc38cb3 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 28 Aug 2016 16:15:44 -0400 Subject: [PATCH 31/61] Order.ignore --- shared/src/Unison/Node/Builtin.hs | 3 +++ shared/tests/Unison/Test/Interpreter.hs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index eb91074c7..56e2784ce 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -446,6 +446,8 @@ makeBuiltins logger whnf = , let r = R.Builtin "Order.invert" in (r, Nothing, unsafeParseType "forall a . Order a -> Order a", prefix "Order.invert") + , let r = R.Builtin "Order.ignore" + in (r, Nothing, unsafeParseType "forall a . Order a", prefix "Order.ignore") , let r = R.Builtin "Less" in (r, Nothing, unsafeParseType "Comparison", prefix "Less") @@ -492,6 +494,7 @@ makeBuiltins logger whnf = | b == "Number.Order" -> do a <- whnf a; pure (a:) | b == "Hash.Order" -> do Term.App' _ a <- whnf a; pure (a:) | b == "Unit.Order" -> do a <- whnf a; pure (a:) + | b == "Order.ignore" -> pure id | otherwise -> fail $ "unrecognized order type: " ++ Text.unpack b Term.Apps' (Term.Builtin' pair) [ord1, ord2] | pair == "Pair.Order" -> do diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 0fd129ac2..2fc5c28ab 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -28,6 +28,8 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Comparison.fold 0 0 1 Greater" "1" , t "Order.compare (Order.invert <| Order.tuple2 Number.Order Number.Order) (1,2) (1,3)" "Greater" , t "Order.compare (Order.invert <| Order.tuple2 Number.Order Number.Order) (2,1) (1,3)" "Less" + , t "Order.compare (Order.tuple2 Number.Order Order.ignore) (1,2) (1,3)" "Equal" + , t "Order.compare (Order.tuple2 Order.ignore Number.Order ) (2,2) (1,3)" "Less" , t "True `or` False" "True" , t "False `or` True" "True" , t "True `or` True" "True" From c0663e2fe802107a712d92c326de8617626bb1ca Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 28 Aug 2016 16:45:37 -0400 Subject: [PATCH 32/61] Adding indexed traversals WIP --- unison-src/base.u | 9 +++++++++ unison-src/extra.u | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/unison-src/base.u b/unison-src/base.u index 09e72bb48..f2274a5d2 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -36,6 +36,15 @@ Order.tuple2 a b = Pair.Order a (Pair.Order b Unit.Order); Order.tuple3 : ∀ a b c . Order a -> Order b -> Order c -> Order (a,b,c); Order.tuple3 a b c = Pair.Order a (Pair.Order b (Pair.Order c Unit.Order)); +Order.by-1st : ∀ a b . Order a -> Order (Pair a b); +Order.by-1st a = Pair.Order a Order.ignore; + +Order.by-2nd : ∀ a b c . Order b -> Order (Pair a (Pair b c)); +Order.by-2nd b = Pair.Order Order.ignore (Pair.Order b Order.ignore); + +Order.by-3rd : ∀ a b c d . Order c -> Order (Pair a (Pair b (Pair c d))); +Order.by-3rd c = Pair.Order Order.ignore (Pair.Order Order.ignore (Pair.Order c Order.ignore)); + Vector.bind : ∀ a b . (a -> Vector b) -> Vector a -> Vector b; Vector.bind f v = Vector.fold-balanced Vector.concatenate Vector.empty (Vector.map f v); diff --git a/unison-src/extra.u b/unison-src/extra.u index c234b943b..5be8e5ec6 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -26,6 +26,24 @@ Index.from-unsafe f ind = let Remote.map f (Remote.at (1st p) (2nd p));; ; +alias IndexedTraversal k v = + ( Remote (Optional k) + , k -> Remote (Optional v) + , k -> Remote (Optional k)); + +Index.traversal : ∀ k v . Index k v -> IndexedTraversal (k, Hash k) v; +Index.traversal ind = let + add-hash = Optional.map (k -> (k, hash# k)); + ( Index.1st-key ind |> Remote.map add-hash + , k -> Index.lookup (1st k) ind + , k -> Index.increment (1st k) ind |> Remote.map add-hash + );; +; + +IndexedTraversal.empty : ∀ k v . IndexedTraversal k v; +IndexedTraversal.empty = + (Remote.pure None, const (Remote.pure None), const (Remote.pure None)); + Http.get-url : Text -> Remote (Either Text Text); Http.get-url url = Remote.map Http.getUrl# (Remote.pure url); From 02227a56e94e4e17d6621da009714186d2bc95a4 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sun, 28 Aug 2016 23:51:13 -0400 Subject: [PATCH 33/61] IndexedTraversals done + some other std library tweaks --- node/src/Unison/Runtime/ExtraBuiltins.hs | 4 +- shared/src/Unison/Node/Builtin.hs | 2 +- unison-src/base.u | 48 +++++++++++++++------- unison-src/extra.u | 51 +++++++++++++++++++++++- 4 files changed, 87 insertions(+), 18 deletions(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index fd70759da..a92655f22 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -208,8 +208,8 @@ make logger blockStore crypto = do Right x -> right $ Term.text x Left x -> left . Term.text . Text.pack $ show x x -> pure $ Term.ref r `Term.app` x - op _ = fail "Http.getUrl# unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "Http.getUrl#") + op _ = fail "Http.get-url# unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "Http.get-url#") -- Hashing -- add erase, comparison functions diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 56e2784ce..f32ece1f9 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -319,7 +319,7 @@ makeBuiltins logger whnf = Term.Vector' init -> Term.vector' (Vector.snoc init last) init -> Term.ref r `Term.app` last `Term.app` init op _ = fail "Vector.append unpossible" - in (r, Just (I.Primop 2 op), unsafeParseType "forall a . a -> Vector a -> Vector a", prefix "append") + in (r, Just (I.Primop 2 op), unsafeParseType "forall a . a -> Vector a -> Vector a", prefix "Vector.append") , let r = R.Builtin "Vector.concatenate" op [a,b] = do ar <- whnf a diff --git a/unison-src/base.u b/unison-src/base.u index f2274a5d2..ca49dd2d7 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -27,6 +27,9 @@ rest p = Pair.fold (x y -> y) p; 4th = rest `then` (rest `then` (rest `then` first)); 5th = rest `then` (rest `then` (rest `then` (rest `then` first))); +set-1st : ∀ a a2 b . a2 -> Pair a b -> Pair a2 b; +set-1st new-first p = Pair new-first (rest p); + Order.compare : ∀ a . Order a -> a -> a -> Comparison; Order.compare o a1 a2 = Order.Key.compare (Order.key o a1) (Order.key o a2); @@ -73,15 +76,39 @@ Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); Vector.sort : ∀ k a . Order k -> (a -> k) -> Vector a -> Vector a; Vector.sort ok f v = Vector.sort-keyed (f `then` Order.key ok) v; -Remote.transfer : Node -> Remote Unit; -Remote.transfer node = Remote.at node unit; - Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; Remote.map f = Remote.bind (f `then` Remote.pure); +Remote.map2 : ∀ a b c . (a -> b -> c) -> Remote a -> Remote b -> Remote c; +Remote.map2 f a b = do Remote + a := a; + b := b; + pure (f a b);; +; + +Remote.map2' : ∀ a b c . (a -> b -> Remote c) -> Remote a -> Remote b -> Remote c; +Remote.map2' f a b = Remote.map2 f a b |> Remote.join; + +Remote.join : ∀ a . Remote (Remote a) -> Remote a; +Remote.join = Remote.bind identity; + Remote.replicate : ∀ a . Number -> Remote a -> Remote (Vector a); Remote.replicate n r = Remote.sequence (Vector.replicate n r); +Remote.unfold : ∀ s a . s -> (s -> Remote (Optional (a, s))) -> Remote (Vector a); +Remote.unfold s f = let rec + go s acc = do Remote + ht := f s; + ht |> Optional.fold + (pure acc) + (ht -> go (2nd ht) (Vector.append (1st ht) acc));; + ; + go s Vector.empty;; +; + +Remote.transfer : Node -> Remote Unit; +Remote.transfer node = Remote.at node unit; + Remote.race : ∀ a . Duration -> Vector (Remote a) -> Remote a; Remote.race timeout rs = do Remote here := Remote.here; @@ -102,13 +129,6 @@ Remote.timeout timeout r = do Remote Remote.delay timeout; pure None;; ]; -Remote.lift2 : ∀ a b c . (a -> b -> c) -> Remote a -> Remote b -> Remote c; -Remote.lift2 f a b = do Remote - a := a; - b := b; - pure (f a b);; - ; - Remote.at' : ∀ a . Node -> Remote a -> Remote a; Remote.at' node r = do Remote Remote.transfer node; r;;; @@ -123,13 +143,13 @@ Remote.start timeout r = do Remote Remote.traverse : ∀ a b . (a -> Remote b) -> Vector a -> Remote (Vector b); Remote.traverse f vs = - Vector.fold-balanced (Remote.lift2 Vector.concatenate) + Vector.fold-balanced (Remote.map2 Vector.concatenate) (Remote.pure Vector.empty) (Vector.map (f `then` Remote.map Vector.single) vs); Remote.sequence : ∀ a . Vector (Remote a) -> Remote (Vector a); Remote.sequence vs = - Vector.fold-balanced (Remote.lift2 Vector.concatenate) + Vector.fold-balanced (Remote.map2 Vector.concatenate) (Remote.pure Vector.empty) (Vector.map (Remote.map Vector.single) vs); @@ -159,8 +179,8 @@ Optional.getOr a = Optional.fold a identity; Optional.somes : ∀ a . Vector (Optional a) -> Vector a; Optional.somes = Vector.bind (Optional.fold Vector.empty Vector.single); -Optional.lift2 : ∀ a b c . (a -> b -> c) -> Optional a -> Optional b -> Optional c; -Optional.lift2 f a b = do Optional +Optional.map2 : ∀ a b c . (a -> b -> c) -> Optional a -> Optional b -> Optional c; +Optional.map2 f a b = do Optional a := a; b := b; pure (f a b);; diff --git a/unison-src/extra.u b/unison-src/extra.u index 5be8e5ec6..f6eb7eebb 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -31,6 +31,15 @@ alias IndexedTraversal k v = , k -> Remote (Optional v) , k -> Remote (Optional k)); +IndexedTraversal.1st-key : ∀ k v . IndexedTraversal k v -> Remote (Optional k); +IndexedTraversal.1st-key t = 1st t; + +IndexedTraversal.lookup : ∀ k v . k -> IndexedTraversal k v -> Remote (Optional v); +IndexedTraversal.lookup k t = 2nd t k; + +IndexedTraversal.increment : ∀ k v . k -> IndexedTraversal k v -> Remote (Optional k); +IndexedTraversal.increment k t = 3rd t k; + Index.traversal : ∀ k v . Index k v -> IndexedTraversal (k, Hash k) v; Index.traversal ind = let add-hash = Optional.map (k -> (k, hash# k)); @@ -44,8 +53,48 @@ IndexedTraversal.empty : ∀ k v . IndexedTraversal k v; IndexedTraversal.empty = (Remote.pure None, const (Remote.pure None), const (Remote.pure None)); +IndexedTraversal.intersect : ∀ k v . Order k + -> IndexedTraversal k v + -> IndexedTraversal k v + -> IndexedTraversal k v; +IndexedTraversal.intersect o t1 t2 = let rec + align-key k1 k2 = Optional.getOr (Remote.pure None) <| Optional.map2 + (k1 k2 -> Order.compare o k1 k2 |> Comparison.fold + (IndexedTraversal.increment k2 t1 |> Remote.bind (k1 -> align-key k1 (Some k2))) + (Remote.pure (Some k1)) + (IndexedTraversal.increment k1 t2 |> Remote.bind (k2 -> align-key (Some k1) k2)) + ) + k1 k2 + ; + 1st-key = Remote.map2' align-key (1st t1) (1st t2); + lookup k = 2nd t1 k |> Remote.bind (Optional.fold (Remote.pure None) (a -> 2nd t2 k)); + increment k = Remote.map2' align-key (3rd t1 k) (3rd t2 k); + (1st-key, lookup, increment);; +; + +IndexedTraversal.1st-entry : ∀ k v . IndexedTraversal k v -> Remote (Optional (k, v)); +IndexedTraversal.1st-entry t = IndexedTraversal.entry-at (1st t) t; + +IndexedTraversal.entry-at : ∀ k v . + Remote (Optional k) -> IndexedTraversal k v -> Remote (Optional (k, v)); +IndexedTraversal.entry-at k t = do Remote + k := k; + v := Optional.fold (pure None) (2nd t) k; + pure (Optional.map2 (k v -> (k,v)) k v);; +; + +IndexedTraversal.take : ∀ k v . Number -> IndexedTraversal k v -> Remote (Vector (k,v)); +IndexedTraversal.take n t = + Remote.unfold (t, n) (tn -> let + t = 1st tn; + n = 2nd tn; + step e = (e, (set-1st (IndexedTraversal.increment (1st e) t) t, n - 1)); + if (n <=_Number 0) (Remote.pure None) + (IndexedTraversal.1st-entry t |> Remote.map (Optional.map step));; + ); + Http.get-url : Text -> Remote (Either Text Text); -Http.get-url url = Remote.map Http.getUrl# (Remote.pure url); +Http.get-url url = Remote.map Http.get-url# (Remote.pure url); hash! : ∀ a . a -> Remote (Hash a); hash! a = Remote.map hash# (Remote.pure a); From 93c9f84b52c65942a62d790b7ac61dfc5c115c03 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 29 Aug 2016 17:12:43 -0400 Subject: [PATCH 34/61] rename Vector.split to Vector.halve, add Vector.sort' --- shared/src/Unison/Node/Builtin.hs | 6 +++--- unison-src/base.u | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index f32ece1f9..ecc0fd033 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -380,16 +380,16 @@ makeBuiltins logger whnf = pure $ Term.vector' (Vector.reverse vs) op _ = fail "Vector.reverse unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "forall a . Vector a -> Vector a", prefix "Vector.reverse") - , let r = R.Builtin "Vector.split" + , let r = R.Builtin "Vector.halve" op [v] = do Term.Vector' vs <- whnf v pure $ case Vector.null vs of True -> pair' (Term.vector []) (Term.vector []) False -> case Vector.splitAt (Vector.length vs `div` 2) vs of (x,y) -> pair' (Term.vector' x) (Term.vector' y) - op _ = fail "Vector.split unpossible" + op _ = fail "Vector.halve unpossible" typ = "forall a . Vector a -> (Vector a, Vector a)" - in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Vector.split") + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Vector.halve") , let r = R.Builtin "Vector.at" op [n,vec] = do Term.Number' n <- whnf n diff --git a/unison-src/base.u b/unison-src/base.u index ca49dd2d7..1249b8f37 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -65,7 +65,7 @@ Vector.fold-balanced plus zero vs = go plus zero vs = if (Vector.size vs <=_Number 2) (Vector.fold-left plus zero vs) - (let p = Vector.split vs; + (let p = Vector.halve vs; go plus zero (1st p) `plus` go plus zero (2nd p);;); go plus zero vs;; ; @@ -76,6 +76,9 @@ Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); Vector.sort : ∀ k a . Order k -> (a -> k) -> Vector a -> Vector a; Vector.sort ok f v = Vector.sort-keyed (f `then` Order.key ok) v; +Vector.sort' : ∀ a . Order a -> Vector a -> Vector a; +Vector.sort' o = Vector.sort o identity; + Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; Remote.map f = Remote.bind (f `then` Remote.pure); From 7b1e574ea6e1181aec3a20fc4161eeacc2356a01 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 30 Aug 2016 10:06:10 -0400 Subject: [PATCH 35/61] Optional.getOr => Optional.get-or, down with CamelCase!! --- shared/tests/Unison/Test/Interpreter.hs | 2 +- unison-src/base.u | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 2fc5c28ab..36abd3894 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -79,7 +79,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.bind 2nd (Vector.zip [1,2,3] [[1],[2],[3]])" "[1,2,3]" , t "Vector.all? identity [True,True,True,True]" "True" , t "Vector.all? identity [True,False,True,True]" "False" - , t "Optional.getOr 96 (Vector.at 1 [0,1,2,3,4])" "1" + , t "Optional.get-or 96 (Vector.at 1 [0,1,2,3,4])" "1" , t "Vector.take 0 [1,2,3]" "[]" , t "Vector.take 2 [1,2,3]" "[1,2]" , t "Vector.drop 2 [1,2,3]" "[3]" diff --git a/unison-src/base.u b/unison-src/base.u index 1249b8f37..e3d687fa0 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -176,8 +176,8 @@ Optional.bind f = Optional.fold None f; Optional.pure : ∀ a . a -> Optional a; Optional.pure = Some; -Optional.getOr : ∀ a . a -> Optional a -> a; -Optional.getOr a = Optional.fold a identity; +Optional.get-or : ∀ a . a -> Optional a -> a; +Optional.get-or a = Optional.fold a identity; Optional.somes : ∀ a . Vector (Optional a) -> Vector a; Optional.somes = Vector.bind (Optional.fold Vector.empty Vector.single); From 86efe2f2a18a9acf3f35622b46033b0f8e6d604f Mon Sep 17 00:00:00 2001 From: Sam Griffin Date: Wed, 31 Aug 2016 00:52:11 +0000 Subject: [PATCH 36/61] fix cpp preprocessing --- node/src/Container.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/node/src/Container.hs b/node/src/Container.hs index 6722bcebd..02803d47d 100644 --- a/node/src/Container.hs +++ b/node/src/Container.hs @@ -38,11 +38,11 @@ main :: IO () main = Mux.uniqueChannel >>= \rand -> let h bytes = BA.convert (hash bytes :: Digest Blake2b_512) - #ifdef leveldb +#ifdef leveldb blockstore = LDBS.make rand h "blockstore.leveldb" - #else +#else blockstore = FBS.make' rand h "blockstore" - #endif +#endif locker _ = pure held held = Lock (pure (Just (Lease (pure True) (pure ())))) mkNode _ = do -- todo: actually use node params @@ -64,11 +64,11 @@ main = Mux.uniqueChannel >>= \rand -> P.std_in = P.CreatePipe, P.std_err = P.CreatePipe } in do - #ifdef leveldb +#ifdef leveldb putStrLn "using leveldb-based block store" - #else +#else putStrLn "using file-based block store" - #endif +#endif blockstore <- blockstore send <- C.make blockstore locker protocol mkNode launchNode S.scotty 8081 $ do From 70a5c54460cbc77c43161a1531e7a1631a00828f Mon Sep 17 00:00:00 2001 From: Sam Griffin Date: Thu, 1 Sep 2016 21:02:00 +0000 Subject: [PATCH 37/61] fixed text encoding issues --- node/src/Container.hs | 3 ++- node/src/Node.hs | 2 ++ node/src/Worker.hs | 7 +++++-- node/tests/Unison/Test/NodeUtil.hs | 4 +++- shared/tests/Suite.hs | 5 ++++- shared/tests/Unison/Test/Common.hs | 4 +++- shared/unison-shared.cabal | 1 + 7 files changed, 20 insertions(+), 6 deletions(-) diff --git a/node/src/Container.hs b/node/src/Container.hs index 02803d47d..ffbd58af6 100644 --- a/node/src/Container.hs +++ b/node/src/Container.hs @@ -10,7 +10,7 @@ import Data.Bytes.Serial (serialize) import Data.Text.Encoding (decodeUtf8) import Network.HTTP.Types.Method (StdMethod(OPTIONS)) import Network.Wai.Middleware.RequestLogger (logStdoutDev) -import System.IO (hSetBinaryMode, hFlush, stdin) +import System.IO (hSetBinaryMode, hFlush, hSetEncoding, stdin, stdout, stderr, utf8) import System.Process as P import Unison.NodeProtocol.V0 (protocol) import Unison.NodeServer as NS @@ -64,6 +64,7 @@ main = Mux.uniqueChannel >>= \rand -> P.std_in = P.CreatePipe, P.std_err = P.CreatePipe } in do + mapM_ (`hSetEncoding` utf8) [stdout, stdin, stderr] #ifdef leveldb putStrLn "using leveldb-based block store" #else diff --git a/node/src/Node.hs b/node/src/Node.hs index 32c57bc8e..5c3e80b97 100644 --- a/node/src/Node.hs +++ b/node/src/Node.hs @@ -3,6 +3,7 @@ module Main where +import System.IO import Unison.Hash.Extra () import Unison.Node.Store (Store) import Unison.Reference (Reference) @@ -44,6 +45,7 @@ makeRandomAddress crypt = Address <$> C.randomBytes crypt 64 main :: IO () main = do + mapM_ (`hSetEncoding` utf8) [stdout, stdin, stderr] store' <- store logger <- L.atomic (L.atInfo L.toStandardError) let crypto = C.noop "dummypublickey" diff --git a/node/src/Worker.hs b/node/src/Worker.hs index 8fb2903a4..c462a8086 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -2,14 +2,16 @@ module Main where +import Data.Text.Encoding (decodeUtf8) import Control.Concurrent.STM.TVar import Control.Monad -import System.IO (stderr) +import System.IO (stderr, stdin, stdout, hSetEncoding, utf8) import Unison.Hash (Hash) import Unison.NodeProtocol.V0 (protocol) import Unison.NodeWorker as W import Unison.SerializationAndHashing (TermV) import qualified Control.Concurrent.STM as STM +import qualified Data.ByteString as B import qualified Data.Set as Set import qualified Data.Text as Text import qualified Data.Text.IO as Text.IO @@ -31,6 +33,7 @@ import qualified Unison.Util.Logger as L main :: IO () main = do + mapM_ (`hSetEncoding` utf8) [stdout, stdin, stderr] logger <- L.scope "worker-main" <$> Config.loggerTo stderr W.make protocol crypto (pure $ lang logger) where crypto keypair = C.noop (W.public keypair) @@ -90,7 +93,7 @@ main = do unRemote _ = Nothing remote = Term.remote loadDeclarations path node = do - txt <- Text.IO.readFile path + txt <- decodeUtf8 <$> B.readFile path let str = Text.unpack txt r <- Note.run $ Node.declare' Term.ref str node L.info logger $ "loaded " ++ path diff --git a/node/tests/Unison/Test/NodeUtil.hs b/node/tests/Unison/Test/NodeUtil.hs index b3e60cbaf..ba3fe7925 100644 --- a/node/tests/Unison/Test/NodeUtil.hs +++ b/node/tests/Unison/Test/NodeUtil.hs @@ -3,6 +3,7 @@ module Unison.Test.NodeUtil where import Control.Applicative +import Data.Text.Encoding (decodeUtf8) import Unison.Hash (Hash) import Unison.Node (Node) import Unison.Reference (Reference) @@ -11,6 +12,7 @@ import Unison.Symbol (Symbol) import Unison.Term (Term) import Unison.Type (Type) import Unison.Var (Var) +import qualified Data.ByteString as B import qualified Data.Text as Text import qualified Data.Text.IO as Text.IO import qualified System.FilePath as FP @@ -48,7 +50,7 @@ loadDeclarations :: L.Logger -> FilePath -> Node IO V Reference (Type V) (Term V loadDeclarations logger path node = do -- note - when run from repl current directory is root, but when run via stack test, current -- directory is the shared subdir - so we check both locations - txt <- Text.IO.readFile path <|> Text.IO.readFile (".." `FP.combine` path) + txt <- decodeUtf8 <$> (B.readFile path <|> B.readFile (".." `FP.combine` path)) let str = Text.unpack txt _ <- Note.run $ Node.declare' Term.ref str node L.info logger $ "loaded file: " ++ path diff --git a/shared/tests/Suite.hs b/shared/tests/Suite.hs index 97d18a473..eaa99234f 100644 --- a/shared/tests/Suite.hs +++ b/shared/tests/Suite.hs @@ -1,5 +1,6 @@ module Main where +import System.IO import Test.Tasty import qualified Unison.Test.Doc as Doc import qualified Unison.Test.Typechecker as Typechecker @@ -13,4 +14,6 @@ tests :: TestTree tests = testGroup "unison" [Doc.tests, Typechecker.tests, Term.tests, TermParser.tests, TypeParser.tests, Interpreter.tests, Components.tests] main :: IO () -main = defaultMain tests +main = do + mapM_ (`hSetEncoding` utf8) [stdout, stdin, stderr] + defaultMain tests diff --git a/shared/tests/Unison/Test/Common.hs b/shared/tests/Unison/Test/Common.hs index 26aab4190..0832ba89a 100644 --- a/shared/tests/Unison/Test/Common.hs +++ b/shared/tests/Unison/Test/Common.hs @@ -4,6 +4,7 @@ module Unison.Test.Common where import Control.Applicative import Control.Monad.IO.Class import Data.Foldable +import Data.Text.Encoding (decodeUtf8) import System.IO (FilePath) import Unison.Symbol (Symbol) import Unison.Node (Node) @@ -11,6 +12,7 @@ import Unison.Reference (Reference) import Unison.Term (Term) import Unison.Type (Type) import Unison.Views (defaultSymbol) +import qualified Data.ByteString as B import qualified Data.Map as Map import qualified Data.Text.IO as Text.IO import qualified Data.Text as Text @@ -31,7 +33,7 @@ loadDeclarations :: FilePath -> Node IO V Reference (Type V) (Term V) -> IO () loadDeclarations path node = do -- note - when run from repl current directory is root, but when run via stack test, current -- directory is the shared subdir - so we check both locations - txt <- Text.IO.readFile path <|> Text.IO.readFile (".." `FP.combine` path) + txt <- decodeUtf8 <$> (B.readFile path <|> B.readFile (".." `FP.combine` path)) let str = Text.unpack txt _ <- Note.run $ Node.declare' Term.ref str node putStrLn $ "loaded file: " ++ path diff --git a/shared/unison-shared.cabal b/shared/unison-shared.cabal index c752d81d5..7dc9db2e5 100644 --- a/shared/unison-shared.cabal +++ b/shared/unison-shared.cabal @@ -111,6 +111,7 @@ test-suite tests other-modules: build-depends: base, + bytestring, containers, filepath, tasty, From 5e7f3aff106f8c710205921ca5d9f39f751af8e2 Mon Sep 17 00:00:00 2001 From: Sam Griffin Date: Fri, 2 Sep 2016 03:27:34 +0000 Subject: [PATCH 38/61] fixed old references to Optional.getOr --- unison-src/dindex.u | 2 +- unison-src/extra.u | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unison-src/dindex.u b/unison-src/dindex.u index 2c7bd45a3..f24605433 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -71,7 +71,7 @@ DIndex.rebalance k ind = do Remote results := Remote.parallel-traverse DIndex.Max-Timeout (Index.lookup k `then` Remote.timeout t) indices; resultsHashes := Remote.traverse hash! results; uh := hash! None; - hd = uh `Optional.getOr` Vector.at 0 resultsHashes; + hd = uh `Optional.get-or` Vector.at 0 resultsHashes; eq = h1 h2 -> Hash.equal (Hash.erase h1) (Hash.erase h2); if (Vector.all? (eq hd) resultsHashes) -- all results matched, we're good diff --git a/unison-src/extra.u b/unison-src/extra.u index f6eb7eebb..3f27b01fc 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -58,7 +58,7 @@ IndexedTraversal.intersect : ∀ k v . Order k -> IndexedTraversal k v -> IndexedTraversal k v; IndexedTraversal.intersect o t1 t2 = let rec - align-key k1 k2 = Optional.getOr (Remote.pure None) <| Optional.map2 + align-key k1 k2 = Optional.get-or (Remote.pure None) <| Optional.map2 (k1 k2 -> Order.compare o k1 k2 |> Comparison.fold (IndexedTraversal.increment k2 t1 |> Remote.bind (k1 -> align-key k1 (Some k2))) (Remote.pure (Some k1)) From bfbfac7fa3481ee497af0481795ffd4fb6996d81 Mon Sep 17 00:00:00 2001 From: Sam Griffin Date: Fri, 2 Sep 2016 03:37:53 +0000 Subject: [PATCH 39/61] cleaning up warnings --- node/src/Worker.hs | 1 - node/unison-node.cabal | 85 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/node/src/Worker.hs b/node/src/Worker.hs index c462a8086..0f5b27e39 100644 --- a/node/src/Worker.hs +++ b/node/src/Worker.hs @@ -14,7 +14,6 @@ import qualified Control.Concurrent.STM as STM import qualified Data.ByteString as B import qualified Data.Set as Set import qualified Data.Text as Text -import qualified Data.Text.IO as Text.IO import qualified Unison.Config as Config import qualified Unison.Cryptography as C import qualified Unison.Node as Node diff --git a/node/unison-node.cabal b/node/unison-node.cabal index 7d8e421f1..8583378af 100644 --- a/node/unison-node.cabal +++ b/node/unison-node.cabal @@ -191,6 +191,34 @@ executable worker unison-node, unison-shared, vector + other-modules: + Unison.ABT.Extra, + Unison.Config, + Unison.Distance.Extra, + Unison.Hash.Extra, + Unison.Kind.Extra, + Unison.NodeProtocol, + Unison.NodeProtocol.V0, + Unison.NodeWorker, + Unison.Reference.Extra, + Unison.Remote.Extra, + Unison.Runtime.Block, + Unison.Runtime.ExpiringMap, + Unison.Runtime.ExtraBuiltins, + Unison.Runtime.Html, + Unison.Runtime.Http, + Unison.Runtime.Index, + Unison.Runtime.Journal, + Unison.Runtime.JournaledMap, + Unison.Runtime.Multiplex, + Unison.Runtime.Queue, + Unison.Runtime.Remote, + Unison.Runtime.ResourcePool, + Unison.Runtime.SharedResourceMap, + Unison.SerializationAndHashing, + Unison.Symbol.Extra, + Unison.Term.Extra, + Unison.Type.Extra executable container main-is: Container.hs @@ -238,6 +266,30 @@ executable container vector, wai-extra, wai-middleware-static + other-modules: + Unison.ABT.Extra, + Unison.BlockStore.FileBlockStore, + Unison.Config, + Unison.Distance.Extra, + Unison.Hash.Extra, + Unison.Kind.Extra, + Unison.NodeContainer, + Unison.NodeProtocol, + Unison.NodeProtocol.V0, + Unison.NodeServer, + Unison.Reference.Extra, + Unison.Remote.Extra, + Unison.Runtime.Block, + Unison.Runtime.ExpiringMap, + Unison.Runtime.Lock, + Unison.Runtime.Multiplex, + Unison.Runtime.Queue, + Unison.Runtime.Remote, + Unison.Runtime.SharedResourceMap, + Unison.SerializationAndHashing, + Unison.Symbol.Extra, + Unison.Term.Extra, + Unison.Type.Extra if flag(leveldb) build-depends: exceptions, leveldb-haskell @@ -291,6 +343,29 @@ executable node vector, wai-extra, wai-middleware-static + other-modules: + Unison.ABT.Extra, + Unison.BlockStore.FileBlockStore, + Unison.Distance.Extra, + Unison.Hash.Extra, + Unison.Kind.Extra, + Unison.Node.FileStore, + Unison.NodeServer, + Unison.Reference.Extra, + Unison.Remote.Extra, + Unison.Runtime.Address, + Unison.Runtime.Block, + Unison.Runtime.ExtraBuiltins, + Unison.Runtime.Html, + Unison.Runtime.Http, + Unison.Runtime.Index, + Unison.Runtime.Journal, + Unison.Runtime.JournaledMap, + Unison.Runtime.ResourcePool, + Unison.SerializationAndHashing, + Unison.Symbol.Extra, + Unison.Term.Extra, + Unison.Type.Extra if flag(leveldb) build-depends: exceptions, leveldb-haskell @@ -325,6 +400,16 @@ test-suite tests unison-node, unison-shared, vector + other-modules: + Unison.Test.BlockStore, + Unison.Test.BlockStore.FileBlockStore, + Unison.Test.BlockStore.MemBlockStore, + Unison.Test.Html, + Unison.Test.Index, + Unison.Test.Journal, + Unison.Test.NodeUtil, + Unison.Test.ResourcePool, + Unison.Test.SerializationAndHashing if flag(leveldb) build-depends: exceptions, leveldb-haskell From 1056a14ecd4288ec4c67fa229900996382791318 Mon Sep 17 00:00:00 2001 From: Sam Griffin Date: Tue, 30 Aug 2016 23:59:20 +0000 Subject: [PATCH 40/61] plaintext from html --- node/src/Unison/Runtime/ExtraBuiltins.hs | 8 ++++++++ node/src/Unison/Runtime/Html.hs | 6 +++++- node/tests/Unison/Test/Html.hs | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index a92655f22..f1018ea1d 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -180,6 +180,14 @@ make logger blockStore crypto = do x -> Term.ref r `Term.app` x op _ = fail "Html.get-links unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Vector Html.Link", prefix "Html.get-links") + , let r = R.Builtin "Html.plain-text" + op [html] = do + html' <- whnf html + pure $ case html' of + Term.Text' h -> Term.text $ Html.toPlainText h + x -> Term.ref r `Term.app` x + op _ = fail "Html.plain-text unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Text", prefix "Html.plain-text") , let r = R.Builtin "Html.get-href" op [link] = do link' <- whnf link diff --git a/node/src/Unison/Runtime/Html.hs b/node/src/Unison/Runtime/Html.hs index c48e3ba5f..c29591298 100644 --- a/node/src/Unison/Runtime/Html.hs +++ b/node/src/Unison/Runtime/Html.hs @@ -2,7 +2,7 @@ module Unison.Runtime.Html where import Data.Maybe (listToMaybe, catMaybes, mapMaybe) import Data.Text (Text, toLower, pack) -import Text.HTML.TagSoup (Tag(..), (~/=), maybeTagText, parseTags) +import Text.HTML.TagSoup (Tag(..), (~/=), maybeTagText, parseTags, innerText) import qualified Data.Text as Text data Link = Link { ref :: Text, description :: Text } deriving (Show) @@ -24,3 +24,7 @@ sectionToLink _ = Nothing getLinks :: Text -> [Link] getLinks s = mapMaybe sectionToLink . justAnchorSections $ parseTags s + +toPlainText :: Text -> Text +--toPlainText s = Text.concat . map fromTagText . filter isTagText $ parseTags s +toPlainText s = innerText $ parseTags s diff --git a/node/tests/Unison/Test/Html.hs b/node/tests/Unison/Test/Html.hs index 4e967db32..22e69fd0c 100644 --- a/node/tests/Unison/Test/Html.hs +++ b/node/tests/Unison/Test/Html.hs @@ -33,6 +33,13 @@ numlinks = let found = getLinks $ pack testHTML in if 3 == length found then pure () else fail $ "expected 3 links, got " ++ show found +plainText :: Assertion +plainText = let expected = "simple linkInside one Inside other outside one inside list Empty link" + result = toPlainText $ pack testHTML + in if expected == result + then pure () + else fail $ "got unclean html: " ++ show result + tests :: TestTree tests = testGroup "html" [ testCase "numlinks" numlinks @@ -67,6 +74,7 @@ unisonEvaluate (testNode, parse) = do nodeTests :: (TestNode, String -> TermV) -> TestTree nodeTests testNode = testGroup "html" [ testCase "numlinks" numlinks + , testCase "plainText" plainText , testCase "unisonEvaluate" (unisonEvaluate testNode) ] From e408ea1746039cdc13d48221a3f6eadf674f6061 Mon Sep 17 00:00:00 2001 From: Sam Griffin Date: Fri, 2 Sep 2016 14:37:09 +0000 Subject: [PATCH 41/61] removing commented out code --- node/src/Unison/Runtime/Html.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/node/src/Unison/Runtime/Html.hs b/node/src/Unison/Runtime/Html.hs index c29591298..e14b34de8 100644 --- a/node/src/Unison/Runtime/Html.hs +++ b/node/src/Unison/Runtime/Html.hs @@ -26,5 +26,4 @@ getLinks :: Text -> [Link] getLinks s = mapMaybe sectionToLink . justAnchorSections $ parseTags s toPlainText :: Text -> Text ---toPlainText s = Text.concat . map fromTagText . filter isTagText $ parseTags s toPlainText s = innerText $ parseTags s From 7b8f83c31ed918105d27f0bbe713748056828e9d Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 13 Sep 2016 11:42:29 -0400 Subject: [PATCH 42/61] fix TermSearchboxParser errors --- editor/src/Unison/TermSearchboxParser.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/editor/src/Unison/TermSearchboxParser.hs b/editor/src/Unison/TermSearchboxParser.hs index e0b1437a8..706da6deb 100644 --- a/editor/src/Unison/TermSearchboxParser.hs +++ b/editor/src/Unison/TermSearchboxParser.hs @@ -16,7 +16,7 @@ import qualified Data.Text as Text import qualified Unison.Term as E import qualified Unison.Var as Var -term :: Parser [Term V] +term :: Parser () [Term V] term = msum [ single . E.lit . E.Text . Text.pack <$> quotedString @@ -30,22 +30,22 @@ term = where single x = [x] -digits :: Parser String -digits = takeWhile Char.isDigit +digits :: Parser () String +digits = takeWhile "digits" Char.isDigit -digits1 :: Parser String +digits1 :: Parser () String digits1 = (:) <$> one Char.isDigit <*> digits -floatingPoint :: Parser Double +floatingPoint :: Parser () Double floatingPoint = do d <- digits1 rest <- optional (void (char '.') *> ((++) <$> pure "0." <*> (fromMaybe "0" <$> optional digits1))) pure $ read d + fromMaybe 0.0 (read <$> rest) -quotedString :: Parser String -quotedString = char '\"' *> takeWhile (\c -> c /= '\"') <* optional (char '\"') +quotedString :: Parser () String +quotedString = char '\"' *> takeWhile "quoted string" (\c -> c /= '\"') <* optional (char '\"') -intro :: Parser [Term V] +intro :: Parser () [Term V] intro = do let sym = (Var.named . Text.pack <$> token (identifier [])) <|> pure (Var.named "_") let lam v = E.lam v E.blank From 5a2ee0c5af75f34011c6d8c47567cfd410c2c440 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 13 Sep 2016 11:43:35 -0400 Subject: [PATCH 43/61] using type aliases in dindex.u --- unison-src/dindex.u | 18 ++++++++++-------- unison-src/extra.u | 4 ++++ unison-src/index.u | 6 +++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/unison-src/dindex.u b/unison-src/dindex.u index 2c7bd45a3..0b9c22331 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -6,11 +6,13 @@ DIndex.Replication-Factor = 3; DIndex.Timeout = Duration.seconds 10; DIndex.Max-Timeout = Duration.seconds 500; -DIndex.empty : ∀ k v . Remote (Index Node (Index k v)); +alias DIndex k v = Index Node (Index k v); + +DIndex.empty : ∀ k v . Remote (DIndex k v); DIndex.empty = Index.empty; -- Pick the nodes responsible for a key, using HRW hashing -DIndex.nodesForKey : ∀ k v . k -> Index Node (Index k v) -> Remote (Vector Node); +DIndex.nodesForKey : ∀ k v . k -> DIndex k v -> Remote (Vector Node); DIndex.nodesForKey k ind = do Remote nodes := Index.keys ind; hashes := Remote.traverse (node -> hash! (node, k)) nodes; @@ -21,7 +23,7 @@ DIndex.nodesForKey k ind = do Remote |> pure;; ; -DIndex.lookup : ∀ k v . k -> Index Node (Index k v) -> Remote (Optional v); +DIndex.lookup : ∀ k v . k -> DIndex k v -> Remote (Optional v); DIndex.lookup k ind = do Remote nodes := DIndex.nodesForKey k ind; localLookup = node -> (do Remote @@ -38,7 +40,7 @@ DIndex.lookup k ind = do Remote pure (Vector.at 0 rs |> Optional.bind identity);; ; -DIndex.insert : ∀ k v . k -> v -> Index Node (Index k v) -> Remote Unit; +DIndex.insert : ∀ k v . k -> v -> DIndex k v -> Remote Unit; DIndex.insert k v ind = do Remote nodes := DIndex.nodesForKey k ind; localInsert = node -> (do Remote @@ -50,21 +52,21 @@ DIndex.insert k v ind = do Remote Remote.race DIndex.Timeout <| Vector.map localInsert nodes;; ; -DIndex.join : ∀ k v . Index Node (Index k v) -> Remote Unit; +DIndex.join : ∀ k v . DIndex k v -> Remote Unit; DIndex.join ind = do Remote here := Remote.here; localInd := Index.empty; Index.insert here localInd ind;; ; -DIndex.indicesForKey : ∀ k v . k -> Index Node (Index k v) -> Remote (Vector (Index k v)); +DIndex.indicesForKey : ∀ k v . k -> DIndex k v -> Remote (Vector (Index k v)); DIndex.indicesForKey k ind = do Remote nodes := DIndex.nodesForKey k ind; indices := Remote.traverse (node -> Index.lookup node ind) nodes; pure (Optional.somes indices);; ; -DIndex.rebalance : ∀ k v . k -> Index Node (Index k v) -> Remote Unit; +DIndex.rebalance : ∀ k v . k -> DIndex k v -> Remote Unit; DIndex.rebalance k ind = do Remote indices := DIndex.indicesForKey k ind; t = DIndex.Timeout; @@ -84,7 +86,7 @@ DIndex.rebalance k ind = do Remote ov;;) ;; ; -DIndex.leave : ∀ k v . Node -> Index Node (Index k v) -> Remote Unit; +DIndex.leave : ∀ k v . Node -> DIndex k v -> Remote Unit; DIndex.leave node ind = do Remote local-ind := Index.lookup node ind; Index.delete node ind; diff --git a/unison-src/extra.u b/unison-src/extra.u index f6eb7eebb..a8f0f64b7 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -14,6 +14,10 @@ Index.increment k = Index.from-unsafe (Index.increment# k); Index.lookup : ∀ k v . k -> Index k v -> Remote (Optional v); Index.lookup k = Index.from-unsafe (Index.lookup# k); +Index.lookup-or : v -> k -> Index k v -> Remote v; +Index.lookup-or v k ind = + Remote.map (Optional.get-or v) (Index.lookup k ind); + Index.delete : ∀ k v . k -> Index k v -> Remote Unit; Index.delete k = Index.from-unsafe (Index.delete# k); diff --git a/unison-src/index.u b/unison-src/index.u index e6dc933db..439b48090 100644 --- a/unison-src/index.u +++ b/unison-src/index.u @@ -7,9 +7,9 @@ do Remote ind := do Remote Remote.transfer n1; ind := Index.empty; - Index.insert "Unison" "Rulez!!!1" ind; - Index.insert "Unison1" "Rulez!!!1" ind; + Index.insert "Alice" "Jones" ind; + Index.insert "Bob" "Smith" ind; pure ind;; ; Remote.transfer n2; - Index.keys ind;; + Index.lookup "Alice" ind;; From 0dc9e37a47cd10fffe147eda19058a78f6ff4a3b Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 13 Sep 2016 11:54:59 -0400 Subject: [PATCH 44/61] fix compile errors in dindex.u and extra.u --- unison-src/dindex.u | 30 +++++++++++++++--------------- unison-src/extra.u | 28 ++++++++++++++-------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/unison-src/dindex.u b/unison-src/dindex.u index 945215164..f1f0357c6 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -11,13 +11,13 @@ alias DIndex k v = Index Node (Index k v); DIndex.empty : ∀ k v . Remote (DIndex k v); DIndex.empty = Index.empty; --- Pick the nodes responsible for a key, using HRW hashing +-- Pick the nodes responsible for a key, using HRW hashing DIndex.nodesForKey : ∀ k v . k -> DIndex k v -> Remote (Vector Node); DIndex.nodesForKey k ind = do Remote nodes := Index.keys ind; - hashes := Remote.traverse (node -> hash! (node, k)) nodes; + hashes := Remote.traverse (node -> hash! (node, k)) nodes; (nodes `Vector.zip` hashes) - |> Vector.sort Hash.Order 2nd + |> Vector.sort Hash.Order 2nd |> Vector.take DIndex.Replication-Factor |> Vector.map 1st |> pure;; @@ -40,7 +40,7 @@ DIndex.lookup k ind = do Remote pure (Vector.at 0 rs |> Optional.bind identity);; ; -DIndex.insert : ∀ k v . k -> v -> DIndex k v -> Remote Unit; +DIndex.insert : ∀ k v . k -> v -> DIndex k v -> Remote Unit; DIndex.insert k v ind = do Remote nodes := DIndex.nodesForKey k ind; localInsert = node -> (do Remote @@ -53,7 +53,7 @@ DIndex.insert k v ind = do Remote ; DIndex.join : ∀ k v . DIndex k v -> Remote Unit; -DIndex.join ind = do Remote +DIndex.join ind = do Remote here := Remote.here; localInd := Index.empty; Index.insert here localInd ind;; @@ -61,12 +61,12 @@ DIndex.join ind = do Remote DIndex.indicesForKey : ∀ k v . k -> DIndex k v -> Remote (Vector (Index k v)); DIndex.indicesForKey k ind = do Remote - nodes := DIndex.nodesForKey k ind; + nodes := DIndex.nodesForKey k ind; indices := Remote.traverse (node -> Index.lookup node ind) nodes; pure (Optional.somes indices);; ; -DIndex.rebalance : ∀ k v . k -> DIndex k v -> Remote Unit; +DIndex.rebalance : ∀ k v . k -> DIndex k v -> Remote Unit; DIndex.rebalance k ind = do Remote indices := DIndex.indicesForKey k ind; t = DIndex.Timeout; @@ -74,15 +74,15 @@ DIndex.rebalance k ind = do Remote resultsHashes := Remote.traverse hash! results; uh := hash! None; hd = uh `Optional.get-or` Vector.at 0 resultsHashes; - eq = h1 h2 -> Hash.equal (Hash.erase h1) (Hash.erase h2); - if (Vector.all? (eq hd) resultsHashes) - -- all results matched, we're good + eq = h1 h2 -> Hash.erase h1 ==_Hash Hash.erase h2; + if (Vector.all? (eq hd) resultsHashes) + -- all results matched, we're good (pure Unit) - -- not all results matched, reinsert - (do Remote + -- not all results matched, reinsert + (do Remote ov := DIndex.lookup k ind; - Optional.fold (pure Unit) - (v -> DIndex.insert k v ind) + Optional.fold (pure Unit) + (v -> DIndex.insert k v ind) ov;;) ;; ; @@ -92,7 +92,7 @@ DIndex.leave node ind = do Remote Index.delete node ind; Optional.fold (pure Unit) - (local-ind -> do Remote + (local-ind -> do Remote keys := Index.keys local-ind; Remote.fork <| Remote.traverse (k -> DIndex.rebalance k ind) keys;;) local-ind;; diff --git a/unison-src/extra.u b/unison-src/extra.u index 1889f35cf..36b31feb5 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -9,16 +9,16 @@ Index.1st-key : ∀ k v . Index k v -> Remote (Optional k); Index.1st-key = Index.from-unsafe Index.1st-key#; Index.increment : ∀ k v . k -> Index k v -> Remote (Optional k); -Index.increment k = Index.from-unsafe (Index.increment# k); +Index.increment k = Index.from-unsafe (Index.increment# k); Index.lookup : ∀ k v . k -> Index k v -> Remote (Optional v); Index.lookup k = Index.from-unsafe (Index.lookup# k); -Index.lookup-or : v -> k -> Index k v -> Remote v; +Index.lookup-or : ∀ k v . v -> k -> Index k v -> Remote v; Index.lookup-or v k ind = Remote.map (Optional.get-or v) (Index.lookup k ind); -Index.delete : ∀ k v . k -> Index k v -> Remote Unit; +Index.delete : ∀ k v . k -> Index k v -> Remote Unit; Index.delete k = Index.from-unsafe (Index.delete# k); Index.insert : ∀ k v . k -> v -> Index k v -> Remote Unit; @@ -30,7 +30,7 @@ Index.from-unsafe f ind = let Remote.map f (Remote.at (1st p) (2nd p));; ; -alias IndexedTraversal k v = +alias IndexedTraversal k v = ( Remote (Optional k) , k -> Remote (Optional v) , k -> Remote (Optional k)); @@ -44,25 +44,25 @@ IndexedTraversal.lookup k t = 2nd t k; IndexedTraversal.increment : ∀ k v . k -> IndexedTraversal k v -> Remote (Optional k); IndexedTraversal.increment k t = 3rd t k; -Index.traversal : ∀ k v . Index k v -> IndexedTraversal (k, Hash k) v; -Index.traversal ind = let +Index.traversal : ∀ k v . Index k v -> IndexedTraversal (k, Hash k) v; +Index.traversal ind = let add-hash = Optional.map (k -> (k, hash# k)); ( Index.1st-key ind |> Remote.map add-hash , k -> Index.lookup (1st k) ind - , k -> Index.increment (1st k) ind |> Remote.map add-hash + , k -> Index.increment (1st k) ind |> Remote.map add-hash );; ; IndexedTraversal.empty : ∀ k v . IndexedTraversal k v; -IndexedTraversal.empty = +IndexedTraversal.empty = (Remote.pure None, const (Remote.pure None), const (Remote.pure None)); -IndexedTraversal.intersect : ∀ k v . Order k - -> IndexedTraversal k v - -> IndexedTraversal k v +IndexedTraversal.intersect : ∀ k v . Order k + -> IndexedTraversal k v + -> IndexedTraversal k v -> IndexedTraversal k v; -IndexedTraversal.intersect o t1 t2 = let rec - align-key k1 k2 = Optional.get-or (Remote.pure None) <| Optional.map2 +IndexedTraversal.intersect o t1 t2 = let rec + align-key k1 k2 = Optional.get-or (Remote.pure None) <| Optional.map2 (k1 k2 -> Order.compare o k1 k2 |> Comparison.fold (IndexedTraversal.increment k2 t1 |> Remote.bind (k1 -> align-key k1 (Some k2))) (Remote.pure (Some k1)) @@ -79,7 +79,7 @@ IndexedTraversal.intersect o t1 t2 = let rec IndexedTraversal.1st-entry : ∀ k v . IndexedTraversal k v -> Remote (Optional (k, v)); IndexedTraversal.1st-entry t = IndexedTraversal.entry-at (1st t) t; -IndexedTraversal.entry-at : ∀ k v . +IndexedTraversal.entry-at : ∀ k v . Remote (Optional k) -> IndexedTraversal k v -> Remote (Optional (k, v)); IndexedTraversal.entry-at k t = do Remote k := k; From 5b844512c0a3c49643e04f928291267b6a7b3516 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 14 Sep 2016 12:31:41 -0400 Subject: [PATCH 45/61] got rid of node worker processes, spawning node worker in same process --- node/src/Container.hs | 156 +++++++++++++++-------- node/src/Unison/NodeContainer.hs | 100 +++++++-------- node/src/Unison/NodeWorker.hs | 117 +++++++++-------- node/src/Unison/Runtime/ExtraBuiltins.hs | 4 +- node/src/Unison/Runtime/Multiplex.hs | 125 +++--------------- node/src/Unison/Runtime/Remote.hs | 5 +- node/src/Worker.hs | 102 --------------- node/unison-node.cabal | 72 +---------- shared/src/Unison/Util/Logger.hs | 4 +- unison-src/html.u | 2 +- unison-src/pingpong.u | 8 +- 11 files changed, 248 insertions(+), 447 deletions(-) delete mode 100644 node/src/Worker.hs diff --git a/node/src/Container.hs b/node/src/Container.hs index ffbd58af6..eb5526bbe 100644 --- a/node/src/Container.hs +++ b/node/src/Container.hs @@ -1,5 +1,6 @@ {-# Language BangPatterns #-} {-# Language OverloadedStrings #-} +{-# Language PartialTypeSignatures #-} {-# Language CPP #-} module Main where @@ -10,8 +11,8 @@ import Data.Bytes.Serial (serialize) import Data.Text.Encoding (decodeUtf8) import Network.HTTP.Types.Method (StdMethod(OPTIONS)) import Network.Wai.Middleware.RequestLogger (logStdoutDev) -import System.IO (hSetBinaryMode, hFlush, hSetEncoding, stdin, stdout, stderr, utf8) -import System.Process as P +import System.IO (stdout) +import Unison.Hash (Hash) import Unison.NodeProtocol.V0 (protocol) import Unison.NodeServer as NS import Unison.Parsers (unsafeParseTerm) @@ -22,68 +23,111 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Base64.URL as Base64 import qualified Data.ByteString.Lazy as LB import qualified Data.Bytes.Put as Put +import qualified Data.Set as Set import qualified Data.Text as Text +import qualified Unison.Config as Config +import qualified Unison.Cryptography as Cryptography +import qualified Unison.Node as Node +import qualified Unison.Node.BasicNode as BasicNode +import qualified Unison.Node.Builtin as Builtin +import qualified Unison.Node.MemStore as Store +import qualified Unison.NodeContainer as C +import qualified Unison.NodeProtocol as NP +import qualified Unison.NodeWorker as NW +import qualified Unison.Note as Note +import qualified Unison.Parsers as Parsers +import qualified Unison.Remote as Remote +import qualified Unison.Runtime.ExtraBuiltins as ExtraBuiltins +import qualified Unison.Runtime.Multiplex as Mux +import qualified Unison.Runtime.Remote as Remote +import qualified Unison.SerializationAndHashing as SAH +import qualified Unison.Term as Term +import qualified Unison.Typechecker.Components as Components +import qualified Unison.Util.Logger as L + #ifdef leveldb import qualified Unison.BlockStore.LevelDbStore as LDBS #else import qualified Unison.BlockStore.FileBlockStore as FBS #endif -import qualified Unison.NodeContainer as C -import qualified Unison.NodeProtocol as NP -import qualified Unison.Remote as R -import qualified Unison.Runtime.Multiplex as Mux -import qualified Unison.Typechecker.Components as Components main :: IO () -main = Mux.uniqueChannel >>= \rand -> - let - h bytes = BA.convert (hash bytes :: Digest Blake2b_512) +main = do + logger <- Config.loggerTo stdout + rand <- Mux.uniqueChannel + let h bytes = BA.convert (hash bytes :: Digest Blake2b_512) #ifdef leveldb - blockstore = LDBS.make rand h "blockstore.leveldb" + putStrLn "using leveldb-based block store" + blockstore <- LDBS.make rand h "blockstore.leveldb" #else - blockstore = FBS.make' rand h "blockstore" + putStrLn "using file-based block store" + blockstore <- FBS.make' rand h "blockstore" #endif - locker _ = pure held - held = Lock (pure (Just (Lease (pure True) (pure ())))) - mkNode _ = do -- todo: actually use node params - publicKey <- Put.runPutS . serialize <$> rand - pure $ R.Node "localhost" publicKey - launchNode node = do - (Just stdin, Just stdout, Just stderr, handle) <- P.createProcess_ "node-worker" cmd - hSetBinaryMode stdin True - B.hPut stdin . Put.runPutS $ do - serialize ("ignored-private-key" :: B.ByteString) - serialize node - serialize (R.Universe "local-universe") - serialize B.empty -- no sandbox specification - hFlush stdin - let proof = "not-real-delete-proof" - pure (stdin, stdout, stderr, handle, proof) - cmd = (P.shell "stack exec worker") { - P.std_out = P.CreatePipe, - P.std_in = P.CreatePipe, - P.std_err = P.CreatePipe } - in do - mapM_ (`hSetEncoding` utf8) [stdout, stdin, stderr] -#ifdef leveldb - putStrLn "using leveldb-based block store" -#else - putStrLn "using file-based block store" -#endif - blockstore <- blockstore - send <- C.make blockstore locker protocol mkNode launchNode - S.scotty 8081 $ do - S.middleware logStdoutDev - S.addroute OPTIONS (S.regex ".*") $ NS.originOptions - NS.postRoute "/compute/:nodepk" $ do - nodepk <- S.param "nodepk" - let node = R.Node "localhost" (Put.runPutS . serialize . Base64.decodeLenient $ nodepk) - programtxt <- S.body - let programstr = Text.unpack (decodeUtf8 (LB.toStrict programtxt)) - let !prog = unsafeParseTerm programstr - let !prog' = Components.minimize' prog - liftIO . putStrLn $ "parsed " ++ show prog - liftIO . putStrLn $ "parsed' " ++ show prog' - let destination = Put.runPutS (serialize node) - let pk = Mux.Packet (Mux.channelId $ NP._localEval protocol) (Put.runPutS (serialize prog')) - liftIO $ send (Mux.Packet destination (Put.runPutS (serialize pk))) + let !b0 = Builtin.makeBuiltins logger + let !crypto = Cryptography.noop "todo-real-public-key" + b1 <- ExtraBuiltins.make logger blockstore crypto + store <- Store.make + backend <- BasicNode.make SAH.hash store (\whnf -> b0 whnf ++ b1 whnf) + loadDeclarations logger "unison-src/base.u" backend + loadDeclarations logger "unison-src/extra.u" backend + loadDeclarations logger "unison-src/dindex.u" backend + let locker _ = pure held + held = Lock (pure (Just (Lease (pure True) (pure ())))) + mkNode _ = do -- todo: actually use node params + publicKey <- Put.runPutS . serialize <$> rand + pure $ Remote.Node "localhost" publicKey + lang :: Remote.Language SAH.TermV Hash + lang = Remote.Language localDependencies eval Term.app Term.node + (Term.builtin "()") Term.channel local unRemote Term.remote + local l = Term.remote (Remote.Step (Remote.Local l)) + unRemote (Term.Distributed' (Term.Remote r)) = Just r + unRemote _ = Nothing + codestore = Remote.makeCodestore blockstore :: Remote.Codestore SAH.TermV Hash + localDependencies _ = Set.empty -- todo, compute this for real + whnf e = do -- todo: may want to have this use evaluator + codestore directly + [(_,_,e)] <- Node.evaluateTerms backend [([], e)] + pure e + eval t = Note.run (whnf t) + -- evaluator = I.eval allprimops + -- allbuiltins = b0 whnf ++ b1 whnf + -- allprimops = Map.fromList [ (r, op) | Builtin.Builtin r (Just op) _ _ <- allbuiltins ] + typecheck e = do + bindings <- Note.run $ Node.allTermsByVarName Term.ref backend + L.debug logger $ "known symbols: " ++ show (map fst bindings) + let e' = Parsers.bindBuiltins bindings [] e + Note.unnote (Node.typeAt backend e' []) >>= \t -> case t of + Left note -> pure $ Left (show note) + Right _ -> pure (Right e') + launchNode logger node = do + let u = Remote.Universe "local-universe" + L.debug logger $ "launching node..." + (send, recv, isActive) <- NW.make logger protocol crypto lang node u typecheck + L.debug logger $ "...launched node" + let proof = "todo: real-delete-proof, based on node private key" + pure (send, recv, isActive, proof) + + send <- C.make blockstore locker protocol mkNode launchNode + S.scotty 8081 $ do + S.middleware logStdoutDev + S.addroute OPTIONS (S.regex ".*") $ NS.originOptions + NS.postRoute "/compute/:nodepk" $ do + nodepk <- S.param "nodepk" + let node = Remote.Node "localhost" (Put.runPutS . serialize . Base64.decodeLenient $ nodepk) + programtxt <- S.body + let programstr = Text.unpack (decodeUtf8 (LB.toStrict programtxt)) + let !prog = unsafeParseTerm programstr + let !prog' = Components.minimize' prog + liftIO $ L.info logger "parsed" + let destination = Put.runPutS (serialize node) + let pk = Mux.Packet (Mux.channelId $ NP._localEval protocol) (Put.runPutS (serialize prog')) + liftIO $ send (Mux.Packet destination (Put.runPutS (serialize pk))) + +loadDeclarations logger path node = do + txt <- decodeUtf8 <$> B.readFile path + let str = Text.unpack txt + r <- Note.run $ Node.declare' Term.ref str node + L.info logger $ "loaded " ++ path + L.debug' logger $ do + ts <- Note.run $ Node.allTermsByVarName Term.ref node + pure $ show ts + pure r diff --git a/node/src/Unison/NodeContainer.hs b/node/src/Unison/NodeContainer.hs index 169abb5ab..a647eeb29 100644 --- a/node/src/Unison/NodeContainer.hs +++ b/node/src/Unison/NodeContainer.hs @@ -1,24 +1,27 @@ +{-# Language DeriveGeneric #-} {-# Language OverloadedStrings #-} module Unison.NodeContainer where import Control.Concurrent (forkIO) import Control.Concurrent.Chan.Unagi +import Control.Concurrent.STM (STM) import Control.Exception import Control.Monad import Data.ByteString (ByteString) +import Data.Bytes.Serial (Serial) import Data.IORef -import System.IO (hClose, hFlush, Handle) +import GHC.Generics import Unison.Runtime.Remote () import qualified Control.Concurrent.Async as Async +import qualified Control.Concurrent.STM as STM import qualified Data.ByteString as B +import qualified Data.ByteArray as BA import qualified Data.ByteString.Base64.URL as Base64 import qualified Data.Bytes.Get as Get import qualified Data.Bytes.Put as Put import qualified Data.Bytes.Serial as S import qualified Data.Trie as Trie -import qualified System.Exit as Exit -import qualified System.Process as Process import qualified Unison.BlockStore as BS import qualified Unison.Config as Config import qualified Unison.NodeProtocol as P @@ -30,19 +33,22 @@ import qualified Unison.Util.Logger as L type Trie = Trie.Trie type DeleteProof = ByteString +data Keypair k = Keypair { public :: k, private :: B.ByteString } deriving Generic +instance Serial k => Serial (Keypair k) + make :: (Ord h, S.Serial h, S.Serial hash) => BS.BlockStore h -> (Remote.Node -> IO L.Lock) -> P.Protocol term hash h thash -> (ByteString -> IO Remote.Node) - -> (Remote.Node -> IO (Handle, Handle, Handle, Process.ProcessHandle, DeleteProof)) + -> (L.Logger -> Remote.Node -> IO (Maybe Mux.Packet -> IO (), IO (Maybe Mux.Packet), STM Bool, DeleteProof)) -> IO (Mux.Packet -> IO ()) -make bs nodeLock p genNode launchNodeCmd = do +make bs nodeLock p genNode launchNode = do logger <- L.scope "container" <$> Config.loggerStandardOut -- packet queue, processed by main `go` loop below (packetWrite, packetRead) <- newChan :: IO (InChan Mux.Packet, OutChan Mux.Packet) -- routing trie for packets; initially empty - routing <- newIORef (Trie.empty :: Trie (ByteString -> IO ())) + routing <- newIORef (Trie.empty :: Trie (Mux.Packet -> IO ())) (writeChan packetWrite <$) . forkIO $ let go = forever $ do @@ -67,51 +73,44 @@ make bs nodeLock p genNode launchNodeCmd = do Nothing -> pure () Just lease -> do L.info logger $ "waking up node " ++ show node - wakeup node [Mux.content packet] `finally` L.release lease + wakeup node packet `finally` L.release lease Just dest -> do L.debug logger "destination exists; routing" - safely (dest (Mux.content packet)) + safely (dest packet) nodeSeries node = BS.Series $ "node-" `mappend` Remote.publicKey node - wakeup node packets = do + wakeup node packet = do -- important: we return immediately to main loop after establishing buffer - -- to hold packets sent to this node. Actual node process is launched asynchronously + -- to hold packets sent to this node. Actual node thread launched asynchronously -- and will draw down this buffer - (toNodeWrite, toNodeRead) <- newChan :: IO (InChan ByteString, OutChan ByteString) + (toNodeWrite, toNodeRead) <- newChan :: IO (InChan (Maybe Mux.Packet), OutChan (Maybe Mux.Packet)) logger <- pure $ L.scope (show . Base64.encode . Remote.publicKey $ node) logger - let send bytes = writeChan toNodeWrite bytes - let nodebytes = Put.runPutS $ S.serialize node + let send pk = case Get.runGetS S.deserialize (Mux.content pk) of + Left err -> L.warn logger $ "packet decoding error: " ++ err + Right pk -> writeChan toNodeWrite (Just pk) + nodebytes = Put.runPutS $ S.serialize node atomicModifyIORef routing $ \t -> (Trie.insert nodebytes send t, ()) - forM_ packets send + send packet let removeRoute = atomicModifyIORef' routing $ \t -> (Trie.delete nodebytes t, ()) - -- spin up a new process for the node, which we will communicate with over standard input/output + -- spin up a new thread for the node void . forkIO . handle (\e -> L.warn logger (show (e :: SomeException)) >> removeRoute) $ do - (stdin, stdout, stderr, process, deleteProof) <- launchNodeCmd node - L.logHandleAt logger L.errorLevel stderr - -- read from the process as quickly as possible, buffering input in a queue - (fromNodeWrite, fromNodeRead) <- newChan - :: IO (InChan (Maybe Mux.Packet), OutChan (Maybe Mux.Packet)) - let write a _ = writeChan fromNodeWrite a - reader <- Async.async $ Mux.deserializeHandle stdout B.empty write - -- now that we have a handle to the process, we write to it from the `toNodeRead` queue + L.debug logger "waking.." + (write, read, isActive, deleteProof) <- launchNode logger node + L.debug logger "awakened" + + -- deregister the node when idle + _ <- Async.async $ do + STM.atomically $ do a <- isActive; when a STM.retry + L.info logger "node idle, removing route" + removeRoute + + -- thread for writing to the node, just processes the `toNodeRead` queue writer <- Async.async . forever $ do - (bytes, force) <- tryReadChan toNodeRead - bytes <- tryRead bytes >>= \bytes -> case bytes of - Nothing -> do - L.trace logger $ "flushing bytes sent to stdin of node worker" - hFlush stdin >> force -- flush buffer whenever there's a pause - Just bytes -> pure bytes -- we're saturating the channel, no need to flush manually - let nodeBytes = Put.runPutS (S.serialize node) - let numbytes = B.length bytes - L.trace logger $ "sending " ++ show numbytes ++ " bytes to node " ++ show node - safely $ - do - B.hPut stdin bytes - L.trace logger $ "done sending " ++ show numbytes ++ " bytes to node " ++ show node - `onException` - writeChan packetWrite (Mux.Packet nodeBytes bytes) + pk <- readChan toNodeRead + L.debug logger $ "writing packet: " ++ show pk + write pk -- establish routes for processing packets coming from the node routes <- id $ @@ -134,7 +133,7 @@ make bs nodeLock p genNode launchNodeCmd = do L.debug logger $ "got request " ++ show (Base64.encode replyTo) b <- h a L.debug logger $ "got response " ++ show (Base64.encode replyTo) - send $ Put.runPutS (S.serialize (Mux.Packet replyTo $ Put.runPutS (S.serialize b))) + writeChan toNodeWrite . Just . Mux.Packet replyTo $ Put.runPutS (S.serialize b) insert = handleRequest (BS.insert bs) lookup = handleRequest (BS.lookup bs) declare = handleRequest (BS.declareSeries bs) @@ -149,15 +148,16 @@ make bs nodeLock p genNode launchNodeCmd = do h0 <- BS.declareSeries bs series Just _ <- BS.update bs series h0 nodeParams pure node - delete proof | proof /= deleteProof = pure () + delete proof | BA.constEq proof deleteProof = pure () | otherwise = do - send (Put.runPutS $ S.serialize (Nothing :: Maybe Mux.Packet)) + writeChan toNodeWrite Nothing BS.deleteSeries bs (BS.Series $ Remote.publicKey node) removeRoute in pure routes processor <- Async.async . Mux.repeatWhile $ do - nodePacket <- readChan fromNodeRead + L.debug logger $ "processor about to read" + nodePacket <- read case nodePacket of Nothing -> False <$ L.info logger "processor completed" Just packet -> True <$ do @@ -171,17 +171,11 @@ make bs nodeLock p genNode launchNodeCmd = do writeChan packetWrite packet -- forwarded to main loop _ <- forkIO $ do - exitCode <- Process.waitForProcess process - L.debug logger "worker process terminated" - removeRoute - _ <- Async.waitCatch reader - L.debug logger "worker reader thread terminated" - Async.cancel writer - _ <- Async.waitCatch processor - mapM_ (safely . hClose) [stdin, stdout] - case exitCode of - Exit.ExitSuccess -> L.info logger $ "node process terminated" - Exit.ExitFailure n -> L.warn logger $ "node process exited with: " ++ show n + r <- Async.waitCatch processor + L.debug logger $ "worker process terminated with: " ++ show r + _ <- Async.waitCatch writer + L.debug logger "worker writer thread terminated" + pure () safely :: IO () -> IO () diff --git a/node/src/Unison/NodeWorker.hs b/node/src/Unison/NodeWorker.hs index eb88be815..56d71e3f4 100644 --- a/node/src/Unison/NodeWorker.hs +++ b/node/src/Unison/NodeWorker.hs @@ -4,32 +4,23 @@ module Unison.NodeWorker where -import Control.Concurrent.STM (atomically) +import Control.Concurrent (threadDelay) +import Control.Concurrent.STM (STM, atomically) import Control.Concurrent.STM.TSem +import Control.Exception.Base as Ex import Control.Monad.IO.Class -import Data.Bytes.Serial (Serial, serialize, deserialize) -import Data.Serialize.Get (Get) -import GHC.Generics -import System.IO (stdin, hSetBinaryMode) -import Unison.BlockStore (BlockStore(..)) +import Data.Bytes.Serial (Serial, serialize) import Unison.Cryptography (Cryptography) import Unison.Hash.Extra () +import qualified Control.Concurrent.Async as Async import qualified Data.ByteArray as BA -import qualified Data.ByteString as B -import qualified Data.Bytes.Get as Get import qualified Data.Bytes.Put as Put -import qualified Data.Serialize.Get as Get -import qualified Unison.Config as Config -import qualified Unison.Cryptography as C import qualified Unison.NodeProtocol as P import qualified Unison.Remote as Remote import qualified Unison.Runtime.Multiplex as Mux import qualified Unison.Runtime.Remote as Remote import qualified Unison.Util.Logger as L -data Keypair k = Keypair { public :: k, private :: B.ByteString } deriving Generic -instance Serial k => Serial (Keypair k) - make :: ( BA.ByteArrayAccess key , Serial signature , Serial term, Show term @@ -39,40 +30,56 @@ make :: ( BA.ByteArrayAccess key , Eq h , Serial key , Ord thash) - => P.Protocol term hash h thash - -> (Keypair key -> Cryptography key symmetricKey signKey skp signature hash Remote.Cleartext) - -> Get (Cryptography key symmetricKey signKey skp signature hash Remote.Cleartext - -> BlockStore h - -> IO (Remote.Language term thash, term -> IO (Either String term), IO ())) - -> IO () -make protocol mkCrypto makeSandbox = do - logger <- L.scope "worker" <$> Config.loggerStandardError - let die msg = liftIO $ L.error logger msg >> error "" - L.info logger $ "initializing... " - hSetBinaryMode stdin True - (privateKey, _, rem) <- Mux.deserializeHandle1 stdin (Get.runGetPartial deserialize B.empty) - (node, _, rem) <- Mux.deserializeHandle1 stdin (Get.runGetPartial deserialize rem) - (universe, _, rem) <- Mux.deserializeHandle1 stdin (Get.runGetPartial deserialize rem) - (sandbox, _, rem) <- Mux.deserializeHandle1 stdin (Get.runGetPartial deserialize rem) - publicKey <- either die pure $ Get.runGetS deserialize (Remote.publicKey node) - let keypair = Keypair publicKey privateKey - L.debug logger $ "parsed private key, node id, universe, sandbox description" - L.debug logger $ "remaining bytes: " ++ show (B.length rem) - interrupt <- atomically $ newTSem 0 - Mux.runStandardIO logger (Mux.seconds 5) rem (atomically $ waitTSem interrupt) $ do + => L.Logger + -> P.Protocol term hash h thash + -> Cryptography key symmetricKey signKey skp signature hash Remote.Cleartext + -> Remote.Language term thash + -> Remote.Node + -> Remote.Universe + -> (term -> IO (Either String term)) + -> IO (Maybe Mux.Packet -> IO (), IO (Maybe Mux.Packet), STM Bool) +make logger protocol crypto sandbox node universe typecheck = do + logger <- pure $ L.scope "worker" logger + (env, toNode, fromNode, isActive) <- Mux.env0 logger + L.debug' logger $ do + active <- atomically isActive + pure $ "active0: " ++ show active + -- used to make sure we are listening on all channels before returning, + -- otherwise the caller could experience packet drops when sending + ok <- atomically $ newTSem 0 -- incremented once initialization done + L.debug logger "kicking off processor" + node <- processor ok env + _ <- Async.async $ supervise ok env node + L.debug logger "about to wait on semaphore" + atomically $ waitTSem ok + L.debug logger "done waiting on semaphore" + threadDelay (1000 * 500) + L.debug' logger $ do + active <- atomically isActive + pure $ "active: " ++ show active + let toNode' pk = check >> toNode pk + fromNode' = check >> fromNode + check = atomically isActive >>= \a -> + if a then pure () else fail "inactive node" + pure (toNode', fromNode', isActive) + where + supervise ok env node = Async.waitCatch node >>= \e -> case e of + Left err | isCatchable err -> do + L.warn logger $ "error during node processing, restarting " + node <- processor ok env + supervise ok env node + Left err -> + L.info logger $ "shutting down node due to uncatchable error: " ++ show err + Right _ -> + L.info logger "shutting down node due to graceful termination" + processor ok env = Async.async . Mux.run env $ do blockStore <- P.blockStoreProxy protocol - makeSandbox <- either die pure $ Get.runGetS makeSandbox sandbox - let crypto = mkCrypto keypair - (sandbox, typecheck, initialize) <- liftIO $ makeSandbox crypto blockStore - let skHash = Put.runPutS (serialize $ C.hash crypto [Put.runPutS (serialize $ private keypair)]) -- todo: load this from persistent store also connectionSandbox <- pure $ Remote.ConnectionSandbox (\_ -> pure True) (\_ -> pure True) env <- liftIO $ Remote.makeEnv universe node blockStore - _ <- Remote.server crypto connectionSandbox env sandbox protocol - _ <- do + server <- Remote.server crypto connectionSandbox env sandbox protocol + localEval <- do (prog, cancel) <- Mux.subscribeTimed (Mux.seconds 60) (P._localEval protocol) - liftIO $ initialize - Mux.info $ "... done initializing" Mux.fork . Mux.scope "_localEval" . Mux.repeatWhile $ do e <- prog case e of @@ -92,17 +99,27 @@ make protocol mkCrypto makeSandbox = do case Remote.unRemote sandbox r of Nothing -> True <$ (Mux.warn $ "received a non-Remote: " ++ show r) Just r -> True <$ Mux.fork (Remote.handle crypto connectionSandbox env sandbox protocol r) - _ <- do - (destroy, cancel) <- Mux.subscribeTimed (Mux.seconds 60) (P._destroyIn protocol) + destroyIn <- do + (destroy, _) <- Mux.subscribeTimed (Mux.seconds 60) (P._destroyIn protocol) Mux.fork . Mux.repeatWhile $ do sig <- destroy case sig of - Just sig | BA.constEq skHash (Put.runPutS (serialize sig)) -> do - cancel - Mux.send (Mux.Channel Mux.Type skHash) () + Just sig -> do + -- cancel + Mux.send (Mux.Channel Mux.Type (Put.runPutS (serialize sig))) () -- no other cleanup needed; container will reclaim resources and eventually -- kill off linked child nodes - liftIO $ atomically (signalTSem interrupt) pure False _ -> pure True - pure () + Mux.info $ "... done initializing" + liftIO . atomically $ signalTSem ok + liftIO $ do Async.wait server; Async.wait localEval; Async.wait destroyIn + +-- Don't catch asynchronous exceptions or deadlocks +isCatchable :: SomeException -> Bool +isCatchable e = not $ + (case Ex.fromException e of Just Ex.ThreadKilled -> True; _ -> False) || + (case Ex.fromException e of Just Ex.UserInterrupt -> True; _ -> False) || + (case Ex.fromException e of Just Ex.BlockedIndefinitelyOnSTM -> True; _ -> False) || + (case Ex.fromException e of Just Ex.BlockedIndefinitelyOnMVar -> True; _ -> False) + diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index f1018ea1d..9845fd4d4 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -26,7 +26,7 @@ import qualified Unison.Runtime.ResourcePool as RP import qualified Unison.SerializationAndHashing as SAH import qualified Unison.Term as Term import qualified Unison.Type as Type -import qualified Unison.Util.Logger as L +-- import qualified Unison.Util.Logger as L indexT :: Ord v => Type v -> Type v -> Type v indexT k v = Type.ref (R.Builtin "Index") `Type.app` k `Type.app` v @@ -57,7 +57,7 @@ pattern Link' href description <- make :: Eq a => Logger -> BlockStore a -> C.Cryptography k syk sk skp s h ByteString -> IO (WHNFEval -> [Builtin]) -make logger blockStore crypto = do +make _ blockStore crypto = do let nextID = do cp <- C.randomBytes crypto 64 ud <- C.randomBytes crypto 64 diff --git a/node/src/Unison/Runtime/Multiplex.hs b/node/src/Unison/Runtime/Multiplex.hs index 73e96c86b..953b82899 100644 --- a/node/src/Unison/Runtime/Multiplex.hs +++ b/node/src/Unison/Runtime/Multiplex.hs @@ -5,7 +5,6 @@ module Unison.Runtime.Multiplex where -import System.IO (Handle, stdin, stdout, hFlush, hSetBinaryMode) import Control.Applicative import Control.Concurrent.Async (Async) import Control.Concurrent.MVar @@ -33,13 +32,10 @@ import qualified Data.ByteArray as BA import qualified Data.ByteString as B import qualified Data.Bytes.Get as Get import qualified Data.Bytes.Put as Put -import qualified Data.Serialize.Get as Get import qualified STMContainers.Map as M import qualified Unison.Cryptography as C import qualified Unison.Runtime.Queue as Q import qualified Unison.Util.Logger as L -import qualified ListT -import qualified Control.Monad.Morph as Morph data Packet = Packet { destination :: !B.ByteString, content :: !B.ByteString } deriving (Generic) instance Serial Packet @@ -63,6 +59,21 @@ type Env = newtype Multiplex a = Multiplex (ReaderT Env IO a) deriving (Applicative, Alternative, Functor, Monad, MonadIO, MonadPlus, MonadReader Env) +env0 :: L.Logger -> IO (Env, Maybe Packet -> IO (), IO (Maybe Packet), STM Bool) +env0 logger = do + fresh <- uniqueChannel + output <- atomically Q.empty :: IO (Q.Queue (Maybe Packet)) + input <- atomically newTQueue :: IO (TQueue (Maybe Packet)) + cb0@(Callbacks m _) <- Callbacks <$> atomically M.new <*> atomically (newTVar 0) + recvs0 <- atomically M.new + let env = (Q.enqueue output . (Just <$>), cb0, fresh, recvs0, logger) + isActive = (||) <$> (not <$> M.null m) <*> (not <$> M.null recvs0) + _ <- run env (fork $ process (atomically (readTQueue input))) + pure ( env + , atomically . writeTQueue input + , atomically $ Q.dequeue output + , isActive ) + run :: Env -> Multiplex a -> IO a run env (Multiplex go) = runReaderT go env @@ -71,106 +82,9 @@ liftLogged msg action = ask >>= \env -> liftIO $ catch action (handle env) where handle :: Env -> SomeException -> IO a handle env ex = run env (warn $ msg ++ " " ++ show ex) >> throwIO ex --- | Run the multiplexed computation using stdin and stdout, terminating --- after a period of inactivity exceeding sleepAfter. `rem` is prepended --- onto stdin. -runStandardIO :: L.Logger -> Microseconds -> B.ByteString -> IO () - -> Multiplex a -> IO a -runStandardIO logger sleepAfter rem interrupt m = do - hSetBinaryMode stdin True - hSetBinaryMode stdout True - fresh <- uniqueChannel - output <- atomically Q.empty :: IO (Q.Queue (Maybe Packet)) - input <- atomically newTQueue :: IO (TQueue (Maybe Packet)) - cb0@(Callbacks cbm cba) <- Callbacks <$> atomically M.new <*> atomically (newTVar 0) - recvs0 <- atomically M.new - let env = (Q.enqueue output . (Just <$>), cb0, fresh, recvs0, logger) - activity <- atomically $ newTVar 0 - let bump = atomically $ modifyTVar' activity (1+) - _ <- Async.async $ do - interrupt - atomically $ writeTQueue input Nothing - L.info logger "interrupted" - _ <- Async.async $ do - let write pk _ = bump >> atomically (writeTQueue input (Just pk)) - deserializeHandle stdin rem write - bump - atomically $ writeTQueue input Nothing - L.info logger "shutting down reader thread" - writer <- Async.async . repeatWhile $ do - logger <- pure $ L.scope "writer" logger - packet <- atomically $ Q.tryDequeue output :: IO (Maybe (Maybe Packet)) - packet <- case packet of - -- writer is saturated, don't bother flushing output buffer - Just packet -> pure packet - -- writer not saturated; flush output buffer to avoid latency and/or deadlock - Nothing -> hFlush stdout >> atomically (Q.dequeue output) - B.putStr (Put.runPutS (serialize packet)) - case packet of - Nothing -> False <$ L.info logger "writer shutting down" - Just packet -> do - L.debug logger $ "output packet " ++ show packet - True <$ bump - watchdog <- Async.async . repeatWhile $ do - activity0 <- (+) <$> readTVarIO activity <*> readTVarIO cba - C.threadDelay sleepAfter - activity1 <- (+) <$> readTVarIO activity <*> readTVarIO cba - nothingPending <- atomically $ M.null cbm - L.debug' (L.scope "watchdog" logger) $ do - keys <- fmap (map fst) . ListT.toList . Morph.hoist atomically . M.stream $ cbm - pure $ "current subscription keys: " ++ show (map Base64.encode keys) - L.debug (L.scope "watchdog" logger) $ - "activity: " ++ show (activity0, activity1, nothingPending) - continue <- atomically $ - if activity0 == activity1 && nothingPending then do - writeTQueue input Nothing - Q.enqueue output (pure Nothing) - pure False - else - pure True - when (not continue) $ L.info logger "watchdog shutting down" - pure continue - a <- run env m - processor <- Async.async $ do - run env (process $ atomically (readTQueue input)) - L.info logger "processor shutting down" - Async.wait watchdog - -- Async.wait reader - Async.wait processor - Async.wait writer - L.info logger "Mux.runStandardIO shutdown" - pure a - -deserializeHandle :: Serial a => Handle -> B.ByteString -> (a -> Int -> IO ()) -> IO () -deserializeHandle h rem write = go (Get.runGetPartial deserialize rem) where - go dec = do - (a, n, rem') <- deserializeHandle1 h dec - write a (n + B.length rem) - go (Get.runGetPartial deserialize rem') - -deserializeHandle1' :: Serial a => Handle -> IO (a, Int, B.ByteString) -deserializeHandle1' h = deserializeHandle1 h (Get.runGetPartial deserialize B.empty) - -deserializeHandle1 :: Handle -> Get.Result a -> IO (a, Int, B.ByteString) -deserializeHandle1 h dec = go dec 0 where - go result !n = case result of - Get.Fail msg _ -> fail msg - Get.Partial k -> do - bs <- B.hGetSome h 65536 - go (k bs) (n + B.length bs) - Get.Done a rem -> pure (a, n, rem) - ask :: Multiplex Env ask = Multiplex Reader.ask -bumpActivity :: Multiplex () -bumpActivity = do - (_, Callbacks _ cba, _, _, _) <- ask - liftIO $ bumpActivity' cba - -bumpActivity' :: TVar Word64 -> IO () -bumpActivity' cba = atomically $ modifyTVar' cba (1+) - logger :: Multiplex L.Logger logger = do ~(_, _, _, _, logger) <- ask @@ -193,7 +107,7 @@ debug msg = logger >>= \logger -> liftIO $ L.debug logger msg process :: IO (Maybe Packet) -> Multiplex () process recv = scope "Mux.process" $ do - (_, Callbacks cbs cba, _, _, logger) <- ask + (_, Callbacks cbs _, _, _, logger) <- ask liftIO . repeatWhile $ do packet <- recv case packet of @@ -206,7 +120,6 @@ process recv = scope "Mux.process" $ do pure True Just callback -> do L.warn logger $ "packet delivered @ " ++ show (Base64.encode destination) - bumpActivity' cba callback content pure True @@ -362,10 +275,9 @@ send' (Channel _ key) a = do receiveCancellable :: Serial a => Channel a -> Multiplex (Multiplex a, String -> Multiplex ()) receiveCancellable (Channel _ key) = do - (_,Callbacks cbs cba,_,_,_) <- ask + (_,Callbacks cbs _,_,_,_) <- ask result <- liftIO newEmptyMVar liftIO . atomically $ M.insert (putMVar result . Right) key cbs - liftIO $ bumpActivity' cba cancel <- pure $ \reason -> do liftIO . atomically $ M.delete key cbs liftIO $ putMVar result (Left $ "Mux.cancelled: " ++ reason) @@ -449,10 +361,9 @@ subscribeTimed micros chan = do subscribe :: Serial a => Channel a -> Multiplex (Multiplex a, Multiplex ()) subscribe (Channel _ key) = scope "subscribe" $ do - (_, Callbacks cbs cba, _, _, _) <- ask + (_, Callbacks cbs _, _, _, _) <- ask q <- liftIO . atomically $ newTQueue liftIO . atomically $ M.insert (atomically . writeTQueue q) key cbs - liftIO $ bumpActivity' cba unsubscribe <- pure . liftIO . atomically . M.delete key $ cbs force <- pure $ do bytes <- liftIO . atomically $ readTQueue q diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index 43457ca54..55cd83e7a 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -4,6 +4,7 @@ module Unison.Runtime.Remote where +import Control.Concurrent.Async (Async) import Data.Functor import Data.Maybe import Control.Monad @@ -107,10 +108,10 @@ server :: (Ord h, Serial key, Serial t, Show t, Serial h) -> Env t h -> Language t h -> P.Protocol t hash h' h - -> Multiplex () + -> Multiplex (Async ()) server crypto allow env lang p = do (accept,_) <- Mux.subscribeTimed (Mux.seconds 60) (Mux.erase (P._eval p)) - void . Mux.fork . Mux.repeatWhile $ do + Mux.fork . Mux.repeatWhile $ do initialPayload <- accept case initialPayload of Nothing -> pure False diff --git a/node/src/Worker.hs b/node/src/Worker.hs deleted file mode 100644 index 0f5b27e39..000000000 --- a/node/src/Worker.hs +++ /dev/null @@ -1,102 +0,0 @@ -{-# Language OverloadedStrings #-} - -module Main where - -import Data.Text.Encoding (decodeUtf8) -import Control.Concurrent.STM.TVar -import Control.Monad -import System.IO (stderr, stdin, stdout, hSetEncoding, utf8) -import Unison.Hash (Hash) -import Unison.NodeProtocol.V0 (protocol) -import Unison.NodeWorker as W -import Unison.SerializationAndHashing (TermV) -import qualified Control.Concurrent.STM as STM -import qualified Data.ByteString as B -import qualified Data.Set as Set -import qualified Data.Text as Text -import qualified Unison.Config as Config -import qualified Unison.Cryptography as C -import qualified Unison.Node as Node -import qualified Unison.Node.BasicNode as BasicNode -import qualified Unison.Node.Builtin as Builtin -import qualified Unison.Node.MemStore as Store -import qualified Unison.Note as Note -import qualified Unison.Parsers as Parsers -import qualified Unison.Reference as Reference -import qualified Unison.Remote as RT -import qualified Unison.Runtime.ExtraBuiltins as ExtraBuiltins -import qualified Unison.Runtime.Remote as R -import qualified Unison.SerializationAndHashing as SAH -import qualified Unison.Term as Term -import qualified Unison.Util.Logger as L - -main :: IO () -main = do - mapM_ (`hSetEncoding` utf8) [stdout, stdin, stderr] - logger <- L.scope "worker-main" <$> Config.loggerTo stderr - W.make protocol crypto (pure $ lang logger) where - crypto keypair = C.noop (W.public keypair) - lang logger crypto blockstore = do - let b0 = Builtin.makeBuiltins logger - b1 <- ExtraBuiltins.make logger blockstore crypto - store <- Store.make - backend <- BasicNode.make SAH.hash store (\whnf -> b0 whnf ++ b1 whnf) - loadDeclarations "unison-src/base.u" backend - loadDeclarations "unison-src/extra.u" backend - loadDeclarations "unison-src/dindex.u" backend - initialized <- STM.atomically $ newTVar False - pure $ go backend initialized - where - go backend initialized = - let - lang :: R.Language TermV Hash - lang = R.Language localDependencies eval apply node unit channel local unRemote remote - codestore = R.makeCodestore blockstore :: R.Codestore TermV Hash - localDependencies _ = Set.empty -- todo, compute this for real - whnf e = do -- todo: may want to have this use evaluator + codestore directly - Note.lift . STM.atomically $ readTVar initialized >>= \ok -> - if ok then pure () - else STM.retry - [(_,_,e)] <- Node.evaluateTerms backend [([], e)] - pure e - eval t = Note.run (whnf t) - -- evaluator = I.eval allprimops - -- allbuiltins = b0 whnf ++ b1 whnf - -- allprimops = Map.fromList [ (r, op) | Builtin.Builtin r (Just op) _ _ <- allbuiltins ] - typecheck e = do - bindings <- Note.run $ Node.allTermsByVarName Term.ref backend - L.debug logger $ "known symbols: " ++ show (map fst bindings) - let e' = Parsers.bindBuiltins bindings [] e - Note.unnote (Node.typeAt backend e' []) >>= \t -> case t of - Left note -> pure $ Left (show note) - Right _ -> pure (Right e') - initialize = do - L.info logger "checking if base libraries loaded" - let idf = Term.lam' ["x"] (Term.var' "x") :: TermV - let Reference.Derived hashIdf = SAH.hash idf - alreadyInitialized <- pure False -- not . null <$> R.getHashes codestore (Set.fromList [hashIdf]) - when (not alreadyInitialized) $ do - L.info logger "codestore not loaded... inserting" - hs <- Note.run (Node.allTerms backend) - -- todo - -- R.saveHashes codestore [ (h,v) | (Reference.Derived h, v) <- hs ] - pure () - STM.atomically $ writeTVar initialized True - in (lang, typecheck, initialize) - apply = Term.app - node = Term.node - unit = Term.builtin "()" - channel = Term.channel - local l = Term.remote (RT.Step (RT.Local l)) - unRemote (Term.Distributed' (Term.Remote r)) = Just r - unRemote _ = Nothing - remote = Term.remote - loadDeclarations path node = do - txt <- decodeUtf8 <$> B.readFile path - let str = Text.unpack txt - r <- Note.run $ Node.declare' Term.ref str node - L.info logger $ "loaded " ++ path - L.debug' logger $ do - ts <- Note.run $ Node.allTermsByVarName Term.ref node - pure $ show ts - pure r diff --git a/node/unison-node.cabal b/node/unison-node.cabal index 8583378af..c802e633e 100644 --- a/node/unison-node.cabal +++ b/node/unison-node.cabal @@ -54,7 +54,6 @@ library Unison.Node.UnisonBlockStore Unison.NodeContainer Unison.NodeServer - Unison.NodeWorker Unison.NodeProtocol Unison.NodeProtocol.V0 Unison.Reference.Extra @@ -111,7 +110,7 @@ library directory, filepath, free, - hashable, + hashable, http-types, io-streams, list-t, @@ -154,72 +153,6 @@ library exposed-modules: Unison.BlockStore.LevelDbStore -executable worker - main-is: Worker.hs - hs-source-dirs: src - ghc-options: -Wall -fno-warn-name-shadowing -threaded -rtsopts -with-rtsopts=-N -v0 - - if flag(optimized) - ghc-options: -funbox-strict-fields -O2 - - build-depends: - aeson, - async, - base, - base64-bytestring, - bytes, - bytestring, - cereal, - containers, - configurator, - cryptonite, - curl, - directory, - filepath, - free, - hashable, - list-t, - memory, - mmorph, - mtl, - stm, - stm-containers, - tagsoup, - text, - time, - transformers, - unison-node, - unison-shared, - vector - other-modules: - Unison.ABT.Extra, - Unison.Config, - Unison.Distance.Extra, - Unison.Hash.Extra, - Unison.Kind.Extra, - Unison.NodeProtocol, - Unison.NodeProtocol.V0, - Unison.NodeWorker, - Unison.Reference.Extra, - Unison.Remote.Extra, - Unison.Runtime.Block, - Unison.Runtime.ExpiringMap, - Unison.Runtime.ExtraBuiltins, - Unison.Runtime.Html, - Unison.Runtime.Http, - Unison.Runtime.Index, - Unison.Runtime.Journal, - Unison.Runtime.JournaledMap, - Unison.Runtime.Multiplex, - Unison.Runtime.Queue, - Unison.Runtime.Remote, - Unison.Runtime.ResourcePool, - Unison.Runtime.SharedResourceMap, - Unison.SerializationAndHashing, - Unison.Symbol.Extra, - Unison.Term.Extra, - Unison.Type.Extra - executable container main-is: Container.hs hs-source-dirs: src @@ -257,6 +190,7 @@ executable container scotty, stm, stm-containers, + tagsoup, text, time, transformers, @@ -266,6 +200,7 @@ executable container vector, wai-extra, wai-middleware-static + other-modules: Unison.ABT.Extra, Unison.BlockStore.FileBlockStore, @@ -277,6 +212,7 @@ executable container Unison.NodeProtocol, Unison.NodeProtocol.V0, Unison.NodeServer, + Unison.NodeWorker, Unison.Reference.Extra, Unison.Remote.Extra, Unison.Runtime.Block, diff --git a/shared/src/Unison/Util/Logger.hs b/shared/src/Unison/Util/Logger.hs index 7e98712fd..dff78e830 100644 --- a/shared/src/Unison/Util/Logger.hs +++ b/shared/src/Unison/Util/Logger.hs @@ -14,7 +14,7 @@ module Unison.Util.Logger where import Control.Concurrent (forkIO) import Control.Concurrent.MVar -import Control.Exception (finally, try) +import Control.Exception (bracket, try) import Control.Monad import Data.List import System.IO (Handle, hPutStrLn, hGetLine, stdout, stderr) @@ -34,7 +34,7 @@ atomic :: Logger -> IO Logger atomic logger = do lock <- newMVar () pure $ - let raw' msg = takeMVar lock >> (raw logger msg `finally` putMVar lock ()) + let raw' msg = bracket (takeMVar lock) (\_ -> putMVar lock ()) (\_ -> raw logger msg) in logger { raw = raw' } toHandle :: Handle -> Logger diff --git a/unison-src/html.u b/unison-src/html.u index 690350aba..73fd0429d 100644 --- a/unison-src/html.u +++ b/unison-src/html.u @@ -1,4 +1,4 @@ -- run from unison root directory -- curl -H "Content-Type: text/plain; charset=UTF-8" --data-binary @node/tests/html.u http://localhost:8081/compute/dummynode909 -Http.getUrl "http://unisonweb.org" +Http.get-url "http://unisonweb.org" diff --git a/unison-src/pingpong.u b/unison-src/pingpong.u index ec16d914b..d19f31c22 100644 --- a/unison-src/pingpong.u +++ b/unison-src/pingpong.u @@ -6,11 +6,11 @@ do Remote n2 := Remote.spawn; let rec ping i = do Remote - i := Remote.at n2 (i + 1); - if (i >= 5) (pure i) (pong i);; + i := Remote.at n2 (i + 1); + if (i >=_Number 5) (pure i) (pong i);; ; - pong i = do Remote - i := Remote.at n1 (i + 1); + pong i = do Remote + i := Remote.at n1 (i + 1); ping i;; ; ping 0;; From 05fa2316229461b898cd4d51da6ae4faa4d971ea Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 15 Sep 2016 00:03:57 -0400 Subject: [PATCH 46/61] fix issue with Remote.receive-async --- node/src/Unison/Runtime/Multiplex.hs | 36 ++++++++++++++++++++-------- node/src/Unison/Runtime/Remote.hs | 12 +++++----- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/node/src/Unison/Runtime/Multiplex.hs b/node/src/Unison/Runtime/Multiplex.hs index 953b82899..80e7d13aa 100644 --- a/node/src/Unison/Runtime/Multiplex.hs +++ b/node/src/Unison/Runtime/Multiplex.hs @@ -96,9 +96,11 @@ scope msg = local tweak where -- | Crash with a message. Include the current logging scope. crash :: String -> Multiplex a -crash msg = scope msg $ do - l <- logger - fail (show $ L.getScope l) +crash msg = do + warn msg + scope msg $ do + l <- logger + fail (show $ L.getScope l) info, warn, debug :: String -> Multiplex () info msg = logger >>= \logger -> liftIO $ L.info logger msg @@ -273,29 +275,43 @@ send' (Channel _ key) a = do ~(send,_,_,_,_) <- ask liftIO . atomically $ send (Packet key . Put.runPutS . serialize <$> a) -receiveCancellable :: Serial a => Channel a -> Multiplex (Multiplex a, String -> Multiplex ()) -receiveCancellable (Channel _ key) = do +receiveCancellable' :: Channel a + -> Multiplex (Multiplex B.ByteString, String -> Multiplex ()) +receiveCancellable' chan@(Channel _ key) = do (_,Callbacks cbs _,_,_,_) <- ask result <- liftIO newEmptyMVar liftIO . atomically $ M.insert (putMVar result . Right) key cbs cancel <- pure $ \reason -> do liftIO . atomically $ M.delete key cbs liftIO $ putMVar result (Left $ "Mux.cancelled: " ++ reason) - force <- pure . scope "receiveCancellable" $ do + force <- pure . scope (show chan) . scope "receiveCancellable" $ do + info "awaiting result" bytes <- liftIO $ takeMVar result + info "got result" bytes <- either crash pure bytes - either crash pure $ Get.runGetS deserialize bytes + info "got result bytes" + pure bytes pure (force, cancel) -receiveTimed :: Serial a => String -> Microseconds -> Channel a -> Multiplex (Multiplex a) -receiveTimed msg micros chan = do - (force, cancel) <- receiveCancellable chan +receiveCancellable :: Serial a => Channel a -> Multiplex (Multiplex a, String -> Multiplex ()) +receiveCancellable chan@(Channel _ key) = f <$> receiveCancellable' chan where + f (get, cancel) = (g =<< get, cancel) + g bytes = either crash pure $ Get.runGetS deserialize bytes + +receiveTimed' :: String -> Microseconds -> Channel a -> Multiplex (Multiplex B.ByteString) +receiveTimed' msg micros chan = do + (force, cancel) <- receiveCancellable' chan env <- ask watchdog <- liftIO . C.forkIO $ do liftIO $ C.threadDelay micros run env (cancel $ "receiveTimed timeout during " ++ msg) pure $ scope "receiveTimed" (force <* liftIO (C.killThread watchdog) <* cancel ("receiveTimed completed" ++ msg)) +receiveTimed :: Serial a => String -> Microseconds -> Channel a -> Multiplex (Multiplex a) +receiveTimed msg micros chan = tweak <$> receiveTimed' msg micros chan where + tweak bytes = tweak' =<< bytes + tweak' bytes = either crash pure $ Get.runGetS deserialize bytes + -- Save a receive future as part of saveReceive :: Microseconds -> B.ByteString -> Multiplex B.ByteString -> Multiplex () diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index 55cd83e7a..cdf5d135d 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -195,22 +195,22 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of Mux.debug $ "runLocal Pure" liftIO $ eval lang t runLocal (Send c@(Channel cid) a) = do - Mux.warn $ "runLocal Send " ++ show c + Mux.warn $ "runLocal Send " ++ show c ++ " " ++ show a Mux.process1 (Mux.Packet cid (Put.runPutS (serialize a))) pure (unit lang) runLocal (ReceiveAsync chan@(Channel cid) (Seconds seconds)) = do - Mux.debug $ "runLocal ReceiveAsync " ++ show (seconds, cid) + Mux.warn $ "runLocal ReceiveAsync " ++ show (seconds, chan) forceChan <- Mux.channel Mux.warn $ "ReceiveAsync force channel " ++ show forceChan let micros = floor $ seconds * 1000 * 1000 - force <- Mux.receiveTimed ("receiveAsync on " ++ show chan) + force <- Mux.receiveTimed' ("receiveAsync on " ++ show chan) micros ((Mux.Channel Mux.Type cid) :: Mux.Channel B.ByteString) Mux.saveReceive micros (Mux.channelId forceChan) force pure (remote lang (Step (Local (Receive (Channel $ Mux.channelId forceChan))))) - runLocal (Receive (Channel cid)) = do - Mux.warn $ "runLocal Receive " ++ show cid + runLocal (Receive chan@(Channel cid)) = do + Mux.warn $ "runLocal Receive " ++ show chan bytes <- Mux.restoreReceive cid - Mux.warn $ "runLocal Receive got bytes " ++ show cid + Mux.warn $ "runLocal Receive got bytes " ++ show chan case Get.runGetS deserialize bytes of Left err -> fail err Right r -> pure r From d62745a74dde7cc9a31d0e7d600c7ff6c31b4974 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 15 Sep 2016 00:06:02 -0400 Subject: [PATCH 47/61] added receive-async example, toned down logging --- node/src/Unison/Runtime/Remote.hs | 10 +++++----- unison-src/receive-async.u | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 unison-src/receive-async.u diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index cdf5d135d..3f9c5db02 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -195,22 +195,22 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of Mux.debug $ "runLocal Pure" liftIO $ eval lang t runLocal (Send c@(Channel cid) a) = do - Mux.warn $ "runLocal Send " ++ show c ++ " " ++ show a + Mux.debug $ "runLocal Send " ++ show c ++ " " ++ show a Mux.process1 (Mux.Packet cid (Put.runPutS (serialize a))) pure (unit lang) runLocal (ReceiveAsync chan@(Channel cid) (Seconds seconds)) = do - Mux.warn $ "runLocal ReceiveAsync " ++ show (seconds, chan) + Mux.debug $ "runLocal ReceiveAsync " ++ show (seconds, chan) forceChan <- Mux.channel - Mux.warn $ "ReceiveAsync force channel " ++ show forceChan + Mux.debug $ "ReceiveAsync force channel " ++ show forceChan let micros = floor $ seconds * 1000 * 1000 force <- Mux.receiveTimed' ("receiveAsync on " ++ show chan) micros ((Mux.Channel Mux.Type cid) :: Mux.Channel B.ByteString) Mux.saveReceive micros (Mux.channelId forceChan) force pure (remote lang (Step (Local (Receive (Channel $ Mux.channelId forceChan))))) runLocal (Receive chan@(Channel cid)) = do - Mux.warn $ "runLocal Receive " ++ show chan + Mux.debug $ "runLocal Receive " ++ show chan bytes <- Mux.restoreReceive cid - Mux.warn $ "runLocal Receive got bytes " ++ show chan + Mux.debug $ "runLocal Receive got bytes " ++ show chan case Get.runGetS deserialize bytes of Left err -> fail err Right r -> pure r diff --git a/unison-src/receive-async.u b/unison-src/receive-async.u new file mode 100644 index 000000000..4d5ce0d4b --- /dev/null +++ b/unison-src/receive-async.u @@ -0,0 +1,8 @@ + +do Remote + c := Remote.channel; + r := Remote.receive-async c (Duration.seconds 5); + -- Remote.fork (Remote.send c (Debug.watch "sent" 42)); + Remote.send c 42; -- (Debug.watch "sent" 42); + r;; + From 4e4b1e17aed80bd72fb978d7da5749bad9a86a86 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 15 Sep 2016 00:09:51 -0400 Subject: [PATCH 48/61] Remote.send is strict --- node/src/Unison/Runtime/Remote.hs | 1 + unison-src/receive-async.u | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index 3f9c5db02..d5dde994e 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -196,6 +196,7 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of liftIO $ eval lang t runLocal (Send c@(Channel cid) a) = do Mux.debug $ "runLocal Send " ++ show c ++ " " ++ show a + a <- liftIO $ eval lang a Mux.process1 (Mux.Packet cid (Put.runPutS (serialize a))) pure (unit lang) runLocal (ReceiveAsync chan@(Channel cid) (Seconds seconds)) = do diff --git a/unison-src/receive-async.u b/unison-src/receive-async.u index 4d5ce0d4b..a0987d882 100644 --- a/unison-src/receive-async.u +++ b/unison-src/receive-async.u @@ -2,7 +2,7 @@ do Remote c := Remote.channel; r := Remote.receive-async c (Duration.seconds 5); - -- Remote.fork (Remote.send c (Debug.watch "sent" 42)); - Remote.send c 42; -- (Debug.watch "sent" 42); + Remote.fork (Remote.send c (Debug.watch "sent" 42)); + -- Remote.send c 42; -- (Debug.watch "sent" 42); r;; From 2eaacba7e0ee392c889dde63386a8e7e13380fda Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 15 Sep 2016 12:35:16 -0400 Subject: [PATCH 49/61] fix issues with Remote.sleep and Remote.race --- node/src/Unison/Remote/Extra.hs | 2 +- node/src/Unison/Runtime/Multiplex.hs | 4 +-- node/src/Unison/Runtime/Remote.hs | 11 +++++-- shared/src/Unison/Node/Builtin.hs | 10 +++--- shared/src/Unison/Remote.hs | 14 ++++---- unison-src/base.u | 48 ++++++++++++++-------------- unison-src/dindex.u | 4 +-- unison-src/fork.u | 5 +++ unison-src/race.u | 6 ++++ 9 files changed, 61 insertions(+), 43 deletions(-) create mode 100644 unison-src/fork.u create mode 100644 unison-src/race.u diff --git a/node/src/Unison/Remote/Extra.hs b/node/src/Unison/Remote/Extra.hs index 11250c253..54519207c 100644 --- a/node/src/Unison/Remote/Extra.hs +++ b/node/src/Unison/Remote/Extra.hs @@ -11,6 +11,6 @@ instance Serial1 Step instance Serial1 Local instance Serial t => Serial (Step t) instance Serial t => Serial (Local t) -instance Serial Timeout +instance Serial Duration instance Serial Node instance Serial Channel diff --git a/node/src/Unison/Runtime/Multiplex.hs b/node/src/Unison/Runtime/Multiplex.hs index 80e7d13aa..f23483de3 100644 --- a/node/src/Unison/Runtime/Multiplex.hs +++ b/node/src/Unison/Runtime/Multiplex.hs @@ -280,10 +280,10 @@ receiveCancellable' :: Channel a receiveCancellable' chan@(Channel _ key) = do (_,Callbacks cbs _,_,_,_) <- ask result <- liftIO newEmptyMVar - liftIO . atomically $ M.insert (putMVar result . Right) key cbs + liftIO . atomically $ M.insert (void . tryPutMVar result . Right) key cbs cancel <- pure $ \reason -> do liftIO . atomically $ M.delete key cbs - liftIO $ putMVar result (Left $ "Mux.cancelled: " ++ reason) + liftIO . void $ tryPutMVar result (Left $ "Mux.cancelled: " ++ reason) force <- pure . scope (show chan) . scope "receiveCancellable" $ do info "awaiting result" bytes <- liftIO $ takeMVar result diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index d5dde994e..0db58f719 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -177,8 +177,8 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of client crypto allow env p n r Mux.debug $ "transferred to node: " ++ show n runLocal (Fork r) = do - Mux.debug $ "runLocal Fork" - Mux.fork (handle crypto allow env lang p r) $> unit lang + Mux.info $ "runLocal Fork" + unit lang <$ Mux.fork (handle crypto allow env lang p r) runLocal CreateChannel = do Mux.debug $ "runLocal CreateChannel" channel lang . Channel . Mux.channelId <$> Mux.channel @@ -195,10 +195,15 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of Mux.debug $ "runLocal Pure" liftIO $ eval lang t runLocal (Send c@(Channel cid) a) = do - Mux.debug $ "runLocal Send " ++ show c ++ " " ++ show a + Mux.warn $ "runLocal Send " ++ show c ++ " " ++ show a a <- liftIO $ eval lang a + Mux.warn $ "runLocal Send[2] " ++ show c ++ " " ++ show a Mux.process1 (Mux.Packet cid (Put.runPutS (serialize a))) pure (unit lang) + runLocal (Sleep (Seconds seconds)) = do + let micros = floor $ seconds * 1000 * 1000 + liftIO $ C.threadDelay micros + pure (unit lang) runLocal (ReceiveAsync chan@(Channel cid) (Seconds seconds)) = do Mux.debug $ "runLocal ReceiveAsync " ++ show (seconds, chan) forceChan <- Mux.channel diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index ecc0fd033..fe89a8599 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -180,13 +180,13 @@ makeBuiltins logger whnf = in (r, Just (I.Primop 1 op), unsafeParseType "Number -> Duration", prefix "Duration.seconds") -- Remote - , let r = R.Builtin "Remote.delay" + , let r = R.Builtin "Remote.sleep" op [seconds] = do Term.Number' seconds <- whnf seconds - N.lift $ threadDelay (floor $ seconds * 1000 * 1000) - pure $ Term.remote (Remote.Step (Remote.Local (Remote.Pure unitRef))) - op _ = fail "Remote.at unpossible" - in (r, Just (I.Primop 1 op), unsafeParseType "Duration -> Remote Unit", prefix "Remote.delay") + let s = Remote.Seconds seconds + pure $ Term.remote (Remote.Step (Remote.Local (Remote.Sleep s))) + op _ = fail "Remote.sleep unpossible" + in (r, Just (I.Primop 1 op), unsafeParseType "Duration -> Remote Unit", prefix "Remote.sleep") , let r = R.Builtin "Remote.at" op [node,term] = do Term.Distributed' (Term.Node node) <- whnf node diff --git a/shared/src/Unison/Remote.hs b/shared/src/Unison/Remote.hs index 5bcfd6367..cc7ef517f 100644 --- a/shared/src/Unison/Remote.hs +++ b/shared/src/Unison/Remote.hs @@ -100,8 +100,10 @@ data Local t | CreateChannel -- here : Local Node | Here - -- receiveAsync : Channel a -> Local (Local a) - | ReceiveAsync Channel Timeout + -- sleep : Duration -> Local () + | Sleep Duration + -- receiveAsync : Channel a -> Duration -> Local (Local a) + | ReceiveAsync Channel Duration -- receive : Channel a -> Local a | Receive Channel -- send : Channel a -> a -> Local () @@ -127,10 +129,10 @@ instance Hashable1 Local where hashed1 = H.Hashed . (H.hash1 hashCycle hash) hashed = H.Hashed . hash -newtype Timeout = Seconds { seconds :: Double } deriving (Eq,Ord,Show,Generic) -instance ToJSON Timeout -instance FromJSON Timeout -instance Hashable Timeout where +newtype Duration = Seconds { seconds :: Double } deriving (Eq,Ord,Show,Generic) +instance ToJSON Duration +instance FromJSON Duration +instance Hashable Duration where tokens (Seconds seconds) = [H.Double seconds] diff --git a/unison-src/base.u b/unison-src/base.u index e3d687fa0..1a65ad154 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -27,26 +27,26 @@ rest p = Pair.fold (x y -> y) p; 4th = rest `then` (rest `then` (rest `then` first)); 5th = rest `then` (rest `then` (rest `then` (rest `then` first))); -set-1st : ∀ a a2 b . a2 -> Pair a b -> Pair a2 b; +set-1st : ∀ a a2 b . a2 -> Pair a b -> Pair a2 b; set-1st new-first p = Pair new-first (rest p); Order.compare : ∀ a . Order a -> a -> a -> Comparison; Order.compare o a1 a2 = Order.Key.compare (Order.key o a1) (Order.key o a2); Order.tuple2 : ∀ a b . Order a -> Order b -> Order (a,b); -Order.tuple2 a b = Pair.Order a (Pair.Order b Unit.Order); +Order.tuple2 a b = Pair.Order a (Pair.Order b Unit.Order); Order.tuple3 : ∀ a b c . Order a -> Order b -> Order c -> Order (a,b,c); -Order.tuple3 a b c = Pair.Order a (Pair.Order b (Pair.Order c Unit.Order)); +Order.tuple3 a b c = Pair.Order a (Pair.Order b (Pair.Order c Unit.Order)); Order.by-1st : ∀ a b . Order a -> Order (Pair a b); -Order.by-1st a = Pair.Order a Order.ignore; +Order.by-1st a = Pair.Order a Order.ignore; Order.by-2nd : ∀ a b c . Order b -> Order (Pair a (Pair b c)); -Order.by-2nd b = Pair.Order Order.ignore (Pair.Order b Order.ignore); +Order.by-2nd b = Pair.Order Order.ignore (Pair.Order b Order.ignore); Order.by-3rd : ∀ a b c d . Order c -> Order (Pair a (Pair b (Pair c d))); -Order.by-3rd c = Pair.Order Order.ignore (Pair.Order Order.ignore (Pair.Order c Order.ignore)); +Order.by-3rd c = Pair.Order Order.ignore (Pair.Order Order.ignore (Pair.Order c Order.ignore)); Vector.bind : ∀ a b . (a -> Vector b) -> Vector a -> Vector b; Vector.bind f v = Vector.fold-balanced Vector.concatenate Vector.empty (Vector.map f v); @@ -60,14 +60,14 @@ Vector.fold-right : ∀ a b . (a -> b -> b) -> b -> Vector a -> b; Vector.fold-right f z vs = Vector.fold-left (flip f) z (Vector.reverse vs); Vector.fold-balanced : ∀ a . (a -> a -> a) -> a -> Vector a -> a; -Vector.fold-balanced plus zero vs = +Vector.fold-balanced plus zero vs = let rec - go plus zero vs = - if (Vector.size vs <=_Number 2) + go plus zero vs = + if (Vector.size vs <=_Number 2) (Vector.fold-left plus zero vs) (let p = Vector.halve vs; go plus zero (1st p) `plus` go plus zero (2nd p);;); - go plus zero vs;; + go plus zero vs;; ; Vector.all? : ∀ a . (a -> Boolean) -> Vector a -> Boolean; @@ -83,10 +83,10 @@ Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; Remote.map f = Remote.bind (f `then` Remote.pure); Remote.map2 : ∀ a b c . (a -> b -> c) -> Remote a -> Remote b -> Remote c; -Remote.map2 f a b = do Remote +Remote.map2 f a b = do Remote a := a; b := b; - pure (f a b);; + pure (f a b);; ; Remote.map2' : ∀ a b c . (a -> b -> Remote c) -> Remote a -> Remote b -> Remote c; @@ -99,11 +99,11 @@ Remote.replicate : ∀ a . Number -> Remote a -> Remote (Vector a); Remote.replicate n r = Remote.sequence (Vector.replicate n r); Remote.unfold : ∀ s a . s -> (s -> Remote (Optional (a, s))) -> Remote (Vector a); -Remote.unfold s f = let rec +Remote.unfold s f = let rec go s acc = do Remote ht := f s; - ht |> Optional.fold - (pure acc) + ht |> Optional.fold + (pure acc) (ht -> go (2nd ht) (Vector.append (1st ht) acc));; ; go s Vector.empty;; @@ -118,18 +118,18 @@ Remote.race timeout rs = do Remote c := Remote.channel; result := Remote.receive-async c timeout; Remote.traverse - (r -> Remote.fork <| do Remote a := r; Remote.transfer here; Remote.send c a;;) + (r -> Remote.fork <| do Remote a := r; Remote.transfer here; Remote.send c a;;) rs; result;; ; --- Returns `None` if no response within the provided `timeout`, +-- Returns `None` if no response within the provided `timeout`, -- which cannot exceed 500 seconds Remote.timeout : ∀ a . Duration -> Remote a -> Remote (Optional a); -Remote.timeout timeout r = +Remote.timeout timeout r = Remote.race (Duration.seconds 501) [ Remote.map Some r, - do Remote Remote.delay timeout; pure None;; + do Remote Remote.sleep timeout; pure None;; ]; Remote.at' : ∀ a . Node -> Remote a -> Remote a; @@ -140,7 +140,7 @@ Remote.start timeout r = do Remote here := Remote.here; c := Remote.channel; result := Remote.receive-async c timeout; - Remote.fork (Remote.at' here (r |> Remote.bind (Remote.send c))); + Remote.fork (Remote.at' here (r |> Remote.bind (Remote.send c))); pure result;; ; @@ -151,7 +151,7 @@ Remote.traverse f vs = (Vector.map (f `then` Remote.map Vector.single) vs); Remote.sequence : ∀ a . Vector (Remote a) -> Remote (Vector a); -Remote.sequence vs = +Remote.sequence vs = Vector.fold-balanced (Remote.map2 Vector.concatenate) (Remote.pure Vector.empty) (Vector.map (Remote.map Vector.single) vs); @@ -163,10 +163,10 @@ Remote.parallel-traverse timeout f vs = do Remote ; -- Run several remote computations in parallel, returning once `n` equivalent --- replies come back. Equivalence is based on result of `hash!`. +-- replies come back. Equivalence is based on result of `hash!`. Remote.quorum : ∀ a b . Duration -> Number -> (a -> Remote b) -> Vector a -> Remote b; Remote.quorum timeout n = _; -- todo - + Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b; Optional.map f = Optional.fold None (f `then` Some); @@ -183,7 +183,7 @@ Optional.somes : ∀ a . Vector (Optional a) -> Vector a; Optional.somes = Vector.bind (Optional.fold Vector.empty Vector.single); Optional.map2 : ∀ a b c . (a -> b -> c) -> Optional a -> Optional b -> Optional c; -Optional.map2 f a b = do Optional +Optional.map2 f a b = do Optional a := a; b := b; pure (f a b);; diff --git a/unison-src/dindex.u b/unison-src/dindex.u index f1f0357c6..04f708023 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -30,7 +30,7 @@ DIndex.lookup k ind = do Remote nind := Index.lookup node ind; -- on slim chance that a Node is removed from the cluster just before -- we do the lookup, it gets treated like a timeout - Optional.fold (Remote.map (const None) (Remote.delay DIndex.Timeout)) + Optional.fold (Remote.map (const None) (Remote.sleep DIndex.Timeout)) (Index.lookup k) nind;;) ; @@ -45,7 +45,7 @@ DIndex.insert k v ind = do Remote nodes := DIndex.nodesForKey k ind; localInsert = node -> (do Remote nind := Index.lookup node ind; - Optional.fold (Remote.map (const Unit) (Remote.delay DIndex.Timeout)) + Optional.fold (Remote.map (const Unit) (Remote.sleep DIndex.Timeout)) (Index.insert k v) nind;;) ; diff --git a/unison-src/fork.u b/unison-src/fork.u new file mode 100644 index 000000000..311c678ef --- /dev/null +++ b/unison-src/fork.u @@ -0,0 +1,5 @@ +do Remote + Remote.fork <| Remote.sleep (Duration.seconds 10); + Remote.fork <| Remote.sleep (Duration.seconds 10); + pure 23;; + diff --git a/unison-src/race.u b/unison-src/race.u new file mode 100644 index 000000000..6a84357e9 --- /dev/null +++ b/unison-src/race.u @@ -0,0 +1,6 @@ +do Remote + r := Remote.race (Duration.seconds 15) [ + do Remote pure (Debug.watch "race.winner1" 1);;, + do Remote Remote.sleep (Duration.seconds 10); pure (Debug.watch "race.winner2" 2);; + ]; + pure <| Debug.watch "result" r;; From 328939809ca6aa51f8087f0f59572373c39355eb Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Thu, 15 Sep 2016 13:03:24 -0400 Subject: [PATCH 50/61] DIndex seems to be working, added simple test --- node/src/Unison/Runtime/Multiplex.hs | 2 +- node/src/Unison/Runtime/Remote.hs | 4 ++-- unison-src/dindex-main.u | 11 +++++++++++ unison-src/dindex.u | 5 ++--- 4 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 unison-src/dindex-main.u diff --git a/node/src/Unison/Runtime/Multiplex.hs b/node/src/Unison/Runtime/Multiplex.hs index f23483de3..fede66e1a 100644 --- a/node/src/Unison/Runtime/Multiplex.hs +++ b/node/src/Unison/Runtime/Multiplex.hs @@ -121,7 +121,7 @@ process recv = scope "Mux.process" $ do L.warn logger $ "dropped packet @ " ++ show (Base64.encode destination) pure True Just callback -> do - L.warn logger $ "packet delivered @ " ++ show (Base64.encode destination) + L.debug logger $ "packet delivered @ " ++ show (Base64.encode destination) callback content pure True diff --git a/node/src/Unison/Runtime/Remote.hs b/node/src/Unison/Runtime/Remote.hs index 0db58f719..e7b1f3361 100644 --- a/node/src/Unison/Runtime/Remote.hs +++ b/node/src/Unison/Runtime/Remote.hs @@ -195,9 +195,9 @@ handle crypto allow env lang p r = Mux.debug (show r) >> case r of Mux.debug $ "runLocal Pure" liftIO $ eval lang t runLocal (Send c@(Channel cid) a) = do - Mux.warn $ "runLocal Send " ++ show c ++ " " ++ show a + Mux.debug $ "runLocal Send " ++ show c ++ " " ++ show a a <- liftIO $ eval lang a - Mux.warn $ "runLocal Send[2] " ++ show c ++ " " ++ show a + Mux.debug $ "runLocal Send[2] " ++ show c ++ " " ++ show a Mux.process1 (Mux.Packet cid (Put.runPutS (serialize a))) pure (unit lang) runLocal (Sleep (Seconds seconds)) = do diff --git a/unison-src/dindex-main.u b/unison-src/dindex-main.u new file mode 100644 index 000000000..662c6e4ae --- /dev/null +++ b/unison-src/dindex-main.u @@ -0,0 +1,11 @@ + +do Remote + root := Remote.spawn; + Remote.transfer root; + ind := DIndex.empty; + nodes := Remote.replicate 10 Remote.spawn; + Remote.traverse (node -> Remote.at' node (DIndex.join ind)) nodes; + DIndex.insert "It's..." "ALIIIVE!!!!" ind; + -- Remote.parallel-traverse DIndex.Timeout (k -> DIndex.insert k k ind) (Vector.range 0 5); + r := DIndex.lookup "It's..." ind; + pure (Debug.watch "result" r);; diff --git a/unison-src/dindex.u b/unison-src/dindex.u index 04f708023..245b1b07e 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -35,9 +35,7 @@ DIndex.lookup k ind = do Remote nind;;) ; -- todo: use Remote.quorum here - -- Remote.race DIndex.Timeout <| Vector.map localLookup nodes;; - rs := Remote.traverse localLookup nodes; - pure (Vector.at 0 rs |> Optional.bind identity);; + Remote.race DIndex.Timeout <| Vector.map localLookup nodes;; ; DIndex.insert : ∀ k v . k -> v -> DIndex k v -> Remote Unit; @@ -86,6 +84,7 @@ DIndex.rebalance k ind = do Remote ov;;) ;; ; + DIndex.leave : ∀ k v . Node -> DIndex k v -> Remote Unit; DIndex.leave node ind = do Remote local-ind := Index.lookup node ind; From dbb2cd0de296e42ddfae12eae338080b0c999274 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 26 Sep 2016 17:59:13 -0400 Subject: [PATCH 51/61] convert to strict interpreter, add if-then-else syntax --- shared/src/Unison/Eval/Interpreter.hs | 22 ++++++++++++++-- shared/src/Unison/Node/Builtin.hs | 12 --------- shared/src/Unison/Term.hs | 4 +++ shared/src/Unison/TermParser.hs | 15 +++++++++-- shared/src/Unison/Typechecker/Context.hs | 9 ++++--- shared/tests/Unison/Test/Interpreter.hs | 24 +++++++++--------- unison-src/base.u | 32 ++++++++++++------------ 7 files changed, 70 insertions(+), 48 deletions(-) diff --git a/shared/src/Unison/Eval/Interpreter.hs b/shared/src/Unison/Eval/Interpreter.hs index 473c99c8f..9da93e5cc 100644 --- a/shared/src/Unison/Eval/Interpreter.hs +++ b/shared/src/Unison/Eval/Interpreter.hs @@ -9,6 +9,7 @@ import Unison.Eval import Unison.Term (Term) import Unison.Var (Var) import qualified Data.Map as M +import qualified Data.Text as Text import qualified Unison.ABT as ABT import qualified Unison.Reference as R import qualified Unison.Term as E @@ -30,6 +31,14 @@ eval env = Eval whnf step reduce resolveRef f args = do f <- whnf resolveRef f case f of + E.If' -> case take 3 args of + [cond,t,f] -> do + cond <- whnf resolveRef cond + case cond of + E.Builtin' c | Text.head c == 'F' -> pure . Just $ foldl E.app f (drop 3 args) + | otherwise -> pure . Just $ foldl E.app t (drop 3 args) + _ -> pure Nothing + _ -> pure Nothing E.Ref' h -> case M.lookup h env of Nothing -> pure Nothing Just op | length args >= arity op -> @@ -64,11 +73,20 @@ eval env = Eval whnf step Just op | arity op == 0 -> call op [] _ -> pure e E.Ann' e _ -> whnf resolveRef e + E.Apps' E.If' (cond:t:f:tl) -> do + cond <- whnf resolveRef cond + case cond of + E.Builtin' b | Text.head b == 'F' -> whnf resolveRef f >>= \f -> whnf resolveRef (f `E.apps` tl) + | otherwise -> whnf resolveRef t >>= \t -> whnf resolveRef (t `E.apps` tl) + _ -> pure e E.App' f x -> do f' <- E.link resolveRef f + x <- whnf resolveRef x e' <- reduce resolveRef f' [x] - maybe (pure e) (whnf resolveRef) e' - E.Let1' binding body -> whnf resolveRef (ABT.bind body binding) + maybe (pure $ f' `E.app` x) (whnf resolveRef) e' + E.Let1' binding body -> do + binding <- whnf resolveRef binding + whnf resolveRef (ABT.bind body binding) E.LetRecNamed' bs body -> whnf resolveRef (ABT.substs substs body) where expandBinding v (E.LamNamed' name body) = E.lam name (expandBinding v body) expandBinding v body = ABT.substs substs' body diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index fe89a8599..a6af07d12 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -136,18 +136,6 @@ makeBuiltins logger whnf = op _ = error "unpossible" typ = "Boolean -> Boolean" in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "not") - , let r = R.Builtin "Boolean.if"; - op [cond,t,f] = do - cond <- whnf cond - case cond of - Term.Builtin' tf -> case Text.head tf of - 'T' -> whnf t - 'F' -> whnf f - _ -> error "unpossible" - _ -> error "unpossible" - op _ = error "unpossible" - typ = "forall a . Boolean -> a -> a -> a" - in (r, Just (I.Primop 3 op), unsafeParseType typ, prefix "if") -- Number , let r = R.Builtin "Number.+" diff --git a/shared/src/Unison/Term.hs b/shared/src/Unison/Term.hs index 273b62349..29f9c6c43 100644 --- a/shared/src/Unison/Term.hs +++ b/shared/src/Unison/Term.hs @@ -45,11 +45,13 @@ import qualified Unison.Remote as Remote data Literal = Number Double | Text Text + | If deriving (Eq,Ord,Generic) instance Hashable Literal where tokens (Number d) = [Hashable.Tag 0, Hashable.Double d] tokens (Text txt) = [Hashable.Tag 1, Hashable.Text txt] + tokens If = [Hashable.Tag 2] -- | Base functor for terms in the Unison language data F v a @@ -119,6 +121,7 @@ pattern Var' v <- ABT.Var' v pattern Lit' l <- (ABT.out -> ABT.Tm (Lit l)) pattern Number' n <- Lit' (Number n) pattern Text' s <- Lit' (Text s) +pattern If' <- Lit' If pattern Blank' <- (ABT.out -> ABT.Tm Blank) pattern Ref' r <- (ABT.out -> ABT.Tm (Ref r)) pattern Builtin' r <- (ABT.out -> ABT.Tm (Ref (Builtin r))) @@ -334,6 +337,7 @@ instance (Ord v, FromJSON v) => J.FromJSON1 (F v) where parseJSON1 j = Aeson.par instance Show Literal where show (Text t) = show t + show If = "if" show (Number n) = case floor n of m | fromIntegral m == n -> show (m :: Int) _ -> show n diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index 0656775de..01713c869 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -42,7 +42,7 @@ term2 :: Var v => Parser (S v) (Term v) term2 = let_ term3 <|> term3 term3 :: Var v => Parser (S v) (Term v) -term3 = infixApp term4 <|> term4 +term3 = ifthen <|> infixApp term4 <|> term4 infixApp :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) infixApp p = f <$> arg <*> some ((,) <$> infixVar <*> arg) @@ -62,6 +62,17 @@ term5 = lam term <|> effectBlock <|> termLeaf termLeaf :: Var v => Parser (S v) (Term v) termLeaf = asum [hashLit, prefixTerm, lit, tupleOrParenthesized term, blank, vector term] +ifthen :: Var v => Parser (S v) (Term v) +ifthen = do + _ <- token (string "if") + scope "if-then-else" . commit $ do + cond <- attempt term + _ <- token (string "then") + iftrue <- attempt term + _ <- token (string "else") + iffalse <- term + pure (Term.apps (Term.lit Term.If) [cond, iftrue, iffalse]) + tupleOrParenthesized :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) tupleOrParenthesized rec = parenthesized $ go <$> sepBy1 (token $ string ",") rec where @@ -198,7 +209,7 @@ prefixTerm :: Var v => Parser (S v) (Term v) prefixTerm = Term.var <$> prefixVar keywords :: [String] -keywords = ["alias", "do", "let", "rec", "in", "->", ":", "=", "where"] +keywords = ["alias", "do", "let", "rec", "in", "->", ":", "=", "where", "else", "then"] lam :: Var v => Parser (S v) (Term v) -> Parser (S v) (Term v) lam p = Term.lam'' <$> vars <* arrow <*> body diff --git a/shared/src/Unison/Typechecker/Context.hs b/shared/src/Unison/Typechecker/Context.hs index 6474a3b6b..728163e4d 100644 --- a/shared/src/Unison/Typechecker/Context.hs +++ b/shared/src/Unison/Typechecker/Context.hs @@ -477,10 +477,11 @@ annotateLetRecBindings letrec = do pure $ (marker, body) -- | Infer the type of a literal -synthLit :: Ord v => Term.Literal -> Type v -synthLit lit = Type.lit $ case lit of - Term.Number _ -> Type.Number - Term.Text _ -> Type.Text +synthLit :: Var v => Term.Literal -> Type v +synthLit lit = case lit of + Term.Number _ -> Type.lit Type.Number + Term.Text _ -> Type.lit Type.Text + Term.If -> Type.forall' ["a"] (Type.builtin "Boolean" --> Type.v' "a" --> Type.v' "a" --> Type.v' "a") -- | Synthesize the type of the given term, updating the context in the process. synthesize :: Var v => Term v -> M v (Type v) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 36abd3894..82bb2d919 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -15,8 +15,8 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "1 + 1 + 1" "3" , t "(x -> x) 42" "42" , t "let x = 2; y = 3 ; x + y;;" "5" - , t "if False 0 1" "1" - , t "if True 12 13" "12" + , t "if False then 0 else 1" "1" + , t "if True then 12 else 13" "12" , t "1 >_Number 0" "True" , t "1 ==_Number 1" "True" , t "2 ==_Number 0" "False" @@ -40,13 +40,13 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "False `and` False" "False" , t "not False" "True" , t "not True" "False" - , t "let rec fac n = if (n ==_Number 0) 1 (n * fac (n - 1)); fac 5;;" "120" - , t "let rec ping n = if (n >=_Number 10) n (pong (n + 1)); pong n = ping (n + 1); ping 0;;" + , t "let rec fac n = if n ==_Number 0 then 1 else n * fac (n - 1); fac 5;;" "120" + , t "let rec ping n = if n >=_Number 10 then n else pong (n + 1); pong n = ping (n + 1); ping 0;;" "10" , t "let id x = x; g = id 42; p = id \"hi\" ; g;;" "42" , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" ; g;;" "42" , t "(let id x = x; id;; : forall a . a -> a) 42" "42" - , t "Optional.map ((+) 1) (Some 1)" "Some (1 + 1)" + , t "Optional.map ((+) 1) (Some 1)" "Some 2" , t "Either.fold ((+) 1) ((+) 2) (Left 1)" "2" , t "Either.fold ((+) 1) ((+) 2) (Right 1)" "3" , t "Either.swap (Left 1)" "Either.Right 1" @@ -56,13 +56,13 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "2nd (1,2 + 1,3,4)" "3" , t "identity <| (1 + 1)" "2" , t "(1 + 1) |> identity" "2" - , t "if (\"hi\" ==_Text \"hi\") 1 2" "1" - , t "if (\"hi\" <_Text \"hiya\") 1 2" "1" - , t "if (\"hi\" <=_Text \"hiya\") 1 2" "1" - , t "if (\"hiya\" >_Text \"hi\") 1 2" "1" - , t "if (\"hiya\" >=_Text \"hi\") 1 2" "1" - , t "if (\"hi\" >=_Text \"hi\") 1 2" "1" - , t "if (\"hi\" <=_Text \"hi\") 1 2" "1" + , t "if \"hi\" ==_Text \"hi\" then 1 else 2" "1" + , t "if \"hi\" <_Text \"hiya\" then 1 else 2" "1" + , t "if \"hi\" <=_Text \"hiya\" then 1 else 2" "1" + , t "if \"hiya\" >_Text \"hi\" then 1 else 2" "1" + , t "if \"hiya\" >=_Text \"hi\" then 1 else 2" "1" + , t "if \"hi\" >=_Text \"hi\" then 1 else 2" "1" + , t "if \"hi\" <=_Text \"hi\" then 1 else 2" "1" , t "Vector.reverse [1,2,3]" "[3,2,1]" , t "Vector.reverse Vector.empty" "[]" , t "Vector.fold-right Vector.prepend Vector.empty [1,2,3]" "[1,2,3]" diff --git a/unison-src/base.u b/unison-src/base.u index 1a65ad154..0400abd3b 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -3,8 +3,8 @@ identity a = a; const x y = x; -then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; -then f1 f2 x = f2 (f1 x); +and-then : ∀ a b c . (a -> b) -> (b -> c) -> a -> c; +and-then f1 f2 x = f2 (f1 x); (|>) : ∀ a b . a -> (a -> b) -> b; a |> f = f a; @@ -22,10 +22,10 @@ rest : ∀ a b . Pair a b -> b; rest p = Pair.fold (x y -> y) p; 1st = first; -2nd = rest `then` first; -3rd = rest `then` (rest `then` first); -4th = rest `then` (rest `then` (rest `then` first)); -5th = rest `then` (rest `then` (rest `then` (rest `then` first))); +2nd = rest `and-then` first; +3rd = rest `and-then` (rest `and-then` first); +4th = rest `and-then` (rest `and-then` (rest `and-then` first)); +5th = rest `and-then` (rest `and-then` (rest `and-then` (rest `and-then` first))); set-1st : ∀ a a2 b . a2 -> Pair a b -> Pair a2 b; set-1st new-first p = Pair new-first (rest p); @@ -63,10 +63,10 @@ Vector.fold-balanced : ∀ a . (a -> a -> a) -> a -> Vector a -> a; Vector.fold-balanced plus zero vs = let rec go plus zero vs = - if (Vector.size vs <=_Number 2) - (Vector.fold-left plus zero vs) - (let p = Vector.halve vs; - go plus zero (1st p) `plus` go plus zero (2nd p);;); + if Vector.size vs <=_Number 2 + then Vector.fold-left plus zero vs + else (let p = Vector.halve vs; + go plus zero (1st p) `plus` go plus zero (2nd p);;); go plus zero vs;; ; @@ -74,13 +74,13 @@ Vector.all? : ∀ a . (a -> Boolean) -> Vector a -> Boolean; Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); Vector.sort : ∀ k a . Order k -> (a -> k) -> Vector a -> Vector a; -Vector.sort ok f v = Vector.sort-keyed (f `then` Order.key ok) v; +Vector.sort ok f v = Vector.sort-keyed (f `and-then` Order.key ok) v; Vector.sort' : ∀ a . Order a -> Vector a -> Vector a; Vector.sort' o = Vector.sort o identity; Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; -Remote.map f = Remote.bind (f `then` Remote.pure); +Remote.map f = Remote.bind (f `and-then` Remote.pure); Remote.map2 : ∀ a b c . (a -> b -> c) -> Remote a -> Remote b -> Remote c; Remote.map2 f a b = do Remote @@ -148,7 +148,7 @@ Remote.traverse : ∀ a b . (a -> Remote b) -> Vector a -> Remote (Vector b); Remote.traverse f vs = Vector.fold-balanced (Remote.map2 Vector.concatenate) (Remote.pure Vector.empty) - (Vector.map (f `then` Remote.map Vector.single) vs); + (Vector.map (f `and-then` Remote.map Vector.single) vs); Remote.sequence : ∀ a . Vector (Remote a) -> Remote (Vector a); Remote.sequence vs = @@ -158,7 +158,7 @@ Remote.sequence vs = Remote.parallel-traverse : ∀ a b . Duration -> (a -> Remote b) -> Vector a -> Remote (Vector b); Remote.parallel-traverse timeout f vs = do Remote - futures := Remote.traverse (f `then` Remote.start timeout) vs; + futures := Remote.traverse (f `and-then` Remote.start timeout) vs; Remote.sequence futures;; ; @@ -168,7 +168,7 @@ Remote.quorum : ∀ a b . Duration -> Number -> (a -> Remote b) -> Vector a -> R Remote.quorum timeout n = _; -- todo Optional.map : ∀ a b . (a -> b) -> Optional a -> Optional b; -Optional.map f = Optional.fold None (f `then` Some); +Optional.map f = Optional.fold None (f `and-then` Some); Optional.bind : ∀ a b . (a -> Optional b) -> Optional a -> Optional b; Optional.bind f = Optional.fold None f; @@ -190,7 +190,7 @@ Optional.map2 f a b = do Optional ; Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; -Either.map f = Either.fold Left (f `then` Right); +Either.map f = Either.fold Left (f `and-then` Right); Either.pure : ∀ a b . b -> Either a b; Either.pure = Right; From c7e698b036fa9316da15d67d0b5a3c9995064b73 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 26 Sep 2016 20:21:38 -0400 Subject: [PATCH 52/61] missed a few eval calls --- shared/src/Unison/Eval/Interpreter.hs | 12 ++++++++---- unison-src/dindex.u | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/shared/src/Unison/Eval/Interpreter.hs b/shared/src/Unison/Eval/Interpreter.hs index 9da93e5cc..bcddb95b5 100644 --- a/shared/src/Unison/Eval/Interpreter.hs +++ b/shared/src/Unison/Eval/Interpreter.hs @@ -26,8 +26,12 @@ eval :: forall f v . (Monad f, Var v) => Map R.Reference (Primop f v) -> Eval f eval env = Eval whnf step where -- reduce x args | trace ("reduce:" ++ show (x:args)) False = undefined - reduce resolveRef (E.App' f x) args = reduce resolveRef f (x:args) - reduce resolveRef (E.Let1' binding body) xs = reduce resolveRef (ABT.bind body binding) xs + reduce resolveRef (E.App' f x) args = do + x <- whnf resolveRef x + reduce resolveRef f (x:args) + reduce resolveRef (E.Let1' binding body) xs = do + binding <- whnf resolveRef binding + reduce resolveRef (ABT.bind body binding) xs reduce resolveRef f args = do f <- whnf resolveRef f case f of @@ -76,8 +80,8 @@ eval env = Eval whnf step E.Apps' E.If' (cond:t:f:tl) -> do cond <- whnf resolveRef cond case cond of - E.Builtin' b | Text.head b == 'F' -> whnf resolveRef f >>= \f -> whnf resolveRef (f `E.apps` tl) - | otherwise -> whnf resolveRef t >>= \t -> whnf resolveRef (t `E.apps` tl) + E.Builtin' b | Text.head b == 'F' -> whnf resolveRef f >>= \f -> (`E.apps` tl) <$> whnf resolveRef f + | otherwise -> whnf resolveRef t >>= \t -> (`E.apps` tl) <$> whnf resolveRef t _ -> pure e E.App' f x -> do f' <- E.link resolveRef f diff --git a/unison-src/dindex.u b/unison-src/dindex.u index 245b1b07e..40e4f8edc 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -68,16 +68,16 @@ DIndex.rebalance : ∀ k v . k -> DIndex k v -> Remote Unit; DIndex.rebalance k ind = do Remote indices := DIndex.indicesForKey k ind; t = DIndex.Timeout; - results := Remote.parallel-traverse DIndex.Max-Timeout (Index.lookup k `then` Remote.timeout t) indices; + results := Remote.parallel-traverse DIndex.Max-Timeout (Index.lookup k `and-then` Remote.timeout t) indices; resultsHashes := Remote.traverse hash! results; uh := hash! None; hd = uh `Optional.get-or` Vector.at 0 resultsHashes; eq = h1 h2 -> Hash.erase h1 ==_Hash Hash.erase h2; - if (Vector.all? (eq hd) resultsHashes) - -- all results matched, we're good - (pure Unit) - -- not all results matched, reinsert - (do Remote + if Vector.all? (eq hd) resultsHashes + -- all results matched, we're good + then pure Unit + -- not all results matched, reinsert + else (do Remote ov := DIndex.lookup k ind; Optional.fold (pure Unit) (v -> DIndex.insert k v ind) From 3f51471b09945ecb15b2dc4b1ddd4067a483b20c Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 28 Sep 2016 10:39:38 -0400 Subject: [PATCH 53/61] fix interpreter bug caused by type annotations not being stripped out --- shared/src/Unison/Eval/Interpreter.hs | 10 ++++++---- shared/tests/Unison/Test/Interpreter.hs | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/src/Unison/Eval/Interpreter.hs b/shared/src/Unison/Eval/Interpreter.hs index bcddb95b5..51b703871 100644 --- a/shared/src/Unison/Eval/Interpreter.hs +++ b/shared/src/Unison/Eval/Interpreter.hs @@ -32,6 +32,7 @@ eval env = Eval whnf step reduce resolveRef (E.Let1' binding body) xs = do binding <- whnf resolveRef binding reduce resolveRef (ABT.bind body binding) xs + reduce resolveRef (E.Ann' e _) args = reduce resolveRef e args reduce resolveRef f args = do f <- whnf resolveRef f case f of @@ -83,11 +84,12 @@ eval env = Eval whnf step E.Builtin' b | Text.head b == 'F' -> whnf resolveRef f >>= \f -> (`E.apps` tl) <$> whnf resolveRef f | otherwise -> whnf resolveRef t >>= \t -> (`E.apps` tl) <$> whnf resolveRef t _ -> pure e - E.App' f x -> do + E.Apps' f xs -> do f' <- E.link resolveRef f - x <- whnf resolveRef x - e' <- reduce resolveRef f' [x] - maybe (pure $ f' `E.app` x) (whnf resolveRef) e' + f <- whnf resolveRef f' + xs <- traverse (whnf resolveRef) xs + e' <- reduce resolveRef f xs + maybe (pure $ f `E.apps` xs) (whnf resolveRef) e' E.Let1' binding body -> do binding <- whnf resolveRef binding whnf resolveRef (ABT.bind body binding) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 82bb2d919..d37445b97 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -47,6 +47,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "let id : forall a . a -> a; id x = x; g = id 42; p = id \"hi\" ; g;;" "42" , t "(let id x = x; id;; : forall a . a -> a) 42" "42" , t "Optional.map ((+) 1) (Some 1)" "Some 2" + , t "Optional.map ((+) 1) ((Some: ∀ a . a -> Optional a) 1)" "Some 2" , t "Either.fold ((+) 1) ((+) 2) (Left 1)" "2" , t "Either.fold ((+) 1) ((+) 2) (Right 1)" "3" , t "Either.swap (Left 1)" "Either.Right 1" From dc491cf88125bf594242d03cb6df4792207afeaa Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Fri, 30 Sep 2016 12:55:33 -0400 Subject: [PATCH 54/61] fix issue with interpreter handling of let rec --- shared/src/Unison/Eval/Interpreter.hs | 7 +++---- shared/src/Unison/Node/Builtin.hs | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/shared/src/Unison/Eval/Interpreter.hs b/shared/src/Unison/Eval/Interpreter.hs index 51b703871..b192341c3 100644 --- a/shared/src/Unison/Eval/Interpreter.hs +++ b/shared/src/Unison/Eval/Interpreter.hs @@ -93,9 +93,8 @@ eval env = Eval whnf step E.Let1' binding body -> do binding <- whnf resolveRef binding whnf resolveRef (ABT.bind body binding) - E.LetRecNamed' bs body -> whnf resolveRef (ABT.substs substs body) where + E.LetRecNamed' bs body -> whnf resolveRef (ABT.substs bs' body) where + bs' = [ (v, expandBinding v b) | (v,b) <- bs ] expandBinding v (E.LamNamed' name body) = E.lam name (expandBinding v body) - expandBinding v body = ABT.substs substs' body - where substs' = [ (v', ABT.subst v (E.letRec bs (E.var v)) b) | (v',b) <- bs ] - substs = [ (v, expandBinding v b) | (v,b) <- bs ] + expandBinding v body = E.letRec bs body _ -> pure e diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index a6af07d12..3b687f44a 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -208,6 +208,8 @@ makeBuiltins logger whnf = Term.Distributed' (Term.Remote (Remote.Step s)) -> pure $ Term.remote (Remote.Bind s g) Term.Distributed' (Term.Remote (Remote.Bind s f)) -> pure $ Term.remote (Remote.Bind s (kcomp f g)) _ -> fail $ "Remote.bind given a value that was not a Remote: " ++ show r + ++ " " + ++ show (ABT.freeVars r) op _ = fail "Remote.bind unpossible" in (r, Just (I.Primop 2 op), remoteSignatureOf "Remote.bind", prefix "Remote.bind") , let r = R.Builtin "Remote.pure" From 47f891d1f57507f7d8caa7872251c8cae9b09f50 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Fri, 30 Sep 2016 14:34:33 -0400 Subject: [PATCH 55/61] fix issue with IndexedTraversal.intersect --- unison-src/base.u | 2 +- unison-src/extra.u | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/unison-src/base.u b/unison-src/base.u index 0400abd3b..35f170e7e 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -28,7 +28,7 @@ rest p = Pair.fold (x y -> y) p; 5th = rest `and-then` (rest `and-then` (rest `and-then` (rest `and-then` first))); set-1st : ∀ a a2 b . a2 -> Pair a b -> Pair a2 b; -set-1st new-first p = Pair new-first (rest p); +set-1st new-1st p = Pair new-1st (rest p); Order.compare : ∀ a . Order a -> a -> a -> Comparison; Order.compare o a1 a2 = Order.Key.compare (Order.key o a1) (Order.key o a2); diff --git a/unison-src/extra.u b/unison-src/extra.u index 36b31feb5..5f745dfa9 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -24,6 +24,10 @@ Index.delete k = Index.from-unsafe (Index.delete# k); Index.insert : ∀ k v . k -> v -> Index k v -> Remote Unit; Index.insert k v = Index.from-unsafe (Index.insert# k v); +Index.inserts : ∀ k v . Vector (k,v) -> Index k v -> Remote Unit; +Index.inserts vs ind = Remote.map (const Unit) <| + Remote.traverse (kv -> Index.insert (1st kv) (2nd kv) ind) vs; + Index.from-unsafe : ∀ k v r . (Text -> r) -> Index k v -> Remote r; Index.from-unsafe f ind = let p = Index.representation# ind; @@ -41,9 +45,17 @@ IndexedTraversal.1st-key t = 1st t; IndexedTraversal.lookup : ∀ k v . k -> IndexedTraversal k v -> Remote (Optional v); IndexedTraversal.lookup k t = 2nd t k; +-- | Returns the smallest key in the traversal which is > the provided key. IndexedTraversal.increment : ∀ k v . k -> IndexedTraversal k v -> Remote (Optional k); IndexedTraversal.increment k t = 3rd t k; +-- | Returns the smallest key in the traversal which is >= the provided key. +IndexedTraversal.ceiling : ∀ k v . k -> IndexedTraversal k v -> Remote (Optional k); +IndexedTraversal.ceiling k t = + IndexedTraversal.lookup k t |> Remote.bind ( + Optional.fold (IndexedTraversal.increment k t) (const (pure <| Some k)) + ); + Index.traversal : ∀ k v . Index k v -> IndexedTraversal (k, Hash k) v; Index.traversal ind = let add-hash = Optional.map (k -> (k, hash# k)); @@ -64,9 +76,9 @@ IndexedTraversal.intersect : ∀ k v . Order k IndexedTraversal.intersect o t1 t2 = let rec align-key k1 k2 = Optional.get-or (Remote.pure None) <| Optional.map2 (k1 k2 -> Order.compare o k1 k2 |> Comparison.fold - (IndexedTraversal.increment k2 t1 |> Remote.bind (k1 -> align-key k1 (Some k2))) + (IndexedTraversal.ceiling k2 t1 |> Remote.bind (k1 -> align-key k1 (Some k2))) (Remote.pure (Some k1)) - (IndexedTraversal.increment k1 t2 |> Remote.bind (k2 -> align-key (Some k1) k2)) + (IndexedTraversal.ceiling k1 t2 |> Remote.bind (k2 -> align-key (Some k1) k2)) ) k1 k2 ; @@ -93,8 +105,8 @@ IndexedTraversal.take n t = t = 1st tn; n = 2nd tn; step e = (e, (set-1st (IndexedTraversal.increment (1st e) t) t, n - 1)); - if (n <=_Number 0) (Remote.pure None) - (IndexedTraversal.1st-entry t |> Remote.map (Optional.map step));; + if n <=_Number 0 then Remote.pure None + else IndexedTraversal.1st-entry t |> Remote.map (Optional.map step);; ); Http.get-url : Text -> Remote (Either Text Text); From 45a4274d22687344a0a7952e0187109e283ada20 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Sat, 1 Oct 2016 09:25:31 -0400 Subject: [PATCH 56/61] ABT.subst avoids inspecting/rebuilding subtrees that can't contain the target variable This sped up the unison-shared tests from 8-9s to 5-6s on my machine --- shared/src/Unison/ABT.hs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/shared/src/Unison/ABT.hs b/shared/src/Unison/ABT.hs index 72509b929..b9afdd147 100644 --- a/shared/src/Unison/ABT.hs +++ b/shared/src/Unison/ABT.hs @@ -221,11 +221,20 @@ freshNamed' used n = fresh' used (v' n) -- | `subst v e body` substitutes `e` for `v` in `body`, avoiding capture by -- renaming abstractions in `body` --- TODO: avoid traversing subtrees that cannot contain the free variable subst :: (Foldable f, Functor f, Var v) => v -> Term f v a -> Term f v a -> Term f v a -subst v = replace match where - match (Var' v') = v == v' - match _ = False +subst v r t2@(Term fvs ann body) + | Set.notMember v fvs = t2 -- subtrees not containing the var can be skipped + | otherwise = case body of + Var v' | v == v' -> r -- var match; perform replacement + | otherwise -> t2 -- var did not match one being substituted; ignore + Cycle body -> cycle' ann (subst v r body) + Abs x e | x == v -> t2 -- x shadows v; ignore subtree + Abs x e -> abs' ann x' e' + where x' = freshInBoth r t2 x + -- rename x to something that cannot be captured by `r` + e' = if x /= x' then subst v r (rename x x' e) + else subst v r e + Tm body -> tm' ann (fmap (subst v r) body) -- | `substs [(t1,v1), (t2,v2), ...] body` performs multiple simultaneous -- substitutions, avoiding capture From d18364fa84acc3bc40eeffdc8add8acc3a73eb80 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Mon, 3 Oct 2016 13:50:48 -0400 Subject: [PATCH 57/61] Fix quadratic performance bug in interpreter, added simple test of indexed traversal --- shared/src/Unison/Eval/Interpreter.hs | 36 ++++++++++++++++++--------- shared/src/Unison/Node/Builtin.hs | 2 +- shared/src/Unison/Remote.hs | 3 ++- shared/src/Unison/Term.hs | 17 ++++++------- unison-src/indexed-traversal.u | 13 ++++++++++ 5 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 unison-src/indexed-traversal.u diff --git a/shared/src/Unison/Eval/Interpreter.hs b/shared/src/Unison/Eval/Interpreter.hs index b192341c3..eefc00451 100644 --- a/shared/src/Unison/Eval/Interpreter.hs +++ b/shared/src/Unison/Eval/Interpreter.hs @@ -1,9 +1,12 @@ {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE BangPatterns #-} + -- | Very simple and inefficient interpreter of Unison terms module Unison.Eval.Interpreter where import Data.Map (Map) +import Data.List import Debug.Trace import Unison.Eval import Unison.Term (Term) @@ -26,13 +29,13 @@ eval :: forall f v . (Monad f, Var v) => Map R.Reference (Primop f v) -> Eval f eval env = Eval whnf step where -- reduce x args | trace ("reduce:" ++ show (x:args)) False = undefined + reduce resolveRef (E.Ann' e _) args = reduce resolveRef e args reduce resolveRef (E.App' f x) args = do x <- whnf resolveRef x reduce resolveRef f (x:args) reduce resolveRef (E.Let1' binding body) xs = do binding <- whnf resolveRef binding reduce resolveRef (ABT.bind body binding) xs - reduce resolveRef (E.Ann' e _) args = reduce resolveRef e args reduce resolveRef f args = do f <- whnf resolveRef f case f of @@ -45,25 +48,29 @@ eval env = Eval whnf step _ -> pure Nothing _ -> pure Nothing E.Ref' h -> case M.lookup h env of - Nothing -> pure Nothing + Nothing -> case h of + R.Derived h -> do + r <- resolveRef h + r <- whnf resolveRef r + reduce resolveRef r args + R.Builtin b -> pure Nothing Just op | length args >= arity op -> call op (take (arity op) args) >>= \e -> pure . Just $ foldl E.app e (drop (arity op) args) Just _ | otherwise -> pure Nothing - E.Lam' f -> case args of + E.LamsNamed' vs body -> let n = length vs in case args of [] -> pure Nothing - (arg1:args) -> - let r = ABT.bind f arg1 - in pure $ Just (foldl E.app r args) + args | length args >= n -> pure $ Just (foldl' E.app (ABT.substs (vs `zip` args) body) (drop n args)) + | otherwise -> pure Nothing _ -> pure Nothing step resolveRef e = case e of + E.Ann' e _ -> step resolveRef e E.Ref' h -> case M.lookup h env of Just op | arity op == 0 -> call op [] _ -> pure e - E.App' f x -> do - f <- E.link resolveRef f - e' <- reduce resolveRef f [x] + E.Apps' f xs -> do + e' <- reduce resolveRef f xs maybe (pure e) pure e' E.Let1' binding body -> step resolveRef (ABT.bind body binding) E.LetRecNamed' bs body -> step resolveRef (ABT.substs substs body) where @@ -76,7 +83,12 @@ eval env = Eval whnf step whnf resolveRef e = case e of E.Ref' h -> case M.lookup h env of Just op | arity op == 0 -> call op [] - _ -> pure e + | otherwise -> pure e + Nothing -> case h of + R.Derived h -> do + r <- resolveRef h + whnf resolveRef r + R.Builtin b -> pure e E.Ann' e _ -> whnf resolveRef e E.Apps' E.If' (cond:t:f:tl) -> do cond <- whnf resolveRef cond @@ -85,9 +97,8 @@ eval env = Eval whnf step | otherwise -> whnf resolveRef t >>= \t -> (`E.apps` tl) <$> whnf resolveRef t _ -> pure e E.Apps' f xs -> do - f' <- E.link resolveRef f - f <- whnf resolveRef f' xs <- traverse (whnf resolveRef) xs + f <- whnf resolveRef f e' <- reduce resolveRef f xs maybe (pure $ f `E.apps` xs) (whnf resolveRef) e' E.Let1' binding body -> do @@ -97,4 +108,5 @@ eval env = Eval whnf step bs' = [ (v, expandBinding v b) | (v,b) <- bs ] expandBinding v (E.LamNamed' name body) = E.lam name (expandBinding v body) expandBinding v body = E.letRec bs body + E.Vector' es -> E.vector' <$> traverse (whnf resolveRef) es _ -> pure e diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 3b687f44a..59df8b105 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -297,7 +297,7 @@ makeBuiltins logger whnf = op [fz,f,o] = whnf o >>= \o -> case o of Term.Builtin' tag | tag == "Optional.None" -> whnf fz Term.App' (Term.Builtin' tag) a | tag == "Optional.Some" -> whnf (f `Term.app` a) - _ -> error "Optional.fold unpossible" + _ -> error $ "Optional.fold unpossible: " ++ show o op _ = error "Optional.fold unpossible" in (r, Just (I.Primop 3 op), unsafeParseType "forall a r . r -> (a -> r) -> Optional a -> r", prefix "Optional.fold") diff --git a/shared/src/Unison/Remote.hs b/shared/src/Unison/Remote.hs index cc7ef517f..079513575 100644 --- a/shared/src/Unison/Remote.hs +++ b/shared/src/Unison/Remote.hs @@ -123,7 +123,8 @@ instance Hashable1 Local where Receive c -> [tag 4, H.accumulateToken c] Send c t -> [tag 5, H.accumulateToken c, hashed t] Spawn -> [tag 6] - Pure t -> [tag 7, hashed t] + Sleep (Seconds d) -> [tag 7, H.Double d] + Pure t -> [tag 8, hashed t] where tag = H.Tag hashed1 = H.Hashed . (H.hash1 hashCycle hash) diff --git a/shared/src/Unison/Term.hs b/shared/src/Unison/Term.hs index 29f9c6c43..c93539af9 100644 --- a/shared/src/Unison/Term.hs +++ b/shared/src/Unison/Term.hs @@ -262,6 +262,14 @@ unApps t = case go t [] of [] -> Nothing; f:args -> Just (f,args) go _ [] = [] go fn args = fn:args +pattern LamsNamed' vs body <- (unLams' -> Just (vs, body)) + +unLams' :: Term v -> Maybe ([v], Term v) +unLams' (LamNamed' v body) = case unLams' body of + Nothing -> Just ([v], body) + Just (vs, body) -> Just (v:vs, body) +unLams' _ = Nothing + dependencies' :: Ord v => Term v -> Set Reference dependencies' t = Set.fromList . Writer.execWriter $ ABT.visit' f t where f t@(Ref r) = Writer.tell [r] *> pure t @@ -275,15 +283,6 @@ countBlanks t = Monoid.getSum . Writer.execWriter $ ABT.visit' f t where f Blank = Writer.tell (Monoid.Sum (1 :: Int)) *> pure Blank f t = pure t --- | Convert all 'Ref' constructors to the corresponding term -link :: (Applicative f, Monad f, Var v) => (Hash -> f (Term v)) -> Term v -> f (Term v) -link env e = - let ds = map (\h -> (h, link env =<< env h)) (Set.toList (dependencies e)) - sub e (h, ft) = replace <$> ft - where replace t = ABT.replace ((==) rt) t e - rt = ref (Reference.Derived h) - in foldM sub e ds - -- | If the outermost term is a function application, -- perform substitution of the argument into the body betaReduce :: Var v => Term v -> Term v diff --git a/unison-src/indexed-traversal.u b/unison-src/indexed-traversal.u new file mode 100644 index 000000000..13653a2a8 --- /dev/null +++ b/unison-src/indexed-traversal.u @@ -0,0 +1,13 @@ + +do Remote + n := Remote.spawn; + Remote.transfer n; + ind1 := Index.empty; + ind2 := Index.empty; + Index.inserts [(1,"a"), (3,"b"), (9,"c")] ind1; + t1 = Index.traversal ind1; + Index.inserts [(3,"a"), (4,"b"), (9,"c"), (10, "d")] ind2; + t2 = Index.traversal ind2; + t3 = IndexedTraversal.intersect (Order.by-2nd Hash.Order) t1 t2; + vs := IndexedTraversal.take 10 t3; + pure (Debug.watch "result" vs);; From e3e134cb83c35d7d72f73137210c9cff556ea12a Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 4 Oct 2016 20:37:59 -0400 Subject: [PATCH 58/61] Text.take/drop/words, Vector.dedup-adjacent --- shared/src/Unison/Node/Builtin.hs | 33 +++++++++++++++++++++++-- shared/src/Unison/TermParser.hs | 2 +- shared/tests/Unison/Test/Interpreter.hs | 1 + unison-src/base.u | 23 +++++++++++++++++ unison-src/extra.u | 9 ++++--- 5 files changed, 62 insertions(+), 6 deletions(-) diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index 59df8b105..ba54aa678 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -10,7 +10,7 @@ import Unison.Term (Term) import Unison.Type (Type) import Unison.Typechecker.Context (remoteSignatureOf) import Unison.Util.Logger (Logger) -import Control.Concurrent (threadDelay) +import qualified Data.Char as Char import qualified Data.Vector as Vector import qualified Data.Text as Text import qualified Unison.ABT as ABT @@ -258,6 +258,35 @@ makeBuiltins logger whnf = in (r, Just (string2' (Term.ref r) (>=)), textCompareTyp, prefix "Text.>=") , let r = R.Builtin "Text.Order" in (r, Nothing, unsafeParseType "Order Text", prefix "Text.Order") + , let r = R.Builtin "Text.lowercase" + op [Term.Text' txt] = pure $ Term.text (Text.toLower txt) + op _ = error "Text.lowercase unpossible" + typ = "Text -> Text" + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Text.lowercase") + , let r = R.Builtin "Text.uppercase" + op [Term.Text' txt] = pure $ Term.text (Text.toUpper txt) + op _ = error "Text.uppercase unpossible" + typ = "Text -> Text" + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Text.lowercase") + , let r = R.Builtin "Text.take" + op [Term.Number' n, Term.Text' txt] = pure $ Term.text (Text.take (floor n) txt) + op _ = error "Text.take unpossible" + typ = "Number -> Text -> Text" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Text.take") + , let r = R.Builtin "Text.drop" + op [Term.Number' n, Term.Text' txt] = pure $ Term.text (Text.drop (floor n) txt) + op _ = error "Text.drop unpossible" + typ = "Number -> Text -> Text" + in (r, Just (I.Primop 2 op), unsafeParseType typ, prefix "Text.drop") + , -- todo: rather special purpose, remove this in favor of more generic regex + let r = R.Builtin "Text.words" + op [Term.Text' txt] = pure $ + let words = map stripPunctuation $ Text.split Char.isSpace txt + stripPunctuation word = Text.dropAround (not . Char.isAlphaNum) word + in Term.vector (map Term.text . filter (not . Text.null) $ words) + op _ = error "Text.words unpossible" + typ = "Text -> Vector Text" + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Text.words") -- Pair , let r = R.Builtin "Pair" @@ -501,7 +530,7 @@ makeBuiltins logger whnf = extractKey :: Term V -> [Either Double Text] extractKey (Term.App' _ t1) = go t1 where - go (Term.Builtin' u) = [] + go (Term.Builtin' _) = [] go (Term.App' (Term.Text' t) tl) = Right t : go tl go (Term.App' (Term.Number' n) tl) = Left n : go tl go (Term.App' (Term.Builtin' b) tl) = Right b : go tl diff --git a/shared/src/Unison/TermParser.hs b/shared/src/Unison/TermParser.hs index 01713c869..cbf0ec1cd 100644 --- a/shared/src/Unison/TermParser.hs +++ b/shared/src/Unison/TermParser.hs @@ -242,7 +242,7 @@ alias = do bindings :: Var v => Parser (S v) (Term v) -> Parser (S v) [(v, Term v)] bindings p = do s0 <- get; some (binding <* semicolon) <* set s0 where binding = do - _ <- optional alias + _ <- many alias typ <- optional (typedecl <* semicolon) (name, args) <- ( (\arg1 op arg2 -> (op,[arg1,arg2])) <$> prefixVar <*> infixVar <*> prefixVar) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index d37445b97..77b0b2386 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -72,6 +72,7 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.fold-balanced Vector.concatenate Vector.empty [[1],[2],[3,4],[5]]" "[1,2,3,4,5]" , t "Vector.fold-balanced (+) 0 [1,2,3]" "6" + , t "Vector.dedup-adjacent (==_Number) [1,1,2,2,3,4,4,4,4,5]" "[1,2,3,4,5]" , t "Vector.range 0 10" "[0,1,2,3,4,5,6,7,8,9]" , t "Vector.range 0 0" "[]" , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" diff --git a/unison-src/base.u b/unison-src/base.u index 35f170e7e..48b99b1f6 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -70,6 +70,13 @@ Vector.fold-balanced plus zero vs = go plus zero vs;; ; +Vector.fold-balanced1 : ∀ a . (a -> a -> a) -> Vector a -> Optional a; +Vector.fold-balanced1 f v = Vector.fold-balanced (Optional.lift-or f) None (Vector.map Some v); + +Optional.lift-or : ∀ a . (a -> a -> a) -> Optional a -> Optional a -> Optional a; +Optional.lift-or f = a1 a2 -> + a1 |> Optional.fold a2 (a1 -> Optional.fold None (a2 -> Some (f a1 a2)) a2); + Vector.all? : ∀ a . (a -> Boolean) -> Vector a -> Boolean; Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); @@ -79,6 +86,22 @@ Vector.sort ok f v = Vector.sort-keyed (f `and-then` Order.key ok) v; Vector.sort' : ∀ a . Order a -> Vector a -> Vector a; Vector.sort' o = Vector.sort o identity; +Vector.last : ∀ a . Vector a -> Optional a; +Vector.last v = Vector.at (Vector.size v - 1) v; + +Vector.1st : ∀ a . Vector a -> Optional a; +Vector.1st = Vector.at 0; + +Vector.dedup-adjacent : ∀ a . (a -> a -> Boolean) -> Vector a -> Vector a; +Vector.dedup-adjacent eq v = + Vector.fold-balanced + (v1 v2 -> + if Optional.map2 eq (Vector.last v1) (Vector.1st v2) |> Optional.get-or False + then Vector.concatenate v1 (Vector.drop 1 v2) + else Vector.concatenate v1 v2) + [] + (Vector.map Vector.pure v); + Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; Remote.map f = Remote.bind (f `and-then` Remote.pure); diff --git a/unison-src/extra.u b/unison-src/extra.u index 5f745dfa9..5a80e9f32 100644 --- a/unison-src/extra.u +++ b/unison-src/extra.u @@ -35,9 +35,9 @@ Index.from-unsafe f ind = let ; alias IndexedTraversal k v = - ( Remote (Optional k) - , k -> Remote (Optional v) - , k -> Remote (Optional k)); + ( Remote (Optional k) -- first key + , k -> Remote (Optional v) -- lookup the value for a key + , k -> Remote (Optional k)); -- increment a key IndexedTraversal.1st-key : ∀ k v . IndexedTraversal k v -> Remote (Optional k); IndexedTraversal.1st-key t = 1st t; @@ -109,6 +109,9 @@ IndexedTraversal.take n t = else IndexedTraversal.1st-entry t |> Remote.map (Optional.map step);; ); +IndexedTraversal.take-keys : ∀ k v . Number -> IndexedTraversal k v -> Remote (Vector k); +IndexedTraversal.take-keys n t = IndexedTraversal.take n t |> Remote.map (Vector.map 1st); + Http.get-url : Text -> Remote (Either Text Text); Http.get-url url = Remote.map Http.get-url# (Remote.pure url); From 9b4233e079efe11627fcfa288f5ac639b81d4927 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 4 Oct 2016 20:53:51 -0400 Subject: [PATCH 59/61] Vector.dedup and Order.equal --- shared/tests/Unison/Test/Interpreter.hs | 5 +++-- unison-src/base.u | 15 +++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 77b0b2386..69792f0f7 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -73,11 +73,12 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> "[1,2,3,4,5]" , t "Vector.fold-balanced (+) 0 [1,2,3]" "6" , t "Vector.dedup-adjacent (==_Number) [1,1,2,2,3,4,4,4,4,5]" "[1,2,3,4,5]" + , t "Vector.dedup Number.Order [1,2,1,5,4,2,4,4,3,5]" "[1,2,3,4,5]" , t "Vector.range 0 10" "[0,1,2,3,4,5,6,7,8,9]" , t "Vector.range 0 0" "[]" , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" - , t "Vector.sort Number.Order identity [5,2,1,3,4]" "[1,2,3,4,5]" - , t "Vector.sort (Order.invert Number.Order) identity [5,2,1,3,4]" "[5,4,3,2,1]" + , t "Vector.sort-by Number.Order identity [5,2,1,3,4]" "[1,2,3,4,5]" + , t "Vector.sort-by (Order.invert Number.Order) identity [5,2,1,3,4]" "[5,4,3,2,1]" , t "Vector.bind 2nd (Vector.zip [1,2,3] [[1],[2],[3]])" "[1,2,3]" , t "Vector.all? identity [True,True,True,True]" "True" , t "Vector.all? identity [True,False,True,True]" "False" diff --git a/unison-src/base.u b/unison-src/base.u index 48b99b1f6..dec07d88f 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -33,6 +33,10 @@ set-1st new-1st p = Pair new-1st (rest p); Order.compare : ∀ a . Order a -> a -> a -> Comparison; Order.compare o a1 a2 = Order.Key.compare (Order.key o a1) (Order.key o a2); +Order.equal : ∀ a . Order a -> a -> a -> Boolean; +Order.equal o a a2 = + Comparison.fold False True False (Order.compare o a a2); + Order.tuple2 : ∀ a b . Order a -> Order b -> Order (a,b); Order.tuple2 a b = Pair.Order a (Pair.Order b Unit.Order); @@ -80,11 +84,11 @@ Optional.lift-or f = a1 a2 -> Vector.all? : ∀ a . (a -> Boolean) -> Vector a -> Boolean; Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); -Vector.sort : ∀ k a . Order k -> (a -> k) -> Vector a -> Vector a; -Vector.sort ok f v = Vector.sort-keyed (f `and-then` Order.key ok) v; +Vector.sort-by : ∀ k a . Order k -> (a -> k) -> Vector a -> Vector a; +Vector.sort-by ok f v = Vector.sort-keyed (f `and-then` Order.key ok) v; -Vector.sort' : ∀ a . Order a -> Vector a -> Vector a; -Vector.sort' o = Vector.sort o identity; +Vector.sort : ∀ a . Order a -> Vector a -> Vector a; +Vector.sort o = Vector.sort-by o identity; Vector.last : ∀ a . Vector a -> Optional a; Vector.last v = Vector.at (Vector.size v - 1) v; @@ -102,6 +106,9 @@ Vector.dedup-adjacent eq v = [] (Vector.map Vector.pure v); +Vector.dedup : ∀ a . Order a -> Vector a -> Vector a; +Vector.dedup o v = Vector.dedup-adjacent (Order.equal o) (Vector.sort o v); + Remote.map : ∀ a b . (a -> b) -> Remote a -> Remote b; Remote.map f = Remote.bind (f `and-then` Remote.pure); From 75a24f5a7c9c9733be193746359ff4dcee7cec13 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Tue, 4 Oct 2016 21:00:34 -0400 Subject: [PATCH 60/61] update sort call, due to rename --- unison-src/dindex.u | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unison-src/dindex.u b/unison-src/dindex.u index 40e4f8edc..02e3fb75e 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -17,7 +17,7 @@ DIndex.nodesForKey k ind = do Remote nodes := Index.keys ind; hashes := Remote.traverse (node -> hash! (node, k)) nodes; (nodes `Vector.zip` hashes) - |> Vector.sort Hash.Order 2nd + |> Vector.sort-by Hash.Order 2nd |> Vector.take DIndex.Replication-Factor |> Vector.map 1st |> pure;; From 3fe4807d55438741ce88f193239c17c75b2fc7d3 Mon Sep 17 00:00:00 2001 From: Paul Chiusano Date: Wed, 5 Oct 2016 16:57:32 -0400 Subject: [PATCH 61/61] search engine implementation + various utilities and tweaks --- node/src/Unison/Runtime/ExtraBuiltins.hs | 19 +++++ node/src/Unison/Runtime/Html.hs | 15 +++- node/src/Unison/Runtime/Multiplex.hs | 4 +- node/unison-node.cabal | 3 + shared/src/Unison/Node/Builtin.hs | 2 +- shared/tests/Unison/Test/Interpreter.hs | 3 + unison-src/base.u | 42 ++++++++- unison-src/dindex.u | 2 +- unison-src/searchengine.u | 103 ++++++++++++++++++++--- 9 files changed, 174 insertions(+), 19 deletions(-) diff --git a/node/src/Unison/Runtime/ExtraBuiltins.hs b/node/src/Unison/Runtime/ExtraBuiltins.hs index 9845fd4d4..b466e20e2 100644 --- a/node/src/Unison/Runtime/ExtraBuiltins.hs +++ b/node/src/Unison/Runtime/ExtraBuiltins.hs @@ -13,6 +13,7 @@ import Unison.Type (Type) import Unison.Util.Logger (Logger) import qualified Data.Text as Text import qualified Data.Vector as Vector +import qualified Network.URI as URI import qualified Unison.Cryptography as C import qualified Unison.Eval.Interpreter as I import qualified Unison.Hash as Hash @@ -219,6 +220,24 @@ make _ blockStore crypto = do op _ = fail "Http.get-url# unpossible" in (r, Just (I.Primop 1 op), unsafeParseType "Text -> Either Text Text", prefix "Http.get-url#") + , let r = R.Builtin "Uri.parse-scheme" + op [Term.Text' url] = pure $ case URI.parseURI (Text.unpack url) of + Nothing -> none + Just uri -> some . Term.text . Text.pack $ URI.uriScheme uri + op _ = error "Uri.parse-scheme unpossible" + typ = "Text -> Optional Text" + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Uri.parse-scheme") + + , let r = R.Builtin "Uri.parse-authority" + op [Term.Text' url] = pure $ + case URI.parseURI (Text.unpack url) >>= URI.uriAuthority of + Nothing -> none + Just auth -> some . Term.text . Text.pack $ + URI.uriUserInfo auth ++ URI.uriRegName auth ++ URI.uriPort auth + op _ = error "Uri.parse-authority unpossible" + typ = "Text -> Optional Text" + in (r, Just (I.Primop 1 op), unsafeParseType typ, prefix "Uri.parse-authority") + -- Hashing -- add erase, comparison functions , let r = R.Builtin "hash#" diff --git a/node/src/Unison/Runtime/Html.hs b/node/src/Unison/Runtime/Html.hs index e14b34de8..99c1fb56c 100644 --- a/node/src/Unison/Runtime/Html.hs +++ b/node/src/Unison/Runtime/Html.hs @@ -2,7 +2,7 @@ module Unison.Runtime.Html where import Data.Maybe (listToMaybe, catMaybes, mapMaybe) import Data.Text (Text, toLower, pack) -import Text.HTML.TagSoup (Tag(..), (~/=), maybeTagText, parseTags, innerText) +import Text.HTML.TagSoup (Tag(..), (~/=), maybeTagText, parseTags, innerText, isTagOpenName, isTagComment, isTagCloseName) import qualified Data.Text as Text data Link = Link { ref :: Text, description :: Text } deriving (Show) @@ -26,4 +26,15 @@ getLinks :: Text -> [Link] getLinks s = mapMaybe sectionToLink . justAnchorSections $ parseTags s toPlainText :: Text -> Text -toPlainText s = innerText $ parseTags s +toPlainText s = innerText . ignores $ parseTags s + +ignores :: [Tag Text] -> [Tag Text] +ignores = go where + script = Text.pack "script" + style = Text.pack "style" + go [] = [] + go (hd:tl) = case hd of + _ | isTagOpenName script hd -> go (dropWhile (not . isTagCloseName script) tl) + | isTagOpenName style hd -> go (dropWhile (not . isTagCloseName style) tl) + | isTagComment hd -> go tl + | otherwise -> hd : go tl diff --git a/node/src/Unison/Runtime/Multiplex.hs b/node/src/Unison/Runtime/Multiplex.hs index fede66e1a..e6c428334 100644 --- a/node/src/Unison/Runtime/Multiplex.hs +++ b/node/src/Unison/Runtime/Multiplex.hs @@ -97,7 +97,7 @@ scope msg = local tweak where -- | Crash with a message. Include the current logging scope. crash :: String -> Multiplex a crash msg = do - warn msg + -- warn msg scope msg $ do l <- logger fail (show $ L.getScope l) @@ -118,7 +118,7 @@ process recv = scope "Mux.process" $ do callback <- atomically $ M.lookup destination cbs case callback of Nothing -> do - L.warn logger $ "dropped packet @ " ++ show (Base64.encode destination) + L.info logger $ "dropped packet @ " ++ show (Base64.encode destination) pure True Just callback -> do L.debug logger $ "packet delivered @ " ++ show (Base64.encode destination) diff --git a/node/unison-node.cabal b/node/unison-node.cabal index c802e633e..dd6c1dd2b 100644 --- a/node/unison-node.cabal +++ b/node/unison-node.cabal @@ -119,6 +119,7 @@ library mtl, murmur-hash, network, + network-uri, network-simple, prelude-extras, process, @@ -185,6 +186,7 @@ executable container memory, mmorph, mtl, + network-uri, process, safecopy, scotty, @@ -264,6 +266,7 @@ executable node memory, mtl, murmur-hash, + network-uri, prelude-extras, random, safecopy, diff --git a/shared/src/Unison/Node/Builtin.hs b/shared/src/Unison/Node/Builtin.hs index ba54aa678..e38d81de2 100644 --- a/shared/src/Unison/Node/Builtin.hs +++ b/shared/src/Unison/Node/Builtin.hs @@ -245,7 +245,7 @@ makeBuiltins logger whnf = -- Text , let r = R.Builtin "Text.concatenate" - in (r, Just (string2 (Term.ref r) mappend), strOpTyp, prefixes ["concatenate", "Text"]) + in (r, Just (string2 (Term.ref r) mappend), strOpTyp, prefix "Text.concatenate") , let r = R.Builtin "Text.==" in (r, Just (string2' (Term.ref r) (==)), textCompareTyp, prefix "Text.==") , let r = R.Builtin "Text.<" diff --git a/shared/tests/Unison/Test/Interpreter.hs b/shared/tests/Unison/Test/Interpreter.hs index 69792f0f7..1d1153dcc 100644 --- a/shared/tests/Unison/Test/Interpreter.hs +++ b/shared/tests/Unison/Test/Interpreter.hs @@ -74,6 +74,9 @@ tests = withResource Common.node (\_ -> pure ()) $ \node -> , t "Vector.fold-balanced (+) 0 [1,2,3]" "6" , t "Vector.dedup-adjacent (==_Number) [1,1,2,2,3,4,4,4,4,5]" "[1,2,3,4,5]" , t "Vector.dedup Number.Order [1,2,1,5,4,2,4,4,3,5]" "[1,2,3,4,5]" + , t "Vector.histogram Number.Order [1,2,1,5,4,2,4,4,3,5]" "[(1,2),(2,2),(3,1),(4,3),(5,2)]" + , t "Vector.ranked-histogram Number.Order [1,2,1,5,4,2,4,4,3,5]" + "[(4,3),(1,2),(2,2),(5,2),(3,1)]" , t "Vector.range 0 10" "[0,1,2,3,4,5,6,7,8,9]" , t "Vector.range 0 0" "[]" , t "Vector.fold-left (+) 0 (Vector.replicate 5 1)" "5" diff --git a/unison-src/base.u b/unison-src/base.u index dec07d88f..99130b713 100644 --- a/unison-src/base.u +++ b/unison-src/base.u @@ -77,9 +77,11 @@ Vector.fold-balanced plus zero vs = Vector.fold-balanced1 : ∀ a . (a -> a -> a) -> Vector a -> Optional a; Vector.fold-balanced1 f v = Vector.fold-balanced (Optional.lift-or f) None (Vector.map Some v); -Optional.lift-or : ∀ a . (a -> a -> a) -> Optional a -> Optional a -> Optional a; -Optional.lift-or f = a1 a2 -> - a1 |> Optional.fold a2 (a1 -> Optional.fold None (a2 -> Some (f a1 a2)) a2); +Vector.join : ∀ a . Vector (Vector a) -> Vector a; +Vector.join = Vector.bind identity; + +Vector.filter : ∀ a . (a -> Boolean) -> Vector a -> Vector a; +Vector.filter f = Vector.bind (a -> if f a then [a] else []); Vector.all? : ∀ a . (a -> Boolean) -> Vector a -> Boolean; Vector.all? f vs = Vector.fold-balanced and True (Vector.map f vs); @@ -106,6 +108,33 @@ Vector.dedup-adjacent eq v = [] (Vector.map Vector.pure v); +Vector.drop-right : ∀ a . Number -> Vector a -> Vector a; +Vector.drop-right n v = Vector.take (Vector.size v - n) v; + +Vector.take-right : ∀ a . Number -> Vector a -> Vector a; +Vector.take-right n v = Vector.drop (Vector.size v - n) v; + +Vector.histogram : ∀ a . Order a -> Vector a -> Vector (a, Number); +Vector.histogram o v = let + merge-bin b1 b2 = (1st b1, 2nd b1 + 2nd b2); + combine bin1 bin2 = + Optional.map2 (p1 p2 -> if Order.equal o (1st p1) (1st p2) + then [merge-bin p1 p2] + else [p1, p2]) + (Vector.last bin1) (Vector.1st bin2) + |> Optional.fold' (u -> Vector.concatenate bin1 bin2) + (p -> Vector.join [Vector.drop-right 1 bin1, p, Vector.drop 1 bin2]) + <| Unit; + Vector.fold-balanced combine [] (Vector.map (a -> Vector.pure (a, 1)) (Vector.sort o v));; +; + +Vector.ranked-histogram : ∀ a . Order a -> Vector a -> Vector (a, Number); +Vector.ranked-histogram o v = + Vector.histogram o v |> Vector.sort-by (Order.invert Number.Order) 2nd; + +Vector.sum : Vector Number -> Number; +Vector.sum = Vector.fold-left (+) 0; + Vector.dedup : ∀ a . Order a -> Vector a -> Vector a; Vector.dedup o v = Vector.dedup-adjacent (Order.equal o) (Vector.sort o v); @@ -219,6 +248,13 @@ Optional.map2 f a b = do Optional pure (f a b);; ; +Optional.lift-or : ∀ a . (a -> a -> a) -> Optional a -> Optional a -> Optional a; +Optional.lift-or f = a1 a2 -> + a1 |> Optional.fold a2 (a1 -> Optional.fold None (a2 -> Some (f a1 a2)) a2); + +Optional.fold' : ∀ a b . (Unit -> b) -> (a -> b) -> Optional a -> Unit -> b; +Optional.fold' thunk f = Optional.fold thunk (a u -> f a); + Either.map : ∀ a b c . (b -> c) -> Either a b -> Either a c; Either.map f = Either.fold Left (f `and-then` Right); diff --git a/unison-src/dindex.u b/unison-src/dindex.u index 02e3fb75e..ecd633633 100644 --- a/unison-src/dindex.u +++ b/unison-src/dindex.u @@ -2,7 +2,7 @@ -- to pick which nodes are responsible for which keys. See: -- https://en.wikipedia.org/wiki/Rendezvous_hashing -DIndex.Replication-Factor = 3; +DIndex.Replication-Factor = 1; DIndex.Timeout = Duration.seconds 10; DIndex.Max-Timeout = Duration.seconds 500; diff --git a/unison-src/searchengine.u b/unison-src/searchengine.u index f96be19ff..25a095f8d 100644 --- a/unison-src/searchengine.u +++ b/unison-src/searchengine.u @@ -1,11 +1,94 @@ --- going to need tuples at least --- allow declarations somehow --- going to need hashing +let + alias DIndex k v = Index Node (Index k v); + alias Set v = Index v Unit; + alias SearchIndex = DIndex Text (Set Text); + alias VisitSet = DIndex (Hash Text) Unit; -let rec - dindex : Vector Node - -> Remote (k -> Remote (Optional v), -- lookup - k -> v -> Remote Unit) -- insert - -in - _ + search : Number -> Vector Text -> SearchIndex + -> Remote (Vector Text); + search limit query ind = do Remote + url-sets := Remote.traverse (k -> DIndex.lookup k ind) query; + url-sets = Vector.map Index.traversal (Optional.somes url-sets); + zero = IndexedTraversal.empty; + merge = IndexedTraversal.intersect (Order.by-2nd Hash.Order); + urls = Optional.get-or IndexedTraversal.empty <| Vector.fold-balanced1 merge url-sets; + urls := IndexedTraversal.take-keys limit urls; + pure (Vector.map 1st urls);; + ; + + trim-to-host : Text -> Text; + trim-to-host url = Optional.get-or url <| do Optional + host := Uri.parse-authority url; + scheme := Uri.parse-scheme url; + pure (Text.concatenate scheme ("//" `Text.concatenate` host));; + ; + + -- | Convert url (possibly relative to parent) to an absolute url + resolve-url : Text -> Text -> Text; + resolve-url parent child = + if Text.take 1 child ==_Text "/" then + Text.concatenate (trim-to-host parent) child + else if (Text.take 5 child ==_Text "http:") `or` (Text.take 6 child ==_Text "https:") then + child + else parent `Text.concatenate` "/" `Text.concatenate` child + ; + + crawl : Number -> SearchIndex -> VisitSet -> Text -> Remote Unit; + crawl depth ind visited url = let rec + insert url keyword = do Remote + url-set := DIndex.lookup keyword ind; + Optional.fold + (do Remote + url-set := Index.empty; + DIndex.insert keyword url-set ind; + insert url keyword;;) + (Index.insert url Unit) + url-set;; + ; + go depth url = + if depth <=_Number 0 then Remote.pure Unit + else do Remote + page := Remote.map (Debug.log "indexing url" url) (Http.get-url url); + page = Either.fold (err -> Debug.log "error fetching" (url, err) "") identity page; + page-hash := hash! page; + h := DIndex.lookup page-hash visited; + Optional.fold + (do Remote + page-text = Html.plain-text page; + keywords = Text.words page-text + |> Vector.map Text.lowercase + |> Vector.ranked-histogram Text.Order; + summary = Vector.drop 5 keywords |> Vector.take 100; -- hacky filter + keywords = summary; + -- rankings = Debug.watch "rs" <| Vector.map 2nd keywords; + -- rankings0 = Debug.watch "kw" <| Vector.map 1st keywords; + keywords = Vector.map 1st keywords; + links = Html.get-links page; + links = Vector.map (Html.get-href `and-then` resolve-url url) links; + -- insert all keywords for the page into the map + Remote.traverse (insert url) keywords; + -- mark page as visited + Debug.log "finished indexing" url <| DIndex.insert page-hash Unit visited; + -- recurse + Remote.traverse (go (depth - 1)) links; + pure Unit;;) + (x -> Remote.pure (Debug.log "already visited" url Unit)) + h;; + ; + go depth url;; + ; + + do Remote + n := Remote.spawn; + Remote.transfer n; + ind := DIndex.empty; + visited := DIndex.empty; + ind-nodes := Remote.replicate 3 Remote.spawn; + visited-nodes := Remote.replicate 3 Remote.spawn; + Remote.traverse (n -> Remote.at' n (DIndex.join ind)) ind-nodes; + Remote.traverse (n -> Remote.at' n (DIndex.join visited)) visited-nodes; + Remote.fork <| crawl 2 ind visited "http://unisonweb.org"; + Remote.sleep (Duration.seconds 500); + results := search 10 ["design", "unison", "refactoring"] ind; + pure <| Debug.watch "results --- " results;; + ;;