Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Strings in the Anoma backend #2789

Merged
merged 1 commit into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Juvix/Compiler/Core/Transformation/Check/Anoma.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ checkAnoma md = do
checkMainExists md
checkNoAxioms md
mapAllNodesM checkNoIO md
mapAllNodesM (checkBuiltins' (builtinsString ++ builtinsCairo) [PrimString, PrimField]) md
mapAllNodesM (checkBuiltins' ([OpStrToInt, OpShow] ++ builtinsCairo) [PrimField]) md
48 changes: 38 additions & 10 deletions src/Juvix/Compiler/Nockma/Encoding/ByteString.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,43 @@ import Juvix.Compiler.Nockma.Language
import Juvix.Prelude.Base

atomToByteString :: (NockNatural a, Member (Error (ErrNockNatural a)) r) => Atom a -> Sem r ByteString
atomToByteString am = do
n <- nockNatural am
return (cloneToByteString . integerToVectorBits . toInteger $ n)
atomToByteString = fmap naturalToByteString . nockNatural

byteStringToAtom :: (NockNatural a, Member (Error (ErrNockNatural a)) r) => ByteString -> Sem r (Atom a)
byteStringToAtom bs = do
a <- fromNatural . fromInteger . vectorBitsToInteger . cloneFromByteString $ bs
return
Atom
{ _atomInfo = emptyAtomInfo,
_atom = a
}
byteStringToAtom = fmap mkEmptyAtom . fromNatural . byteStringToNatural

byteStringToNatural :: ByteString -> Natural
byteStringToNatural = bitsToNatural . cloneFromByteString

naturalToByteString :: Natural -> ByteString
naturalToByteString = cloneToByteString . naturalToBits

textToNatural :: Text -> Natural
textToNatural = byteStringToNatural . encodeUtf8

bitsToNatural :: Vector Bit -> Natural
bitsToNatural = fromInteger . vectorBitsToInteger

naturalToBits :: Natural -> Vector Bit
naturalToBits = integerToVectorBits . toInteger

atomToText :: (NockNatural a, Member (Error (ErrNockNatural a)) r) => Atom a -> Sem r Text
atomToText = fmap decodeUtf8Lenient . atomToByteString

-- | Construct an atom formed by concatenating the bits of two atoms, where each atom represents a sequence of bytes
atomConcatenateBytes :: forall a r. (NockNatural a, Member (Error (ErrNockNatural a)) r) => Atom a -> Atom a -> Sem r (Atom a)
atomConcatenateBytes l r = do
-- cloneToByteString ensures that the bytestring is zero-padded up to the byte boundary
lBs <- cloneToByteString <$> atomToBits l
rBs <- cloneToByteString <$> atomToBits r
byteStringToAtom (lBs <> rBs)
where
atomToBits :: Atom a -> Sem r (Vector Bit)
atomToBits = fmap naturalToBits . nockNatural

mkEmptyAtom :: a -> Atom a
mkEmptyAtom x =
Atom
{ _atomInfo = emptyAtomInfo,
_atom = x
}
6 changes: 6 additions & 0 deletions src/Juvix/Compiler/Nockma/Evaluator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,13 @@ evalProfile inistack initerm =
StdlibVerify -> case args' of
TCell (TermAtom signedMessage) (TermAtom pubKey) -> goVerify signedMessage pubKey
_ -> error "expected a term of the form [signedMessage (atom) public_key (atom)]"
StdlibCatBytes -> case args' of
TCell (TermAtom arg1) (TermAtom arg2) -> goCat arg1 arg2
_ -> error "expected a term with two atoms"
where
goCat :: Atom a -> Atom a -> Sem r (Term a)
goCat arg1 arg2 = TermAtom . setAtomHint AtomHintString <$> atomConcatenateBytes arg1 arg2

goVerifyDetached :: Atom a -> Atom a -> Atom a -> Sem r (Term a)
goVerifyDetached sigT messageT pubKeyT = do
sig <- Signature <$> atomToByteString sigT
Expand Down
4 changes: 4 additions & 0 deletions src/Juvix/Compiler/Nockma/Language.hs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ data AtomHint
| AtomHintNil
| AtomHintVoid
| AtomHintFunctionsPlaceholder
| AtomHintString
deriving stock (Show, Eq, Lift, Generic)

instance Hashable AtomHint
Expand Down Expand Up @@ -346,6 +347,9 @@ atomHintInfo h =
{ _atomInfoHint = Just h
}

setAtomHint :: AtomHint -> Atom a -> Atom a
setAtomHint h = set (atomInfo . atomInfoHint) (Just h)

class IsNock nock where
toNock :: nock -> Term Natural

Expand Down
7 changes: 6 additions & 1 deletion src/Juvix/Compiler/Nockma/Pretty/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Juvix.Compiler.Nockma.Pretty.Base
)
where

import Juvix.Compiler.Nockma.Encoding.ByteString (atomToText)
import Juvix.Compiler.Nockma.Language
import Juvix.Compiler.Nockma.Pretty.Options
import Juvix.Data.CodeAnn
Expand All @@ -23,7 +24,7 @@ class PrettyCode c where
runPrettyCode :: (PrettyCode c) => Options -> c -> Doc Ann
runPrettyCode opts = run . runReader opts . ppCode

instance (PrettyCode a, NockNatural a) => PrettyCode (Atom a) where
instance forall a. (PrettyCode a, NockNatural a) => PrettyCode (Atom a) where
ppCode atm = do
t <- runFail $ do
failWhenM (asks (^. optIgnoreTags))
Expand All @@ -43,6 +44,10 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (Atom a) where
AtomHintNil -> return (annotate (AnnKind KNameConstructor) Str.nil)
AtomHintVoid -> return (annotate (AnnKind KNameAxiom) Str.void)
AtomHintFunctionsPlaceholder -> return (annotate (AnnKind KNameAxiom) Str.functionsPlaceholder)
AtomHintString -> atomToText atm >>= ppCode

instance PrettyCode Text where
ppCode = return . dquotes . pretty

instance PrettyCode Interval where
ppCode = return . pretty
Expand Down
7 changes: 7 additions & 0 deletions src/Juvix/Compiler/Nockma/StdlibFunction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ stdlibPath = \case
StdlibVerifyDetached -> [nock| [9 22 0 1] |]
StdlibSign -> [nock| [9 10 0 1] |]
StdlibVerify -> [nock| [9 4 0 1] |]
-- Obtained from the urbit dojo using:
--
-- => anoma !=(~(cat block 3))
--
-- The `3` here is because we want to treat each atom as sequences of 2^3
-- bits, i.e bytes.
StdlibCatBytes -> [nock| [8 [9 10 0 7] 9 4 10 [6 7 [0 3] 1 3] 0 2] |]
2 changes: 2 additions & 0 deletions src/Juvix/Compiler/Nockma/StdlibFunction/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ instance Pretty StdlibFunction where
StdlibVerifyDetached -> "verify-detached"
StdlibSign -> "sign"
StdlibVerify -> "verify"
StdlibCatBytes -> "cat"

data StdlibFunction
= StdlibDec
Expand All @@ -35,6 +36,7 @@ data StdlibFunction
| StdlibVerifyDetached
| StdlibSign
| StdlibVerify
| StdlibCatBytes
deriving stock (Show, Lift, Eq, Bounded, Enum, Generic)

instance Hashable StdlibFunction
Expand Down
16 changes: 16 additions & 0 deletions src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Juvix.Compiler.Nockma.Translation.FromSource.Base where
import Data.HashMap.Internal.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NonEmpty
import Data.Text qualified as Text
import Juvix.Compiler.Nockma.Encoding.ByteString (textToNatural)
import Juvix.Compiler.Nockma.Language
import Juvix.Extra.Paths
import Juvix.Extra.Strings qualified as Str
Expand Down Expand Up @@ -63,6 +64,9 @@ lsbracket = void (lexeme "[")
rsbracket :: Parser ()
rsbracket = void (lexeme "]")

stringLiteral :: Parser Text
stringLiteral = lexeme (pack <$> (char '"' >> manyTill L.charLiteral (char '"')))

dottedNatural :: Parser Natural
dottedNatural = lexeme $ do
firstDigit <- digit
Expand Down Expand Up @@ -139,6 +143,17 @@ atomVoid = symbol Str.void $> nockVoid
atomFunctionsPlaceholder :: Parser (Atom Natural)
atomFunctionsPlaceholder = symbol Str.functionsPlaceholder $> nockNil

atomStringLiteral :: Parser (Atom Natural)
atomStringLiteral = do
WithLoc loc s <- withLoc stringLiteral
let info =
AtomInfo
{ _atomInfoTag = Nothing,
_atomInfoLoc = Irrelevant (Just loc),
_atomInfoHint = Just AtomHintString
}
return (Atom (textToNatural s) info)

patom :: Parser (Atom Natural)
patom = do
mtag <- optional pTag
Expand All @@ -149,6 +164,7 @@ patom = do
<|> atomNil
<|> atomVoid
<|> atomFunctionsPlaceholder
<|> try atomStringLiteral

iden :: Parser Text
iden = lexeme (takeWhile1P (Just "<iden>") (isAscii .&&. not . isWhiteSpace))
Expand Down
21 changes: 15 additions & 6 deletions src/Juvix/Compiler/Nockma/Translation/FromTree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module Juvix.Compiler.Nockma.Translation.FromTree
)
where

import Juvix.Compiler.Nockma.Encoding
import Juvix.Compiler.Nockma.Language.Path
import Juvix.Compiler.Nockma.Pretty
import Juvix.Compiler.Nockma.Stdlib
Expand Down Expand Up @@ -384,11 +385,19 @@ compile = \case
| i < 0 -> error "negative integer"
| otherwise -> nockIntegralLiteral i
Tree.ConstBool i -> nockBoolLiteral i
Tree.ConstString {} -> stringsErr
Tree.ConstString t -> OpQuote # goConstString t
Tree.ConstUnit -> OpQuote # constUnit
Tree.ConstVoid -> OpQuote # constVoid
Tree.ConstField {} -> fieldErr

goConstString :: Text -> Term Natural
goConstString t =
TermAtom
Atom
{ _atomInfo = atomHintInfo AtomHintString,
_atom = textToNatural t
}

goSave :: Tree.NodeSave -> Sem r (Term Natural)
goSave Tree.NodeSave {..} = do
arg <- compile _nodeSaveArg
Expand Down Expand Up @@ -440,8 +449,8 @@ compile = \case

goPrimUnop :: Tree.UnaryOp -> Term Natural -> Term Natural
goPrimUnop op arg = case op of
Tree.OpShow -> stringsErr
Tree.OpStrToInt -> stringsErr
Tree.OpShow -> stringsErr "show"
Tree.OpStrToInt -> stringsErr "strToInt"
Tree.OpArgsNum ->
let getF f = getClosureField f arg
in sub (getF ClosureTotalArgsNum) (getF ClosureArgsNum)
Expand Down Expand Up @@ -500,7 +509,7 @@ compile = \case
Tree.OpIntLt -> return (callStdlib StdlibLt args)
Tree.OpIntLe -> return (callStdlib StdlibLe args)
Tree.OpEq -> testEq _nodeBinopArg1 _nodeBinopArg2
Tree.OpStrConcat -> stringsErr
Tree.OpStrConcat -> return (callStdlib StdlibCatBytes args)
Tree.OpFieldAdd -> fieldErr
Tree.OpFieldSub -> fieldErr
Tree.OpFieldMul -> fieldErr
Expand Down Expand Up @@ -718,8 +727,8 @@ goConstructor mr t args = case t of
unsupported :: Text -> a
unsupported thing = error ("The Nockma backend does not support " <> thing)

stringsErr :: a
stringsErr = unsupported "strings"
stringsErr :: Text -> a
stringsErr t = unsupported ("strings: " <> t)

fieldErr :: a
fieldErr = unsupported "the field type"
Expand Down
9 changes: 8 additions & 1 deletion test/Anoma/Compilation/Positive.hs
Original file line number Diff line number Diff line change
Expand Up @@ -576,5 +576,12 @@ allTests =
$(mkRelFile "test078.juvix")
[OpQuote # toSignAndVerify]
$ checkOutput
[toSignAndVerify]
[toSignAndVerify],
let inputStr :: Term Natural = [nock| "Juvix!" |]
in mkAnomaCallTest
"Test079: Strings"
$(mkRelDir ".")
$(mkRelFile "test079.juvix")
[OpQuote # inputStr]
$ checkOutput [[nock| "Juvix! ✨ héllo world ✨" |]]
]
3 changes: 2 additions & 1 deletion test/Nockma/Eval/Positive.hs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ juvixCallingConventionTests =
res :: Term Natural = foldTerms (toNock <$> r)
lenR :: Term Natural = nockIntegralLiteral (length r)
tupR = OpQuote # foldTerms (toNock <$> r)
in compilerTest "appendToTuple (left empty, right-nonempty)" (appendToTuple (OpQuote # nockNilTagged "test-appendtotuple") (nockNatLiteral 0) tupR lenR) (eqNock res)
in compilerTest "appendToTuple (left empty, right-nonempty)" (appendToTuple (OpQuote # nockNilTagged "test-appendtotuple") (nockNatLiteral 0) tupR lenR) (eqNock res),
compilerTest "stdlib cat" (callStdlib StdlibCatBytes [nockNatLiteral 2, nockNatLiteral 1]) (eqNock [nock| 258 |])
]

unitTests :: [Test]
Expand Down
2 changes: 1 addition & 1 deletion tests/Anoma/Compilation/negative/String.juvix
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module String;

import Stdlib.Prelude open;

main : String := "boom";
main : String := intToString 1;
6 changes: 6 additions & 0 deletions tests/Anoma/Compilation/positive/test079.juvix
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module test079;

import Stdlib.Prelude open;

main (s : String) : String :=
s ++str " " ++str "✨ héllo" ++str " " ++str "world ✨";
Loading