⅄ trunk → 22-10-18-backticky-parser

This commit is contained in:
Mitchell Rosen 2024-02-01 13:54:11 -05:00
commit 96ca5c71ce
20 changed files with 823 additions and 47 deletions

View File

@ -1730,16 +1730,18 @@ prettyDoc2 ::
prettyDoc2 ac tm = do prettyDoc2 ac tm = do
ppe <- getPPE ppe <- getPPE
let brace p = let brace p =
fmt S.DocDelimiter "{{" if PP.isMultiLine p
<> PP.softbreak then fmt S.DocDelimiter "{{" <> PP.newline <> p <> PP.newline <> fmt S.DocDelimiter "}}"
<> p else fmt S.DocDelimiter "{{" <> PP.softbreak <> p <> PP.softbreak <> fmt S.DocDelimiter "}}"
<> PP.softbreak
<> fmt S.DocDelimiter "}}"
bail tm = brace <$> pretty0 ac tm bail tm = brace <$> pretty0 ac tm
contains :: Char -> Pretty SyntaxText -> Bool
contains c p =
PP.toPlainUnbroken (PP.syntaxToColor p)
& elem c
-- Finds the longest run of a character and return one bigger than that -- Finds the longest run of a character and return one bigger than that
longestRun c s = longestRun c s =
case filter (\s -> take 2 s == [c, c]) $ case filter (\s -> take 2 s == [c, c]) $
group (PP.toPlainUnbroken $ PP.syntaxToColor s) of List.group (PP.toPlainUnbroken $ PP.syntaxToColor s) of
[] -> 2 [] -> 2
x -> 1 + maximum (map length x) x -> 1 + maximum (map length x)
oneMore c inner = replicate (longestRun c inner) c oneMore c inner = replicate (longestRun c inner) c
@ -1773,7 +1775,12 @@ prettyDoc2 ac tm = do
pure $ PP.text t pure $ PP.text t
(toDocCode ppe -> Just d) -> do (toDocCode ppe -> Just d) -> do
inner <- rec d inner <- rec d
let quotes = PP.string $ oneMore '\'' inner let quotes =
-- Prefer ` if there aren't any in the inner text,
-- otherwise use one more than the longest run of ' in the inner text
if contains '`' inner
then PP.string $ oneMore '\'' inner
else PP.string "`"
pure $ PP.group $ quotes <> inner <> quotes pure $ PP.group $ quotes <> inner <> quotes
(toDocJoin ppe -> Just ds) -> foldMapM rec ds (toDocJoin ppe -> Just ds) -> foldMapM rec ds
(toDocItalic ppe -> Just d) -> do (toDocItalic ppe -> Just d) -> do

View File

@ -66,6 +66,7 @@ import Unison.Codebase.Editor.HandleInput.AuthLogin (authLogin)
import Unison.Codebase.Editor.HandleInput.Branch (handleBranch) import Unison.Codebase.Editor.HandleInput.Branch (handleBranch)
import Unison.Codebase.Editor.HandleInput.BranchRename (handleBranchRename) import Unison.Codebase.Editor.HandleInput.BranchRename (handleBranchRename)
import Unison.Codebase.Editor.HandleInput.Branches (handleBranches) import Unison.Codebase.Editor.HandleInput.Branches (handleBranches)
import Unison.Codebase.Editor.HandleInput.DebugDefinition qualified as DebugDefinition
import Unison.Codebase.Editor.HandleInput.DeleteBranch (handleDeleteBranch) import Unison.Codebase.Editor.HandleInput.DeleteBranch (handleDeleteBranch)
import Unison.Codebase.Editor.HandleInput.DeleteProject (handleDeleteProject) import Unison.Codebase.Editor.HandleInput.DeleteProject (handleDeleteProject)
import Unison.Codebase.Editor.HandleInput.EditNamespace (handleEditNamespace) import Unison.Codebase.Editor.HandleInput.EditNamespace (handleEditNamespace)
@ -1142,6 +1143,8 @@ loop e = do
traceM $ show name ++ ",Type," ++ Text.unpack (Reference.toText r) traceM $ show name ++ ",Type," ++ Text.unpack (Reference.toText r)
for_ (Relation.toList . Branch.deepTerms $ rootBranch0) \(r, name) -> for_ (Relation.toList . Branch.deepTerms $ rootBranch0) \(r, name) ->
traceM $ show name ++ ",Term," ++ Text.unpack (Referent.toText r) traceM $ show name ++ ",Term," ++ Text.unpack (Referent.toText r)
DebugTermI isVerbose hqName -> DebugDefinition.debugTerm isVerbose hqName
DebugTypeI hqName -> DebugDefinition.debugDecl hqName
DebugClearWatchI {} -> DebugClearWatchI {} ->
Cli.runTransaction Codebase.clearWatches Cli.runTransaction Codebase.clearWatches
DebugDoctorI {} -> do DebugDoctorI {} -> do
@ -1355,6 +1358,11 @@ inputDescription input =
DebugDoctorI {} -> wat DebugDoctorI {} -> wat
DebugDumpNamespaceSimpleI {} -> wat DebugDumpNamespaceSimpleI {} -> wat
DebugDumpNamespacesI {} -> wat DebugDumpNamespacesI {} -> wat
DebugTermI verbose hqName ->
if verbose
then pure ("debug.term.verbose " <> HQ.toText hqName)
else pure ("debug.term " <> HQ.toText hqName)
DebugTypeI hqName -> pure ("debug.type " <> HQ.toText hqName)
DebugNameDiffI {} -> wat DebugNameDiffI {} -> wat
DebugNumberedArgsI {} -> wat DebugNumberedArgsI {} -> wat
DebugTabCompletionI _input -> wat DebugTabCompletionI _input -> wat

View File

@ -0,0 +1,59 @@
module Unison.Codebase.Editor.HandleInput.DebugDefinition
( debugTerm,
debugDecl,
)
where
import Control.Monad.Reader
import Unison.Cli.Monad (Cli)
import Unison.Cli.Monad qualified as Cli
import Unison.Cli.NamesUtils qualified as Cli
import Unison.Codebase qualified as Codebase
import Unison.Codebase.Editor.Output (Output (..))
import Unison.ConstructorReference (GConstructorReference (ConstructorReference))
import Unison.DataDeclaration.ConstructorId (ConstructorId)
import Unison.HashQualified qualified as HQ
import Unison.Name (Name)
import Unison.NamesWithHistory qualified as Names
import Unison.Prelude
import Unison.Reference (TermReference, TypeReference)
import Unison.Reference qualified as Reference
import Unison.Referent qualified as Referent
debugTermReference :: Bool -> TermReference -> Cli ()
debugTermReference verbose ref = do
Cli.Env {codebase} <- ask
case ref of
Reference.DerivedId refId -> do
Cli.runTransaction (Codebase.getTerm codebase refId) >>= \case
Nothing -> Cli.respond $ TermNotFound' (Reference.toShortHash ref)
Just term -> do
Cli.respond $ DebugTerm verbose (Right term)
Reference.Builtin builtinTxt -> do
Cli.respond $ DebugTerm verbose (Left builtinTxt)
debugTypeReference :: TypeReference -> Maybe ConstructorId -> Cli ()
debugTypeReference ref mayConId = do
Cli.Env {codebase} <- ask
case ref of
Reference.DerivedId refId -> do
Cli.runTransaction (Codebase.getTypeDeclaration codebase refId) >>= \case
Nothing -> Cli.respond $ TypeNotFound' (Reference.toShortHash ref)
Just decl -> do
Cli.respond $ DebugDecl (Right decl) mayConId
Reference.Builtin builtinTxt -> do
Cli.respond $ DebugDecl (Left builtinTxt) mayConId
debugTerm :: Bool -> HQ.HashQualified Name -> Cli ()
debugTerm verbose hqName = do
names <- Cli.currentNames
let matches = Names.lookupHQTerm Names.IncludeSuffixes hqName names
for_ matches \case
Referent.Ref termReference -> debugTermReference verbose termReference
Referent.Con (ConstructorReference typeRef conId) _conTyp -> debugTypeReference typeRef (Just conId)
debugDecl :: HQ.HashQualified Name -> Cli ()
debugDecl hqName = do
names <- Cli.currentNames
let matches = Names.lookupHQType Names.IncludeSuffixes hqName names
for_ matches \typeRef -> debugTypeReference typeRef Nothing

View File

@ -220,6 +220,8 @@ data Input
| DebugTypecheckedUnisonFileI | DebugTypecheckedUnisonFileI
| DebugDumpNamespacesI | DebugDumpNamespacesI
| DebugDumpNamespaceSimpleI | DebugDumpNamespaceSimpleI
| DebugTermI (Bool {- Verbose mode -}) (HQ.HashQualified Name)
| DebugTypeI (HQ.HashQualified Name)
| DebugClearWatchI | DebugClearWatchI
| DebugDoctorI | DebugDoctorI
| DebugNameDiffI ShortCausalHash ShortCausalHash | DebugNameDiffI ShortCausalHash ShortCausalHash

View File

@ -47,6 +47,7 @@ import Unison.Codebase.ShortCausalHash (ShortCausalHash)
import Unison.Codebase.ShortCausalHash qualified as SCH import Unison.Codebase.ShortCausalHash qualified as SCH
import Unison.Codebase.Type (GitError) import Unison.Codebase.Type (GitError)
import Unison.CommandLine.InputPattern qualified as Input import Unison.CommandLine.InputPattern qualified as Input
import Unison.DataDeclaration qualified as DD
import Unison.DataDeclaration.ConstructorId (ConstructorId) import Unison.DataDeclaration.ConstructorId (ConstructorId)
import Unison.HashQualified qualified as HQ import Unison.HashQualified qualified as HQ
import Unison.HashQualified' qualified as HQ' import Unison.HashQualified' qualified as HQ'
@ -324,6 +325,8 @@ data Output
| DisplayDebugCompletions [Completion.Completion] | DisplayDebugCompletions [Completion.Completion]
| DebugDisplayFuzzyOptions Text [String {- arg description, options -}] | DebugDisplayFuzzyOptions Text [String {- arg description, options -}]
| DebugFuzzyOptionsNoResolver | DebugFuzzyOptionsNoResolver
| DebugTerm (Bool {- verbose mode -}) (Either (Text {- A builtin hash -}) (Term Symbol Ann))
| DebugDecl (Either (Text {- A builtin hash -}) (DD.Decl Symbol Ann)) (Maybe ConstructorId {- If 'Just' we're debugging a constructor of the given decl -})
| ClearScreen | ClearScreen
| PulledEmptyBranch (ReadRemoteNamespace Share.RemoteProjectBranch) | PulledEmptyBranch (ReadRemoteNamespace Share.RemoteProjectBranch)
| CreatedProject Bool {- randomly-generated name? -} ProjectName | CreatedProject Bool {- randomly-generated name? -} ProjectName
@ -569,6 +572,8 @@ isFailure o = case o of
DisplayDebugCompletions {} -> False DisplayDebugCompletions {} -> False
DebugDisplayFuzzyOptions {} -> False DebugDisplayFuzzyOptions {} -> False
DebugFuzzyOptionsNoResolver {} -> True DebugFuzzyOptionsNoResolver {} -> True
DebugTerm {} -> False
DebugDecl {} -> False
DisplayDebugNameDiff {} -> False DisplayDebugNameDiff {} -> False
ClearScreen -> False ClearScreen -> False
PulledEmptyBranch {} -> False PulledEmptyBranch {} -> False

View File

@ -2245,6 +2245,45 @@ debugDumpNamespaceSimple =
"Dump the namespace to a text file" "Dump the namespace to a text file"
(const $ Right Input.DebugDumpNamespaceSimpleI) (const $ Right Input.DebugDumpNamespaceSimpleI)
debugTerm :: InputPattern
debugTerm =
InputPattern
"debug.term.abt"
[]
I.Hidden
[("term", Required, exactDefinitionTermQueryArg)]
"View debugging information for a given term."
( \case
[thing] -> fmap (Input.DebugTermI False) $ parseHashQualifiedName thing
_ -> Left (I.help debugTerm)
)
debugTermVerbose :: InputPattern
debugTermVerbose =
InputPattern
"debug.term.abt.verbose"
[]
I.Hidden
[("term", Required, exactDefinitionTermQueryArg)]
"View verbose debugging information for a given term."
( \case
[thing] -> fmap (Input.DebugTermI True) $ parseHashQualifiedName thing
_ -> Left (I.help debugTermVerbose)
)
debugType :: InputPattern
debugType =
InputPattern
"debug.type.abt"
[]
I.Hidden
[("type", Required, exactDefinitionTypeQueryArg)]
"View debugging information for a given type."
( \case
[thing] -> fmap (Input.DebugTypeI) $ parseHashQualifiedName thing
_ -> Left (I.help debugType)
)
debugClearWatchCache :: InputPattern debugClearWatchCache :: InputPattern
debugClearWatchCache = debugClearWatchCache =
InputPattern InputPattern
@ -2964,6 +3003,9 @@ validInputs =
debugDoctor, debugDoctor,
debugDumpNamespace, debugDumpNamespace,
debugDumpNamespaceSimple, debugDumpNamespaceSimple,
debugTerm,
debugTermVerbose,
debugType,
debugFileHashes, debugFileHashes,
debugNameDiff, debugNameDiff,
debugNumberedArgs, debugNumberedArgs,

View File

@ -21,15 +21,18 @@ import Data.Set qualified as Set
import Data.Set.NonEmpty (NESet) import Data.Set.NonEmpty (NESet)
import Data.Text qualified as Text import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text import Data.Text.Encoding qualified as Text
import Data.Text.Lazy qualified as TL
import Data.Time (UTCTime, getCurrentTime) import Data.Time (UTCTime, getCurrentTime)
import Data.Tuple (swap) import Data.Tuple (swap)
import Data.Tuple.Extra (dupe) import Data.Tuple.Extra (dupe)
import Data.Void (absurd) import Data.Void (absurd)
import Debug.RecoverRTTI qualified as RTTI
import Network.HTTP.Types qualified as Http import Network.HTTP.Types qualified as Http
import Servant.Client qualified as Servant import Servant.Client qualified as Servant
import System.Console.ANSI qualified as ANSI import System.Console.ANSI qualified as ANSI
import System.Console.Haskeline.Completion qualified as Completion import System.Console.Haskeline.Completion qualified as Completion
import System.Directory (canonicalizePath, getHomeDirectory) import System.Directory (canonicalizePath, getHomeDirectory)
import Text.Pretty.Simple (pShowNoColor, pStringNoColor)
import U.Codebase.Branch (NamespaceStats (..)) import U.Codebase.Branch (NamespaceStats (..))
import U.Codebase.Branch.Diff (NameChanges (..)) import U.Codebase.Branch.Diff (NameChanges (..))
import U.Codebase.HashTags (CausalHash (..)) import U.Codebase.HashTags (CausalHash (..))
@ -1773,6 +1776,21 @@ notifyUser dir = \case
IntegrityCheck result -> pure $ case result of IntegrityCheck result -> pure $ case result of
NoIntegrityErrors -> "🎉 No issues detected 🎉" NoIntegrityErrors -> "🎉 No issues detected 🎉"
IntegrityErrorDetected ns -> prettyPrintIntegrityErrors ns IntegrityErrorDetected ns -> prettyPrintIntegrityErrors ns
DebugTerm verbose builtinOrTerm -> pure $ case builtinOrTerm of
Left builtin -> "Builtin term: ##" <> P.text builtin
Right trm ->
if verbose
then P.text . TL.toStrict . pStringNoColor $ RTTI.anythingToString trm
else P.shown trm
DebugDecl typ mayConId -> do
let constructorMsg = case mayConId of
Nothing -> ""
Just conId -> "Constructor #" <> P.shown conId <> " of the following type:\n"
pure $
constructorMsg
<> case typ of
Left builtinTxt -> "Builtin type: ##" <> P.text builtinTxt
Right decl -> either (P.text . TL.toStrict . pShowNoColor) (P.text . TL.toStrict . pShowNoColor) decl
DisplayDebugNameDiff NameChanges {termNameAdds, termNameRemovals, typeNameAdds, typeNameRemovals} -> do DisplayDebugNameDiff NameChanges {termNameAdds, termNameRemovals, typeNameAdds, typeNameRemovals} -> do
let referentText = let referentText =
-- We don't use the constructor type in the actual output here, so there's no -- We don't use the constructor type in the actual output here, so there's no
@ -2747,7 +2765,7 @@ renderEditConflicts ppe Patch {..} = do
then "deprecated and also replaced with" then "deprecated and also replaced with"
else "replaced with" else "replaced with"
) )
`P.hang` P.lines replacements `P.hang` P.lines replacements
formatTermEdits :: formatTermEdits ::
(Reference.TermReference, Set TermEdit.TermEdit) -> (Reference.TermReference, Set TermEdit.TermEdit) ->
Numbered Pretty Numbered Pretty
@ -2762,7 +2780,7 @@ renderEditConflicts ppe Patch {..} = do
then "deprecated and also replaced with" then "deprecated and also replaced with"
else "replaced with" else "replaced with"
) )
`P.hang` P.lines replacements `P.hang` P.lines replacements
formatConflict :: formatConflict ::
Either Either
(Reference, Set TypeEdit.TypeEdit) (Reference, Set TypeEdit.TypeEdit)

View File

@ -50,6 +50,7 @@ library
Unison.Codebase.Editor.HandleInput.Branch Unison.Codebase.Editor.HandleInput.Branch
Unison.Codebase.Editor.HandleInput.Branches Unison.Codebase.Editor.HandleInput.Branches
Unison.Codebase.Editor.HandleInput.BranchRename Unison.Codebase.Editor.HandleInput.BranchRename
Unison.Codebase.Editor.HandleInput.DebugDefinition
Unison.Codebase.Editor.HandleInput.DeleteBranch Unison.Codebase.Editor.HandleInput.DeleteBranch
Unison.Codebase.Editor.HandleInput.DeleteProject Unison.Codebase.Editor.HandleInput.DeleteProject
Unison.Codebase.Editor.HandleInput.EditNamespace Unison.Codebase.Editor.HandleInput.EditNamespace

View File

@ -166,7 +166,8 @@ fix_2224b = cases
fix_2271 : Doc2 fix_2271 : Doc2
fix_2271 = fix_2271 =
{{ # Full doc body indented {{
# Full doc body indented
``` raw ``` raw
myVal1 = 42 myVal1 = 42
@ -179,7 +180,8 @@ fix_2271 =
indented2="this is two indents" indented2="this is two indents"
``` ```
I am two spaces over }} I am two spaces over
}}
Fix_2337.f : Fix_2337 -> Boolean Fix_2337.f : Fix_2337 -> Boolean
Fix_2337.f = cases Fix_2337 a b -> a Fix_2337.f = cases Fix_2337 a b -> a
@ -304,10 +306,12 @@ fix_4384b = {{ {{ docExampleBlock 0 '99 }} }}
fix_4384c : Doc2 fix_4384c : Doc2
fix_4384c = fix_4384c =
use Nat + use Nat +
{{ {{ docExampleBlock 0 do {{
{{ docExampleBlock 0 do
x = 1 x = 1
y = 2 y = 2
x + y }} }} x + y }}
}}
fix_4384d : Doc2 fix_4384d : Doc2
fix_4384d = fix_4384d =
@ -432,11 +436,13 @@ multiline_list =
nested_fences : Doc2 nested_fences : Doc2
nested_fences = nested_fences =
{{ ```` raw {{
```` raw
```unison ```unison
r = "boopydoo" r = "boopydoo"
``` ```
```` }} ````
}}
raw_a : Text raw_a : Text
raw_a = raw_a =

View File

@ -112,7 +112,7 @@ and the rendered output using `display`:
section elements. section elements.
Text can be **bold**, __italicized__, ~~strikethrough~~, Text can be **bold**, __italicized__, ~~strikethrough~~,
or ''monospaced'' (or ''monospaced''). or `monospaced` (or `monospaced`).
You can link to Unison terms, types, and external URLs: You can link to Unison terms, types, and external URLs:
@ -122,7 +122,7 @@ and the rendered output using `display`:
[a named term link]({Some}). Term links are handy for [a named term link]({Some}). Term links are handy for
linking to other documents! linking to other documents!
You can use ''{{ .. }}'' to escape out to regular Unison You can use `{{ .. }}` to escape out to regular Unison
syntax, for instance {{ docWord "__not bold__" }}. This is syntax, for instance {{ docWord "__not bold__" }}. This is
useful for creating documents programmatically or just useful for creating documents programmatically or just
including other documents. including other documents.
@ -164,9 +164,9 @@ and the rendered output using `display`:
## Bulleted lists ## Bulleted lists
Bulleted lists can use ''+'', ''-'', or ''*'' for the Bulleted lists can use `+`, `-`, or `*` for the bullets
bullets (though the choice will be normalized away by (though the choice will be normalized away by the
the pretty-printer). They can be nested, to any depth: pretty-printer). They can be nested, to any depth:
* A * A
* B * B
@ -331,9 +331,9 @@ and the rendered output using `display`:
You can include typechecked code snippets inline, for You can include typechecked code snippets inline, for
instance: instance:
* {{ docExample 2 '(f x -> f x + sqr 1) }} - the ''2'' * {{ docExample 2 '(f x -> f x + sqr 1) }} - the `2`
says to ignore the first two arguments when says to ignore the first two arguments when
rendering. In richer renderers, the ''sqr'' link will rendering. In richer renderers, the `sqr` link will
be clickable. be clickable.
* If your snippet expression is just a single function * If your snippet expression is just a single function
application, you can put it in double backticks, like application, you can put it in double backticks, like
@ -456,7 +456,7 @@ and the rendered output using `display`:
{{ {{
There are also asides, callouts, tables, tooltips, and more. There are also asides, callouts, tables, tooltips, and more.
These don't currently have special syntax; just use the These don't currently have special syntax; just use the
''{{ }}'' syntax to call these functions directly. `{{ }}` syntax to call these functions directly.
@signatures{docAside, docCallout, docBlockquote, docTooltip, docTable} @signatures{docAside, docCallout, docBlockquote, docTooltip, docTable}
@ -553,7 +553,8 @@ Lastly, it's common to build longer documents including subdocuments via `{{ sub
doc.guide : Doc2 doc.guide : Doc2
doc.guide = doc.guide =
{{ # Unison computable documentation {{
# Unison computable documentation
{{ basicFormatting }} {{ basicFormatting }}
@ -565,7 +566,8 @@ Lastly, it's common to build longer documents including subdocuments via `{{ sub
{{ nonUnisonCodeBlocks }} {{ nonUnisonCodeBlocks }}
{{ otherElements }} }} {{ otherElements }}
}}
.> display doc.guide .> display doc.guide

View File

@ -0,0 +1,28 @@
```ucm:hide
.> builtins.merge
```
```unison:hide
x = 30
y : Nat
y =
z = x + 2
z + 10
structural type Optional a = Some a | None
ability Ask a where
ask : a
```
```ucm
.> add
.> debug.term.abt Nat.+
.> debug.term.abt y
.> debug.term.abt Some
.> debug.term.abt ask
.> debug.type.abt Nat
.> debug.type.abt Optional
.> debug.type.abt Ask
```

View File

@ -0,0 +1,154 @@
```unison
x = 30
y : Nat
y =
z = x + 2
z + 10
structural type Optional a = Some a | None
ability Ask a where
ask : a
```
```ucm
.> add
⍟ I've added these definitions:
ability Ask a
structural type Optional a
(also named builtin.Optional)
x : Nat
y : Nat
.> debug.term.abt Nat.+
Builtin term: ##Nat.+
.> debug.term.abt y
(let Ref(ReferenceBuiltin "Nat.+") Ref(ReferenceDerived (Id "qpo3o788girkkbb43uf6ggqberfduhtnqbt7096eojlrp27jieco09mdasb7b0b06ej9hj60a00nnbbdo8he0b4e0m7vtopifiuhdig" 0)) 2 in (User "z". Ref(ReferenceBuiltin "Nat.+") (Var User "z") 10)):ReferenceBuiltin "Nat"
.> debug.term.abt Some
Constructor #0 of the following type:
DataDeclaration
{ modifier = Structural
, annotation = External
, bound =
[ User "a" ]
, constructors' =
[
( External
, User "Constructor0"
,
( User "a". Var User "a" -> ReferenceDerived
( Id "nirp5os0q69o4e1u9p3t6mmq6l6otluefi3ksm7dhm0diidjvkkgl8o9bvnflbj0sanuvdusf34f1qrins3ktcaglpcqv9oums2slsg" 0 )
( Var User "a" )
)
)
,
( External
, User "Constructor1"
,
( User "a". ReferenceDerived
( Id "nirp5os0q69o4e1u9p3t6mmq6l6otluefi3ksm7dhm0diidjvkkgl8o9bvnflbj0sanuvdusf34f1qrins3ktcaglpcqv9oums2slsg" 0 )
( Var User "a" )
)
)
]
}
.> debug.term.abt ask
Constructor #0 of the following type:
EffectDeclaration
{ toDataDecl = DataDeclaration
{ modifier = Unique "a1ns7cunv2dvjmum0q8jbc54g6811cbh"
, annotation = External
, bound =
[ User "a" ]
, constructors' =
[
( External
, User "Constructor0"
,
( User "a".
(
{
[ ReferenceDerived
( Id "d8m1kmiscgfrl5n9ruvq1432lntfntl7nnao45qlk2uqhparm0uq2im0kbspu6u6kv65hd0i5oljq9m4b78peh5ekpma7gkihtsmfh0" 0 )
( Var User "a" )
]
} Var User "a"
)
)
)
]
}
}
.> debug.type.abt Nat
Builtin type: ##Nat
.> debug.type.abt Optional
DataDeclaration
{ modifier = Structural
, annotation = External
, bound =
[ User "a" ]
, constructors' =
[
( External
, User "Constructor0"
,
( User "a". Var User "a" -> ReferenceDerived
( Id "nirp5os0q69o4e1u9p3t6mmq6l6otluefi3ksm7dhm0diidjvkkgl8o9bvnflbj0sanuvdusf34f1qrins3ktcaglpcqv9oums2slsg" 0 )
( Var User "a" )
)
)
,
( External
, User "Constructor1"
,
( User "a". ReferenceDerived
( Id "nirp5os0q69o4e1u9p3t6mmq6l6otluefi3ksm7dhm0diidjvkkgl8o9bvnflbj0sanuvdusf34f1qrins3ktcaglpcqv9oums2slsg" 0 )
( Var User "a" )
)
)
]
}
.> debug.type.abt Ask
EffectDeclaration
{ toDataDecl = DataDeclaration
{ modifier = Unique "a1ns7cunv2dvjmum0q8jbc54g6811cbh"
, annotation = External
, bound =
[ User "a" ]
, constructors' =
[
( External
, User "Constructor0"
,
( User "a".
(
{
[ ReferenceDerived
( Id "d8m1kmiscgfrl5n9ruvq1432lntfntl7nnao45qlk2uqhparm0uq2im0kbspu6u6kv65hd0i5oljq9m4b78peh5ekpma7gkihtsmfh0" 0 )
( Var User "a" )
]
} Var User "a"
)
)
)
]
}
}
```

View File

@ -0,0 +1,118 @@
# Test parsing and round-trip of doc2 syntax elements
```ucm:hide
.> builtins.mergeio
```
```unison:hide
otherDoc : a -> Doc2
otherDoc _ = {{ yo }}
otherTerm : Nat
otherTerm = 99
fulldoc : Doc2
fulldoc =
use Nat +
{{
Heres some text with a
soft line break
hard line break
Here's a cool **BOLD** __italic__ ~~strikethrough~~ thing with an inline code block `1 + 2`
Should print with appropriate fences for the contents:
`No fancy quotes`
'' There are `backticks` in here ''
''' There are `backticks` and ''quotes'' in here '''
# Heading
## Heading 2
Term Link: {otherTerm}
Type Link: {type Optional}
Term source:
@source{term}
Term signature:
@signature{term}
* List item
Inline code:
`` 1 + 2 ``
` "doesn't typecheck" + 1 `
[Link](https://unison-lang.org)
![Image](https://share-next.unison-lang.org/static/unison-logo-circle.png)
Horizontal rule
---
Video
{{
Special
(Embed
(Any (Video [MediaSource "test.mp4" None] [("poster", "test.png")])))
}}
Transclusion/evaluation:
{{ otherDoc (a -> Word a) }}
---
The following markdown features aren't supported by the Doc format yet, but maybe will someday
> Block quote
Table
| Header 1 | Header 2 |
| -------- | -------- |
| Cell 1 | Cell 2 |
Indented Code block
'''
Exact whitespace should be preserved across multiple updates. Don't mess with the logo!
_____ _
| | |___|_|___ ___ ___
| | | | |_ -| . | |
|_____|_|_|_|___|___|_|_|
Line with no whitespace:
Should have one full trailing newline below here:
'''
Inline '' text literal with 1 space of padding '' in the middle of a sentence.
}}
```
Format it to check that everything pretty-prints in a valid way.
```ucm
.> debug.format
```

View File

@ -0,0 +1,217 @@
# Test parsing and round-trip of doc2 syntax elements
```unison
otherDoc : a -> Doc2
otherDoc _ = {{ yo }}
otherTerm : Nat
otherTerm = 99
fulldoc : Doc2
fulldoc =
use Nat +
{{
Heres some text with a
soft line break
hard line break
Here's a cool **BOLD** __italic__ ~~strikethrough~~ thing with an inline code block `1 + 2`
Should print with appropriate fences for the contents:
`No fancy quotes`
'' There are `backticks` in here ''
''' There are `backticks` and ''quotes'' in here '''
# Heading
## Heading 2
Term Link: {otherTerm}
Type Link: {type Optional}
Term source:
@source{term}
Term signature:
@signature{term}
* List item
Inline code:
`` 1 + 2 ``
` "doesn't typecheck" + 1 `
[Link](https://unison-lang.org)
![Image](https://share-next.unison-lang.org/static/unison-logo-circle.png)
Horizontal rule
---
Video
{{
Special
(Embed
(Any (Video [MediaSource "test.mp4" None] [("poster", "test.png")])))
}}
Transclusion/evaluation:
{{ otherDoc (a -> Word a) }}
---
The following markdown features aren't supported by the Doc format yet, but maybe will someday
> Block quote
Table
| Header 1 | Header 2 |
| -------- | -------- |
| Cell 1 | Cell 2 |
Indented Code block
'''
Exact whitespace should be preserved across multiple updates. Don't mess with the logo!
_____ _
| | |___|_|___ ___ ___
| | | | |_ -| . | |
|_____|_|_|_|___|___|_|_|
Line with no whitespace:
Should have one full trailing newline below here:
'''
Inline '' text literal with 1 space of padding '' in the middle of a sentence.
}}
```
Format it to check that everything pretty-prints in a valid way.
```ucm
.> debug.format
```
```unison:added-by-ucm scratch.u
otherDoc : a -> Doc2
otherDoc _ = {{ yo }}
otherTerm : Nat
otherTerm = 99
fulldoc : Doc2
fulldoc =
use Nat +
{{
Heres some text with a soft line break
hard line break
Here's a cool **BOLD** __italic__ ~~strikethrough~~ thing with an inline code
block `1 + 2`
Should print with appropriate fences for the contents:
`No fancy quotes`
'' There are `backticks` in here ''
''' There are `backticks` and ''quotes'' in here '''
# Heading
## Heading 2
Term Link: {otherTerm}
Type Link: {type Optional}
Term source:
@source{term}
Term signature:
@signature{term}
* List item
Inline code:
`` 1 + 2 ``
` "doesn't typecheck" + 1 `
[Link](https://unison-lang.org)
![Image](https://share-next.unison-lang.org/static/unison-logo-circle.png)
Horizontal rule
---
Video
{{
Special
(Embed
(Any (Video [MediaSource "test.mp4" None] [("poster", "test.png")])))
}}
Transclusion/evaluation:
{{ otherDoc (a -> Word a) }}
---
The following markdown features aren't supported by the Doc format yet,
but maybe will someday
> Block quote
Table
| Header 1 | Header 2 | | -------- | -------- | | Cell 1 | Cell 2 |
Indented Code block
'''
Exact whitespace should be preserved across multiple updates. Don't mess with the logo!
_____ _
| | |___|_|___ ___ ___
| | | | |_ -| . | |
|_____|_|_|_|___|___|_|_|
Line with no whitespace:
Should have one full trailing newline below here:
'''
Inline ` text literal with 1 space of padding ` in the middle of a
sentence.
}}
```

View File

@ -52,11 +52,13 @@ type Two = One Nat | Two Text
``` ```
```unison:added-by-ucm scratch.u ```unison:added-by-ucm scratch.u
x.doc = x.doc =
{{ # Doc This is a **doc**! {{
# Doc This is a **doc**!
term link {x} term link {x}
type link {type Optional} }} type link {type Optional}
}}
x : Nat -> Nat x : Nat -> Nat
x y = x y =
use Nat + use Nat +
@ -70,11 +72,13 @@ oneLiner = {{ one liner }}
-- Before -- Before
explicit.doc = explicit.doc =
{{ # Here's a top-level doc {{
# Here's a top-level doc
With a paragraph With a paragraph
Or two }} Or two
}}
-- After -- After
Thing.doc = {{ A doc before an ability }} Thing.doc = {{ A doc before an ability }}

View File

@ -9,6 +9,7 @@ dependencies:
- bytes - bytes
- containers - containers
- cryptonite - cryptonite
- extra
- lens - lens
- megaparsec - megaparsec
- mtl - mtl

View File

@ -27,8 +27,10 @@ where
import Control.Monad.State qualified as S import Control.Monad.State qualified as S
import Data.Char (isAlphaNum, isControl, isDigit, isSpace, ord, toLower) import Data.Char (isAlphaNum, isControl, isDigit, isSpace, ord, toLower)
import Data.List (intercalate, isPrefixOf) import Data.List qualified as List
import Data.List.Extra qualified as List
import Data.List.NonEmpty qualified as Nel import Data.List.NonEmpty qualified as Nel
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map import Data.Map.Strict qualified as Map
import Data.Set qualified as Set import Data.Set qualified as Set
import Data.Text qualified as Text import Data.Text qualified as Text
@ -404,7 +406,7 @@ lexemes' eof =
body = join <$> P.many (sectionElem <* CP.space) body = join <$> P.many (sectionElem <* CP.space)
sectionElem = section <|> fencedBlock <|> list <|> paragraph sectionElem = section <|> fencedBlock <|> list <|> paragraph
paragraph = wrap "syntax.docParagraph" $ join <$> spaced leaf paragraph = wrap "syntax.docParagraph" $ join <$> spaced leaf
reserved word = isPrefixOf "}}" word || all (== '#') word reserved word = List.isPrefixOf "}}" word || all (== '#') word
wordy closing = wrap "syntax.docWord" . tok . fmap Textual . P.try $ do wordy closing = wrap "syntax.docWord" . tok . fmap Textual . P.try $ do
let end = let end =
@ -497,28 +499,26 @@ lexemes' eof =
verbatim = verbatim =
P.label "code (examples: ''**unformatted**'', `words` or '''_words_''')" $ do P.label "code (examples: ''**unformatted**'', `words` or '''_words_''')" $ do
Token txt start stop <- tokenP do Token originalText start stop <- tokenP do
-- a single backtick followed by a non-backtick is treated as monospaced -- a single backtick followed by a non-backtick is treated as monospaced
let tick = P.try (lit "`" <* P.lookAhead (P.satisfy (/= '`'))) let tick = P.try (lit "`" <* P.lookAhead (P.satisfy (/= '`')))
-- also two or more ' followed by that number of closing ' -- also two or more ' followed by that number of closing '
quotes <- tick <|> (lit "''" <+> P.takeWhileP Nothing (== '\'')) quotes <- tick <|> (lit "''" <+> P.takeWhileP Nothing (== '\''))
P.someTill P.anySingle (lit quotes) P.someTill P.anySingle (lit quotes)
if all isSpace $ takeWhile (/= '\n') txt let isMultiLine = line start /= line stop
then if isMultiLine
then do
let trimmed = (trimAroundDelimiters originalText)
let txt = trimIndentFromVerbatimBlock (column start - 1) trimmed
-- If it's a multi-line verbatim block we trim any whitespace representing
-- indentation from the pretty-printer. See 'trimIndentFromVerbatimBlock'
wrap "syntax.docVerbatim" $ wrap "syntax.docVerbatim" $
wrap "syntax.docWord" $ wrap "syntax.docWord" $
pure [Token (Textual (trim txt)) start stop] pure [Token (Textual txt) start stop]
else else
wrap "syntax.docCode" $ wrap "syntax.docCode" $
wrap "syntax.docWord" $ wrap "syntax.docWord" $
pure [Token (Textual txt) start stop] pure [Token (Textual originalText) start stop]
trim = f . f
where
f = reverse . dropThru
dropThru = dropNl . dropWhile (\ch -> isSpace ch && ch /= '\n')
dropNl ('\n' : t) = t
dropNl as = as
exampleInline = exampleInline =
P.label "inline code (examples: ``List.map f xs``, ``[1] :+ 2``)" $ P.label "inline code (examples: ``List.map f xs``, ``[1] :+ 2``)" $
@ -589,7 +589,7 @@ lexemes' eof =
| isSpace c && (not $ isControl c) = | isSpace c && (not $ isControl c) =
skip (col - 1) r skip (col - 1) r
skip _ s = s skip _ s = s
in intercalate "\n" $ skip column <$> lines s in List.intercalate "\n" $ skip column <$> lines s
other = wrap "syntax.docCodeBlock" $ do other = wrap "syntax.docCodeBlock" $ do
column <- (\x -> x - 1) . toInteger . P.unPos <$> LP.indentLevel column <- (\x -> x - 1) . toInteger . P.unPos <$> LP.indentLevel
@ -602,7 +602,7 @@ lexemes' eof =
_ <- void CP.eol _ <- void CP.eol
verbatim <- verbatim <-
tok $ tok $
Textual . uncolumn column tabWidth . trim Textual . uncolumn column tabWidth . trimAroundDelimiters
<$> P.someTill P.anySingle ([] <$ lit fence) <$> P.someTill P.anySingle ([] <$ lit fence)
pure (name <> verbatim) pure (name <> verbatim)
@ -816,7 +816,7 @@ lexemes' eof =
pure $ case tweak (lines s) of pure $ case tweak (lines s) of
[] -> s [] -> s
ls ls
| all (\l -> isPrefixOf leading l || all isSpace l) ls -> intercalate "\n" (drop (length leading) <$> ls) | all (\l -> List.isPrefixOf leading l || all isSpace l) ls -> List.intercalate "\n" (drop (length leading) <$> ls)
| otherwise -> s | otherwise -> s
quotedSingleLine = char '"' *> P.manyTill (LP.charLiteral <|> sp) (char '"') quotedSingleLine = char '"' *> P.manyTill (LP.charLiteral <|> sp) (char '"')
where where
@ -1017,6 +1017,108 @@ lexemes' eof =
where where
ok c = isDelayOrForce c || isSpace c || isAlphaNum c || Set.member c delimiters || c == '\"' ok c = isDelayOrForce c || isSpace c || isAlphaNum c || Set.member c delimiters || c == '\"'
-- | If it's a multi-line verbatim block we trim any whitespace representing
-- indentation from the pretty-printer.
--
-- E.g.
--
-- @@
-- {{
-- # Heading
-- '''
-- code
-- indented
-- '''
-- }}
-- @@
--
-- Should lex to the text literal "code\n indented".
--
-- If there's text in the literal that has LESS trailing whitespace than the
-- opening delimiters, we don't trim it at all. E.g.
--
-- @@
-- {{
-- # Heading
-- '''
-- code
-- '''
-- }}
-- @@
--
-- Is parsed as " code".
--
-- Trim the expected amount of whitespace from a text literal:
-- >>> trimIndentFromVerbatimBlock 2 " code\n indented"
-- "code\n indented"
--
-- If the text literal has less leading whitespace than the opening delimiters,
-- leave it as-is
-- >>> trimIndentFromVerbatimBlock 2 "code\n indented"
-- "code\n indented"
trimIndentFromVerbatimBlock :: Int -> String -> String
trimIndentFromVerbatimBlock leadingSpaces txt = fromMaybe txt $ do
List.intercalate "\n" <$> for (lines txt) \line -> do
-- If any 'stripPrefix' fails, we fail and return the unaltered text
case List.stripPrefix (replicate leadingSpaces ' ') line of
Just stripped -> Just stripped
Nothing ->
-- If it was a line with all white-space, just use an empty line,
-- this can happen easily in editors which trim trailing whitespace.
if all isSpace line
then Just ""
else Nothing
-- Trim leading/trailing whitespace from around delimiters, e.g.
--
-- {{
-- '''___ <- whitespace here including newline
-- text block
-- 👇 or here
-- __'''
-- }}
-- >>> trimAroundDelimiters " \n text block \n "
-- " text block "
--
-- Should leave leading and trailing line untouched if it contains non-whitespace, e.g.:
--
-- ''' leading whitespace
-- text block
-- trailing whitespace: '''
-- >>> trimAroundDelimiters " leading whitespace\n text block \ntrailing whitespace: "
-- " leading whitespace\n text block \ntrailing whitespace: "
--
-- Should keep trailing newline if it's the only thing on the line, e.g.:
--
-- '''
-- newline below
--
-- '''
-- >>> trimAroundDelimiters "\nnewline below\n\n"
-- "newline below\n\n"
trimAroundDelimiters :: String -> String
trimAroundDelimiters txt =
txt
& ( \s ->
List.breakOn "\n" s
& \case
(prefix, suffix)
| all isSpace prefix -> drop 1 suffix
| otherwise -> prefix <> suffix
)
& ( \s ->
List.breakOnEnd "\n" s
& \case
(_prefix, "") -> s
(prefix, suffix)
| all isSpace suffix -> dropTrailingNewline prefix
| otherwise -> prefix <> suffix
)
where
dropTrailingNewline = \case
[] -> []
(x : xs) -> NonEmpty.init (x NonEmpty.:| xs)
separated :: (Char -> Bool) -> P a -> P a separated :: (Char -> Bool) -> P a -> P a
separated ok p = P.try $ p <* P.lookAhead (void (P.satisfy ok) <|> P.eof) separated ok p = P.try $ p <* P.lookAhead (void (P.satisfy ok) <|> P.eof)
@ -1116,7 +1218,7 @@ close' reopenBlockname open closeP = do
case findClose open (layout env) of case findClose open (layout env) of
Nothing -> err pos1 (CloseWithoutMatchingOpen msgOpen (quote close)) Nothing -> err pos1 (CloseWithoutMatchingOpen msgOpen (quote close))
where where
msgOpen = intercalate " or " (quote <$> open) msgOpen = List.intercalate " or " (quote <$> open)
quote s = "'" <> s <> "'" quote s = "'" <> s <> "'"
Just (_, n) -> do Just (_, n) -> do
S.put (env {layout = drop n (layout env), opening = reopenBlockname}) S.put (env {layout = drop n (layout env), opening = reopenBlockname})

View File

@ -68,6 +68,7 @@ library
, bytes , bytes
, containers , containers
, cryptonite , cryptonite
, extra
, lens , lens
, megaparsec , megaparsec
, mtl , mtl
@ -125,6 +126,7 @@ test-suite syntax-tests
, containers , containers
, cryptonite , cryptonite
, easytest , easytest
, extra
, lens , lens
, megaparsec , megaparsec
, mtl , mtl