disco (empty) → 0.1.0.0
raw patch · 423 files changed
+19601/−0 lines, 423 filesdep +QuickCheckdep +algebraic-graphsdep +arithmoisetup-changed
Dependencies added: QuickCheck, algebraic-graphs, arithmoi, base, bytestring, containers, directory, disco, exact-combinatorics, exceptions, fgl, filepath, haskeline, integer-logarithms, lens, megaparsec, mtl, oeis, optparse-applicative, parser-combinators, polysemy, polysemy-plugin, polysemy-zoo, pretty, pretty-show, process, simple-enumeration, split, splitmix, tasty, tasty-golden, transformers, unbound-generics
Files
- LICENSE +31/−0
- README.md +108/−0
- Setup.hs +2/−0
- disco.cabal +515/−0
- docs/tutorial/example/arith-pattern.disco +8/−0
- docs/tutorial/example/basic-ellipsis.disco +19/−0
- docs/tutorial/example/basics.disco +5/−0
- docs/tutorial/example/case-pattern.disco +5/−0
- docs/tutorial/example/case.disco +5/−0
- docs/tutorial/example/comment.disco +9/−0
- docs/tutorial/example/comprehension.disco +10/−0
- docs/tutorial/example/doc.disco +14/−0
- docs/tutorial/example/function-desugar.disco +8/−0
- docs/tutorial/example/function.disco +9/−0
- docs/tutorial/example/general-ellipsis.disco +11/−0
- docs/tutorial/example/higher-order.disco +2/−0
- docs/tutorial/example/let.disco +6/−0
- docs/tutorial/example/list.disco +17/−0
- docs/tutorial/example/multi-arg-functions.disco +9/−0
- docs/tutorial/example/pair.disco +11/−0
- docs/tutorial/example/poly.disco +3/−0
- docs/tutorial/example/property.disco +21/−0
- docs/tutorial/example/sum.disco +8/−0
- docs/tutorial/example/tydefs-poly.disco +13/−0
- docs/tutorial/example/tydefs.disco +11/−0
- docs/tutorial/example/unit-test.disco +8/−0
- example/TestExamples.hs +25/−0
- example/abs.disco +13/−0
- example/catalan.disco +19/−0
- example/demo.disco +29/−0
- example/gcd.disco +22/−0
- example/grid.disco +75/−0
- example/lists.disco +12/−0
- example/logic.disco +20/−0
- example/nim.disco +66/−0
- example/prime.disco +53/−0
- example/prog.disco +57/−0
- example/prop.disco +4/−0
- example/rsa.disco +73/−0
- example/sums.disco +7/−0
- example/tree.disco +23/−0
- lib/container.disco +13/−0
- lib/graph.disco +58/−0
- lib/list.disco +72/−0
- lib/num.disco +47/−0
- lib/oeis.disco +13/−0
- lib/prim.disco +4/−0
- lib/product.disco +7/−0
- lib/prop.disco +16/−0
- repl/REPL.hs +16/−0
- src/Disco/AST/Core.hs +390/−0
- src/Disco/AST/Desugared.hs +272/−0
- src/Disco/AST/Generic.hs +656/−0
- src/Disco/AST/Surface.hs +654/−0
- src/Disco/AST/Typed.hs +532/−0
- src/Disco/Compile.hs +490/−0
- src/Disco/Context.hs +232/−0
- src/Disco/Data.hs +32/−0
- src/Disco/Desugar.hs +822/−0
- src/Disco/Effects/Counter.hs +41/−0
- src/Disco/Effects/Fresh.hs +107/−0
- src/Disco/Effects/Input.hs +26/−0
- src/Disco/Effects/LFresh.hs +149/−0
- src/Disco/Effects/Random.hs +32/−0
- src/Disco/Effects/State.hs +42/−0
- src/Disco/Effects/Store.hs +53/−0
- src/Disco/Enumerate.hs +192/−0
- src/Disco/Error.hs +318/−0
- src/Disco/Eval.hs +422/−0
- src/Disco/Extensions.hs +48/−0
- src/Disco/Interactive/CmdLine.hs +193/−0
- src/Disco/Interactive/Commands.hs +785/−0
- src/Disco/Interpret/CESK.hs +790/−0
- src/Disco/Messages.hs +63/−0
- src/Disco/Module.hs +196/−0
- src/Disco/Names.hs +111/−0
- src/Disco/Parser.hs +1048/−0
- src/Disco/Pretty.hs +191/−0
- src/Disco/Pretty/DSL.hs +107/−0
- src/Disco/Pretty/Prec.hs +53/−0
- src/Disco/Property.hs +53/−0
- src/Disco/Report.hs +55/−0
- src/Disco/Subst.hs +126/−0
- src/Disco/Syntax/Operators.hs +213/−0
- src/Disco/Syntax/Prims.hs +229/−0
- src/Disco/Typecheck.hs +1604/−0
- src/Disco/Typecheck/Constraints.hs +82/−0
- src/Disco/Typecheck/Erase.hs +123/−0
- src/Disco/Typecheck/Graph.hs +155/−0
- src/Disco/Typecheck/Solve.hs +1046/−0
- src/Disco/Typecheck/Unify.hs +142/−0
- src/Disco/Typecheck/Util.hs +151/−0
- src/Disco/Types.hs +778/−0
- src/Disco/Types/Qualifiers.hs +117/−0
- src/Disco/Types/Rules.hs +270/−0
- src/Disco/Util.hs +30/−0
- src/Disco/Value.hs +574/−0
- stack.yaml +44/−0
- test/README.md +48/−0
- test/Tests.hs +83/−0
- test/arith-basic-bin/expected +24/−0
- test/arith-basic-bin/input +24/−0
- test/arith-basic-un/expected +23/−0
- test/arith-basic-un/input +26/−0
- test/arith-count/expected +28/−0
- test/arith-count/input +28/−0
- test/arith-numthry/expected +12/−0
- test/arith-numthry/input +12/−0
- test/arith-prim/arith-prim.disco +4/−0
- test/arith-prim/expected +4/−0
- test/arith-prim/input +2/−0
- test/arith-round/expected +15/−0
- test/arith-round/input +16/−0
- test/case-arith/case-arith.disco +54/−0
- test/case-arith/expected +37/−0
- test/case-arith/input +41/−0
- test/case-basic/case-basic.disco +7/−0
- test/case-basic/expected +7/−0
- test/case-basic/input +6/−0
- test/case-let/case-let.disco +5/−0
- test/case-let/expected +4/−0
- test/case-let/input +3/−0
- test/compile-cons/expected +1/−0
- test/compile-cons/input +1/−0
- test/compile-misc/expected +9/−0
- test/compile-misc/input +6/−0
- test/containers-cmp/expected +4/−0
- test/containers-cmp/input +4/−0
- test/containers-comp/expected +5/−0
- test/containers-comp/input +5/−0
- test/containers-convert/expected +10/−0
- test/containers-convert/input +10/−0
- test/containers-each/expected +12/−0
- test/containers-each/input +12/−0
- test/containers-ellipsis/expected +5/−0
- test/containers-ellipsis/input +5/−0
- test/containers-filter/expected +6/−0
- test/containers-filter/input +6/−0
- test/containers-join/expected +7/−0
- test/containers-join/input +8/−0
- test/containers-merge/expected +8/−0
- test/containers-merge/input +9/−0
- test/containers-ops/expected +60/−0
- test/containers-ops/input +60/−0
- test/containers-reduce/containers-reduce.disco +14/−0
- test/containers-reduce/expected +15/−0
- test/containers-reduce/input +10/−0
- test/error-ambiguous/a.disco +2/−0
- test/error-ambiguous/ambiguous.disco +8/−0
- test/error-ambiguous/b.disco +2/−0
- test/error-ambiguous/expected +8/−0
- test/error-ambiguous/input +1/−0
- test/error-cyclic/cyclic.disco +3/−0
- test/error-cyclic/expected +3/−0
- test/error-cyclic/input +1/−0
- test/error-duplicatedecls/dupdecls.disco +5/−0
- test/error-duplicatedecls/expected +3/−0
- test/error-duplicatedecls/input +1/−0
- test/error-duplicatedefns/dupdefns.disco +3/−0
- test/error-duplicatedefns/expected +3/−0
- test/error-duplicatedefns/input +1/−0
- test/error-duplicatetydefns/duptydefns.disco +2/−0
- test/error-duplicatetydefns/expected +3/−0
- test/error-duplicatetydefns/input +1/−0
- test/error-emptycase/expected +2/−0
- test/error-emptycase/input +1/−0
- test/error-names/expected +2/−0
- test/error-names/input +1/−0
- test/error-notcon/expected +5/−0
- test/error-notcon/input +4/−0
- test/error-notype/expected +3/−0
- test/error-notype/input +1/−0
- test/error-numpatterns/expected +3/−0
- test/error-numpatterns/input +1/−0
- test/error-numpatterns/numpatterns.disco +3/−0
- test/error-pattype/expected +14/−0
- test/error-pattype/input +8/−0
- test/error-polyrec/expected +4/−0
- test/error-polyrec/input +1/−0
- test/error-polyrec/polyrec.disco +1/−0
- test/error-qualskolem/expected +4/−0
- test/error-qualskolem/input +1/−0
- test/error-qualskolem/qualskolem.disco +2/−0
- test/error-tyargs/error-tyargs.disco +1/−0
- test/error-tyargs/expected +12/−0
- test/error-tyargs/input +7/−0
- test/error-unboundtyvar/expected +3/−0
- test/error-unboundtyvar/input +1/−0
- test/error-unboundtyvar/unboundtyvar.disco +1/−0
- test/error-unqual-base/expected +3/−0
- test/error-unqual-base/input +1/−0
- test/error-unqual-base/unqualbase.disco +2/−0
- test/error-unqual/expected +2/−0
- test/error-unqual/input +1/−0
- test/error-wildcard/expected +2/−0
- test/error-wildcard/input +1/−0
- test/graphs-basic/expected +16/−0
- test/graphs-basic/input +17/−0
- test/graphs-equality/expected +4/−0
- test/graphs-equality/input +4/−0
- test/interp-loop/expected +1/−0
- test/interp-loop/input +2/−0
- test/interp-strictmatch/bomb.disco +3/−0
- test/interp-strictmatch/expected +13/−0
- test/interp-strictmatch/input +13/−0
- test/lib-oeis/expected +8/−0
- test/lib-oeis/input +22/−0
- test/list-comp/expected +11/−0
- test/list-comp/input +12/−0
- test/list-poly/expected +32/−0
- test/list-poly/input +32/−0
- test/logic-bools/expected +20/−0
- test/logic-bools/input +20/−0
- test/logic-cmp/expected +45/−0
- test/logic-cmp/input +45/−0
- test/map-basic/expected +23/−0
- test/map-basic/input +25/−0
- test/map-compare/expected +4/−0
- test/map-compare/input +4/−0
- test/module-basic/a.disco +7/−0
- test/module-basic/b.disco +2/−0
- test/module-basic/c.disco +6/−0
- test/module-basic/e.disco +5/−0
- test/module-basic/expected +13/−0
- test/module-basic/input +7/−0
- test/module-basic/subdir/d.disco +2/−0
- test/module-cycle/cyclic1.disco +3/−0
- test/module-cycle/cyclic2.disco +3/−0
- test/module-cycle/expected +4/−0
- test/module-cycle/input +1/−0
- test/module-notfound/expected +1/−0
- test/module-notfound/input +1/−0
- test/parse-245/expected +4/−0
- test/parse-245/input +4/−0
- test/parse-280/capitalvars.disco +5/−0
- test/parse-280/expected +2/−0
- test/parse-280/input +1/−0
- test/parse-case-expr/expected +1/−0
- test/parse-case-expr/input +1/−0
- test/parse-nested-list/expected +2/−0
- test/parse-nested-list/input +2/−0
- test/parse-quantifiers/expected +37/−0
- test/parse-quantifiers/input +36/−0
- test/parse-top-term/expected +2/−0
- test/parse-top-term/input +1/−0
- test/parse-top-term/parse-top-term.disco +14/−0
- test/poly-bad/expected +12/−0
- test/poly-bad/input +6/−0
- test/poly-infer-sort/expected +11/−0
- test/poly-infer-sort/input +10/−0
- test/poly-instantiate/expected +12/−0
- test/poly-instantiate/input +11/−0
- test/poly-instantiate/poly-instantiate.disco +5/−0
- test/poly-rectype/expected +2/−0
- test/poly-rectype/input +1/−0
- test/poly-rectype/poly-rectype.disco +10/−0
- test/pretty-defn/expected +5/−0
- test/pretty-defn/input +10/−0
- test/pretty-functions/expected +3/−0
- test/pretty-functions/input +3/−0
- test/pretty-issue258/expected +12/−0
- test/pretty-issue258/input +4/−0
- test/pretty-lit/expected +3/−0
- test/pretty-lit/input +3/−0
- test/pretty-ops/expected +6/−0
- test/pretty-ops/input +6/−0
- test/pretty-pattern/expected +11/−0
- test/pretty-pattern/input +11/−0
- test/pretty-torture/expected +25/−0
- test/pretty-torture/input +24/−0
- test/pretty-type/expected +3/−0
- test/pretty-type/input +3/−0
- test/pretty-whnf/expected +2/−0
- test/pretty-whnf/input +2/−0
- test/prim-crash/expected +11/−0
- test/prim-crash/input +11/−0
- test/prim-frac/expected +10/−0
- test/prim-frac/input +11/−0
- test/prim-sum/expected +3/−0
- test/prim-sum/input +3/−0
- test/prop-basic/expected +12/−0
- test/prop-basic/input +9/−0
- test/prop-basic/prop-basic.disco +23/−0
- test/prop-cmp/expected +6/−0
- test/prop-cmp/input +3/−0
- test/prop-fail/bad-tests.disco +21/−0
- test/prop-fail/expected +21/−0
- test/prop-fail/input +1/−0
- test/prop-fairness/expected +4/−0
- test/prop-fairness/input +4/−0
- test/prop-higher-order/expected +19/−0
- test/prop-higher-order/higher-order.disco +35/−0
- test/prop-higher-order/input +5/−0
- test/prop-holds/expected +7/−0
- test/prop-holds/input +7/−0
- test/prop-impredicative/expected +18/−0
- test/prop-impredicative/input +9/−0
- test/prop-impredicative/prop-impredicative.disco +1/−0
- test/prop-tests/expected +6/−0
- test/prop-tests/input +1/−0
- test/prop-tests/prop-tests.disco +65/−0
- test/prop-type/expected +12/−0
- test/prop-type/input +9/−0
- test/repl-ann/expected +3/−0
- test/repl-ann/input +3/−0
- test/repl-compile/expected +2/−0
- test/repl-compile/input +2/−0
- test/repl-defn/expected +4/−0
- test/repl-defn/input +7/−0
- test/repl-defns/expected +2/−0
- test/repl-defns/input +6/−0
- test/repl-desugar/expected +2/−0
- test/repl-desugar/input +2/−0
- test/repl-doc/doc.disco +8/−0
- test/repl-doc/expected +36/−0
- test/repl-doc/input +7/−0
- test/repl-eval-tydef-import/a.disco +4/−0
- test/repl-eval-tydef-import/b.disco +1/−0
- test/repl-eval-tydef-import/expected +4/−0
- test/repl-eval-tydef-import/input +2/−0
- test/repl-help/expected +12/−0
- test/repl-help/input +2/−0
- test/repl-import/expected +3/−0
- test/repl-import/input +3/−0
- test/repl-names/expected +8/−0
- test/repl-names/input +2/−0
- test/repl-names/logic.disco +27/−0
- test/repl-names/other.disco +9/−0
- test/repl-proptest/expected +12/−0
- test/repl-proptest/input +4/−0
- test/solver-issue112/diag-iso-bad.disco +9/−0
- test/solver-issue112/expected +5/−0
- test/solver-issue112/input +2/−0
- test/syntax-chain/expected +16/−0
- test/syntax-chain/inRange.disco +2/−0
- test/syntax-chain/input +15/−0
- test/syntax-clause/clauses.disco +17/−0
- test/syntax-clause/expected +4/−0
- test/syntax-clause/input +3/−0
- test/syntax-comment/expected +5/−0
- test/syntax-comment/fib.disco +18/−0
- test/syntax-comment/input +2/−0
- test/syntax-containers/expected +3/−0
- test/syntax-containers/input +3/−0
- test/syntax-decimals/expected +24/−0
- test/syntax-decimals/input +24/−0
- test/syntax-doc/expected +10/−0
- test/syntax-doc/input +2/−0
- test/syntax-doc/syntax-doc.disco +10/−0
- test/syntax-exts/expected +2/−0
- test/syntax-exts/input +1/−0
- test/syntax-exts/syntax-exts.disco +5/−0
- test/syntax-juxt-app/expected +3/−0
- test/syntax-juxt-app/input +2/−0
- test/syntax-juxt-app/juxt-app.disco +2/−0
- test/syntax-juxt-mul/expected +18/−0
- test/syntax-juxt-mul/input +13/−0
- test/syntax-juxt-mul/juxt-mul.disco +8/−0
- test/syntax-lambda-pat/expected +7/−0
- test/syntax-lambda-pat/input +7/−0
- test/syntax-lambda/expected +14/−0
- test/syntax-lambda/input +14/−0
- test/syntax-let/expected +6/−0
- test/syntax-let/input +6/−0
- test/syntax-many-args/expected +4/−0
- test/syntax-many-args/input +3/−0
- test/syntax-many-args/many-args.disco +4/−0
- test/syntax-many-clauses/expected +9/−0
- test/syntax-many-clauses/input +8/−0
- test/syntax-many-clauses/many-clauses.disco +104/−0
- test/syntax-patclause/expected +4/−0
- test/syntax-patclause/fact.disco +13/−0
- test/syntax-patclause/input +3/−0
- test/syntax-prims/expected +4/−0
- test/syntax-prims/input +3/−0
- test/syntax-prims/syntax-prims.disco +4/−0
- test/syntax-tuples/expected +10/−0
- test/syntax-tuples/input +10/−0
- test/syntax-types/expected +11/−0
- test/syntax-types/input +12/−0
- test/types-192/expected +1/−0
- test/types-192/input +1/−0
- test/types-bind/expected +1/−0
- test/types-bind/input +1/−0
- test/types-char-string/expected +34/−0
- test/types-char-string/input +30/−0
- test/types-compare/expected +1/−0
- test/types-compare/input +1/−0
- test/types-container/expected +12/−0
- test/types-container/input +13/−0
- test/types-kinds/expected +10/−0
- test/types-kinds/input +5/−0
- test/types-naked-ops/expected +5/−0
- test/types-naked-ops/input +5/−0
- test/types-numpats/expected +2/−0
- test/types-numpats/input +1/−0
- test/types-numpats/types-numpats.disco +9/−0
- test/types-ops/expected +38/−0
- test/types-ops/input +50/−0
- test/types-rational/expected +19/−0
- test/types-rational/input +22/−0
- test/types-rec/expected +5/−0
- test/types-rec/input +2/−0
- test/types-rec/types-rec.disco +24/−0
- test/types-squash/expected +8/−0
- test/types-squash/input +10/−0
- test/types-standalone-ops/expected +4/−0
- test/types-standalone-ops/input +4/−0
- test/types-toomanypats/expected +3/−0
- test/types-toomanypats/input +1/−0
- test/types-toomanypats/toomanypats.disco +2/−0
- test/types-tydef-bad/expected +3/−0
- test/types-tydef-bad/input +1/−0
- test/types-tydef-bad/types-tydef-bad.disco +1/−0
- test/types-tydef-kind/expected +3/−0
- test/types-tydef-kind/input +2/−0
- test/types-tydef-kind/types-tydef-kind.disco +5/−0
- test/types-tydef-param/expected +9/−0
- test/types-tydef-param/input +8/−0
- test/types-tydef-param/types-tydef-param.disco +37/−0
- test/types-tydefs/expected +7/−0
- test/types-tydefs/input +6/−0
- test/types-tydefs/types-tydefs.disco +20/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright the disco project and contributors+SPDX-License-Identifier: BSD-3-Clause++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Disco team nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,108 @@+[](https://travis-ci.org/disco-lang/disco)+[](CODE_OF_CONDUCT.md)++Disco is a programming language intended to teach basic functional+programming principles in the context of a discrete mathematics+course.++Installation+------------++If you just want to *use* disco (*i.e.* if you are a student), follow+these instructions. If you want to *contribute* to disco development,+you should skip to the instructions below about building with stack.++- Follow the instructions to [install+ ghcup](https://www.haskell.org/ghcup/) by opening a terminal or+ command prompt and copy-pasting the given installation command. You+ can just accept all the defaults. If you don't have [Windows+ Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/)+ (if you don't know what that is, then you don't have it), see the+ [instructions here](https://www.haskell.org/ghcup/install/) for a+ PowerShell command to run.+- Now run `cabal install disco` at a command prompt.+- If it works, you should be able to now type `disco` at a command+ prompt, which should display a message like this:++ ```+ Welcome to Disco!++ A language for programming discrete mathematics.++ Disco>+ ```++If you encounter any difficulties, please let me know --- either come+talk to me or [open a GitHub+issue](https://github.com/disco-lang/disco/issues/new). These+instructions will be kept up-to-date with whatever helpful tips or+workarounds I learn. So even if you encounter a difficulty but figure+out the solution youself, let me know --- that way I can include the+problem and solution here so others can benefit!++Design principles+-----------------++* Includes those features, and *only* those features, useful in the+ context of a discrete math course. This is *not* intended to be a+ general-purpose language.+* Syntax is as close to standard *mathematical* practice as possible,+ to make it easier for mathematicians to pick up, and to reduce as+ much as possible the incongruity between the language and the+ mathematics being explored and modeled.+* Tooling, error messages, etc. are very important---the language+ needs to be accessible to undergrads with no prior programming+ experience. (However, this principle is, as of yet, only+ that---there is no tooling or nice error messages to speak of.)++Feel free to look around, ask questions, etc. You can also+[contribute](CONTRIBUTING.md)---collaborators are most welcome.++Community+---------++Check out the disco IRC channel, `#disco-lang` on Libera.Chat. If+you're not familiar with IRC, you can connect via [this web client](https://kiwiirc.com/nextclient/irc.libera.chat/?nick=Guest?#disco-lang).++Documentation+-------------++Documentation is [hosted on+readthedocs.io](http://disco-lang.readthedocs.io/en/latest/).++Contributing+------------++If you'd like to contribute to disco development, check out+[CONTRIBUTING.md](CONTRIBUTING.md).++Building with stack+-------------------++First, make sure you have+[the `stack` tool](https://docs.haskellstack.org/en/stable/README/)+(the easiest way to install it is via [ghcup](https://www.haskell.org/ghcup/)).+Then open a command prompt, navigate to the root directory of this+repository, and execute++```+stack build+```++After this completes, you should be able to++```+stack exec disco+```++to run the Disco command-line REPL.++While developing, you may want to use a command like++```+stack test --fast --file-watch --ghc-options='-Wall'+```++which will turn on warnings, turn off optimizations for a faster+edit-compile-test cycle, and automatically recompile and run the test+suite every time a source file changes.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ disco.cabal view
@@ -0,0 +1,515 @@+cabal-version: 2.4+name: disco+version: 0.1.0.0+synopsis: Functional programming language for teaching discrete math.+description: Disco is a simple functional programming language for use in+ teaching discrete math. Its syntax is designed to be close+ to standard mathematical practice.+license: BSD-3-Clause+license-file: LICENSE+author: Disco team+maintainer: byorgey@gmail.com+copyright: Disco team 2016 (see LICENSE)+category: Language++tested-with: GHC == 8.10.4++data-dir: lib++data-files: *.disco++extra-source-files: README.md, stack.yaml, example/*.disco, repl/*.hs+ docs/tutorial/example/*.disco+ --- TEST FILES BEGIN (updated automatically by add-test-files.hs) ---+ test/README.md+ test/Tests.hs+ test/arith-basic-bin/expected+ test/arith-basic-bin/input+ test/arith-basic-un/expected+ test/arith-basic-un/input+ test/arith-count/expected+ test/arith-count/input+ test/arith-numthry/expected+ test/arith-numthry/input+ test/arith-prim/arith-prim.disco+ test/arith-prim/expected+ test/arith-prim/input+ test/arith-round/expected+ test/arith-round/input+ test/case-arith/case-arith.disco+ test/case-arith/expected+ test/case-arith/input+ test/case-basic/case-basic.disco+ test/case-basic/expected+ test/case-basic/input+ test/case-let/case-let.disco+ test/case-let/expected+ test/case-let/input+ test/compile-cons/expected+ test/compile-cons/input+ test/compile-misc/expected+ test/compile-misc/input+ test/containers-cmp/expected+ test/containers-cmp/input+ test/containers-comp/expected+ test/containers-comp/input+ test/containers-convert/expected+ test/containers-convert/input+ test/containers-each/expected+ test/containers-each/input+ test/containers-ellipsis/expected+ test/containers-ellipsis/input+ test/containers-filter/expected+ test/containers-filter/input+ test/containers-join/expected+ test/containers-join/input+ test/containers-merge/expected+ test/containers-merge/input+ test/containers-ops/expected+ test/containers-ops/input+ test/containers-reduce/containers-reduce.disco+ test/containers-reduce/expected+ test/containers-reduce/input+ test/error-ambiguous/a.disco+ test/error-ambiguous/ambiguous.disco+ test/error-ambiguous/b.disco+ test/error-ambiguous/expected+ test/error-ambiguous/input+ test/error-cyclic/cyclic.disco+ test/error-cyclic/expected+ test/error-cyclic/input+ test/error-duplicatedecls/dupdecls.disco+ test/error-duplicatedecls/expected+ test/error-duplicatedecls/input+ test/error-duplicatedefns/dupdefns.disco+ test/error-duplicatedefns/expected+ test/error-duplicatedefns/input+ test/error-duplicatetydefns/duptydefns.disco+ test/error-duplicatetydefns/expected+ test/error-duplicatetydefns/input+ test/error-emptycase/expected+ test/error-emptycase/input+ test/error-names/expected+ test/error-names/input+ test/error-notcon/expected+ test/error-notcon/input+ test/error-notype/expected+ test/error-notype/input+ test/error-numpatterns/expected+ test/error-numpatterns/input+ test/error-numpatterns/numpatterns.disco+ test/error-pattype/expected+ test/error-pattype/input+ test/error-polyrec/expected+ test/error-polyrec/input+ test/error-polyrec/polyrec.disco+ test/error-qualskolem/expected+ test/error-qualskolem/input+ test/error-qualskolem/qualskolem.disco+ test/error-tyargs/error-tyargs.disco+ test/error-tyargs/expected+ test/error-tyargs/input+ test/error-unboundtyvar/expected+ test/error-unboundtyvar/input+ test/error-unboundtyvar/unboundtyvar.disco+ test/error-unqual-base/expected+ test/error-unqual-base/input+ test/error-unqual-base/unqualbase.disco+ test/error-unqual/expected+ test/error-unqual/input+ test/error-wildcard/expected+ test/error-wildcard/input+ test/graphs-basic/expected+ test/graphs-basic/input+ test/graphs-equality/expected+ test/graphs-equality/input+ test/interp-loop/expected+ test/interp-loop/input+ test/interp-strictmatch/bomb.disco+ test/interp-strictmatch/expected+ test/interp-strictmatch/input+ test/lib-oeis/expected+ test/lib-oeis/input+ test/list-comp/expected+ test/list-comp/input+ test/list-poly/expected+ test/list-poly/input+ test/logic-bools/expected+ test/logic-bools/input+ test/logic-cmp/expected+ test/logic-cmp/input+ test/map-basic/expected+ test/map-basic/input+ test/map-compare/expected+ test/map-compare/input+ test/module-basic/a.disco+ test/module-basic/b.disco+ test/module-basic/c.disco+ test/module-basic/e.disco+ test/module-basic/expected+ test/module-basic/input+ test/module-basic/subdir/d.disco+ test/module-cycle/cyclic1.disco+ test/module-cycle/cyclic2.disco+ test/module-cycle/expected+ test/module-cycle/input+ test/module-notfound/expected+ test/module-notfound/input+ test/parse-245/expected+ test/parse-245/input+ test/parse-280/capitalvars.disco+ test/parse-280/expected+ test/parse-280/input+ test/parse-case-expr/expected+ test/parse-case-expr/input+ test/parse-nested-list/expected+ test/parse-nested-list/input+ test/parse-quantifiers/expected+ test/parse-quantifiers/input+ test/parse-top-term/expected+ test/parse-top-term/input+ test/parse-top-term/parse-top-term.disco+ test/poly-bad/expected+ test/poly-bad/input+ test/poly-infer-sort/expected+ test/poly-infer-sort/input+ test/poly-instantiate/expected+ test/poly-instantiate/input+ test/poly-instantiate/poly-instantiate.disco+ test/poly-rectype/expected+ test/poly-rectype/input+ test/poly-rectype/poly-rectype.disco+ test/pretty-defn/expected+ test/pretty-defn/input+ test/pretty-functions/expected+ test/pretty-functions/input+ test/pretty-issue258/expected+ test/pretty-issue258/input+ test/pretty-lit/expected+ test/pretty-lit/input+ test/pretty-ops/expected+ test/pretty-ops/input+ test/pretty-pattern/expected+ test/pretty-pattern/input+ test/pretty-torture/expected+ test/pretty-torture/input+ test/pretty-type/expected+ test/pretty-type/input+ test/pretty-whnf/expected+ test/pretty-whnf/input+ test/prim-crash/expected+ test/prim-crash/input+ test/prim-frac/expected+ test/prim-frac/input+ test/prim-sum/expected+ test/prim-sum/input+ test/prop-basic/expected+ test/prop-basic/input+ test/prop-basic/prop-basic.disco+ test/prop-cmp/expected+ test/prop-cmp/input+ test/prop-fail/bad-tests.disco+ test/prop-fail/expected+ test/prop-fail/input+ test/prop-fairness/expected+ test/prop-fairness/input+ test/prop-higher-order/expected+ test/prop-higher-order/higher-order.disco+ test/prop-higher-order/input+ test/prop-holds/expected+ test/prop-holds/input+ test/prop-impredicative/expected+ test/prop-impredicative/input+ test/prop-impredicative/prop-impredicative.disco+ test/prop-tests/expected+ test/prop-tests/input+ test/prop-tests/prop-tests.disco+ test/prop-type/expected+ test/prop-type/input+ test/repl-ann/expected+ test/repl-ann/input+ test/repl-compile/expected+ test/repl-compile/input+ test/repl-defn/expected+ test/repl-defn/input+ test/repl-defns/expected+ test/repl-defns/input+ test/repl-desugar/expected+ test/repl-desugar/input+ test/repl-doc/doc.disco+ test/repl-doc/expected+ test/repl-doc/input+ test/repl-eval-tydef-import/a.disco+ test/repl-eval-tydef-import/b.disco+ test/repl-eval-tydef-import/expected+ test/repl-eval-tydef-import/input+ test/repl-help/expected+ test/repl-help/input+ test/repl-import/expected+ test/repl-import/input+ test/repl-names/expected+ test/repl-names/input+ test/repl-names/logic.disco+ test/repl-names/other.disco+ test/repl-proptest/expected+ test/repl-proptest/input+ test/solver-issue112/diag-iso-bad.disco+ test/solver-issue112/expected+ test/solver-issue112/input+ test/syntax-chain/expected+ test/syntax-chain/inRange.disco+ test/syntax-chain/input+ test/syntax-clause/clauses.disco+ test/syntax-clause/expected+ test/syntax-clause/input+ test/syntax-comment/expected+ test/syntax-comment/fib.disco+ test/syntax-comment/input+ test/syntax-containers/expected+ test/syntax-containers/input+ test/syntax-decimals/expected+ test/syntax-decimals/input+ test/syntax-doc/expected+ test/syntax-doc/input+ test/syntax-doc/syntax-doc.disco+ test/syntax-exts/expected+ test/syntax-exts/input+ test/syntax-exts/syntax-exts.disco+ test/syntax-juxt-app/expected+ test/syntax-juxt-app/input+ test/syntax-juxt-app/juxt-app.disco+ test/syntax-juxt-mul/expected+ test/syntax-juxt-mul/input+ test/syntax-juxt-mul/juxt-mul.disco+ test/syntax-lambda-pat/expected+ test/syntax-lambda-pat/input+ test/syntax-lambda/expected+ test/syntax-lambda/input+ test/syntax-let/expected+ test/syntax-let/input+ test/syntax-many-args/expected+ test/syntax-many-args/input+ test/syntax-many-args/many-args.disco+ test/syntax-many-clauses/expected+ test/syntax-many-clauses/input+ test/syntax-many-clauses/many-clauses.disco+ test/syntax-patclause/expected+ test/syntax-patclause/fact.disco+ test/syntax-patclause/input+ test/syntax-prims/expected+ test/syntax-prims/input+ test/syntax-prims/syntax-prims.disco+ test/syntax-tuples/expected+ test/syntax-tuples/input+ test/syntax-types/expected+ test/syntax-types/input+ test/types-192/expected+ test/types-192/input+ test/types-bind/expected+ test/types-bind/input+ test/types-char-string/expected+ test/types-char-string/input+ test/types-compare/expected+ test/types-compare/input+ test/types-container/expected+ test/types-container/input+ test/types-kinds/expected+ test/types-kinds/input+ test/types-naked-ops/expected+ test/types-naked-ops/input+ test/types-numpats/expected+ test/types-numpats/input+ test/types-numpats/types-numpats.disco+ test/types-ops/expected+ test/types-ops/input+ test/types-rational/expected+ test/types-rational/input+ test/types-rec/expected+ test/types-rec/input+ test/types-rec/types-rec.disco+ test/types-squash/expected+ test/types-squash/input+ test/types-standalone-ops/expected+ test/types-standalone-ops/input+ test/types-toomanypats/expected+ test/types-toomanypats/input+ test/types-toomanypats/toomanypats.disco+ test/types-tydef-bad/expected+ test/types-tydef-bad/input+ test/types-tydef-bad/types-tydef-bad.disco+ test/types-tydef-kind/expected+ test/types-tydef-kind/input+ test/types-tydef-kind/types-tydef-kind.disco+ test/types-tydef-param/expected+ test/types-tydef-param/input+ test/types-tydef-param/types-tydef-param.disco+ test/types-tydefs/expected+ test/types-tydefs/input+ test/types-tydefs/types-tydefs.disco+ --- TEST FILES END ---++build-type: Simple++source-repository head+ type: git+ location: git://github.com/disco-lang/disco.git++common common+ ghc-options: -Wall+ -Wcompat+ -Widentities+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -Wno-star-is-type+ -Wpartial-fields+ default-language: Haskell2010++library+ import: common+ ghc-options: -flate-specialise -fspecialise-aggressively -fplugin=Polysemy.Plugin+ default-extensions: DataKinds+ DeriveGeneric+ FlexibleContexts+ FlexibleInstances+ GADTs+ LambdaCase+ MultiParamTypeClasses+ PolyKinds+ RankNTypes+ ScopedTypeVariables+ TupleSections+ TypeApplications+ TypeOperators+ TypeFamilies+ ViewPatterns+ exposed-modules:+ Disco.Syntax.Operators+ Disco.Syntax.Prims+ Disco.Extensions+ Disco.Effects.Counter+ Disco.Effects.Fresh+ Disco.Effects.Input+ Disco.Effects.LFresh+ Disco.Effects.Random+ Disco.Effects.State+ Disco.Effects.Store+ Disco.AST.Core+ Disco.AST.Desugared+ Disco.AST.Generic+ Disco.AST.Surface+ Disco.AST.Typed+ Disco.Data+ Disco.Names+ Disco.Context+ Disco.Report+ Disco.Messages+ Disco.Module+ Disco.Parser+ Disco.Pretty+ Disco.Pretty.DSL+ Disco.Pretty.Prec+ Disco.Property+ Disco.Desugar+ Disco.Compile+ Disco.Enumerate+ Disco.Value+ Disco.Error+ Disco.Eval+ Disco.Interpret.CESK+ Disco.Subst+ Disco.Typecheck+ Disco.Typecheck.Constraints+ Disco.Typecheck.Erase+ Disco.Typecheck.Graph+ Disco.Typecheck.Util+ Disco.Typecheck.Solve+ Disco.Typecheck.Unify+ Disco.Types+ Disco.Types.Qualifiers+ Disco.Types.Rules+ Disco.Util+ Disco.Interactive.CmdLine+ Disco.Interactive.Commands++ other-modules: Paths_disco+ autogen-modules: Paths_disco++ build-depends: base >=4.8 && <4.17,+ filepath,+ directory,+ mtl >=2.2 && <2.3,+ megaparsec >= 6.1.1 && < 9.3,+ parser-combinators >= 1.0.0 && < 1.4,+ pretty >=1.1 && <1.2,+ split >= 0.2 && < 0.3,+ transformers >= 0.4 && < 0.7,+ containers >=0.5 && <0.7,+ unbound-generics >= 0.3 && < 0.5,+ polysemy >= 1.6.0.0 && < 1.8,+ polysemy-plugin >= 0.4 && < 0.5,+ polysemy-zoo >= 0.7 && < 0.8,+ lens >= 4.14 && < 5.2,+ exact-combinatorics >= 0.2 && < 0.3,+ arithmoi >= 0.10 && < 0.13,+ integer-logarithms >= 1.0 && < 1.1,+ simple-enumeration >= 0.2 && < 0.3,+ haskeline >=0.8 && <0.9,+ exceptions >= 0.10 && < 0.11,+ QuickCheck >= 2.9 && < 2.15,+ splitmix >= 0.1 && < 0.2,+ fgl >= 5.5 && < 5.8,+ optparse-applicative >= 0.12 && < 0.17,+ oeis >= 0.3.10,+ algebraic-graphs >= 0.5,+ pretty-show >= 1.10++ hs-source-dirs: src+ default-language: Haskell2010++executable disco+ import: common+ hs-source-dirs: repl+ main-is: REPL.hs+ build-depends: base,+ disco,+ directory,+ filepath,+ haskeline >=0.8 && <0.9,+ mtl >=2.2 && <2.3,+ transformers >= 0.4 && < 0.7,+ megaparsec >= 6.1.1 && < 9.3,+ containers >= 0.5 && < 0.7,+ unbound-generics >= 0.3 && < 0.5,+ lens >= 4.14 && < 5.2,+ optparse-applicative >= 0.12 && < 0.17,+ oeis >= 0.3.10++ default-language: Haskell2010++test-suite disco-tests+ import: common+ type: exitcode-stdio-1.0+ main-is: Tests.hs+ hs-source-dirs: test+ ghc-options: -threaded+ build-depends: base >= 4.7 && < 4.17,+ tasty >= 0.10 && < 1.5,+ tasty-golden >= 2.3 && < 2.4,+ directory >= 1.2 && < 1.4,+ filepath >= 1.4 && < 1.5,+ process >= 1.4 && < 1.7,+ bytestring >= 0.9 && < 0.12,+ disco+ default-language: Haskell2010++test-suite disco-examples+ import: common+ type: exitcode-stdio-1.0+ main-is: TestExamples.hs+ hs-source-dirs: example+ ghc-options: -threaded+ build-depends: base >= 4.7 && < 4.17,+ directory >= 1.2 && < 1.4,+ filepath >= 1.4 && < 1.5,+ process >= 1.4 && < 1.7+ default-language: Haskell2010
+ docs/tutorial/example/arith-pattern.disco view
@@ -0,0 +1,8 @@+h : N -> N+h(0) = 1 -- matches 0+h(2k+1) = h(k) -- matches any natural of the form 2k+1 for a natural number k+h(2k+2) = h(k+1) + h(k) -- matches any natural of the form 2k+2++isHalf : Q -> Bool+isHalf(s) = {? true when s is _ / 2, -- matches fractions with denominator 2+ false otherwise ?}
+ docs/tutorial/example/basic-ellipsis.disco view
@@ -0,0 +1,19 @@+-- Counting numbers from 1 to 100+counting : List(N)+counting = [1 .. 100]++-- Even numbers from 2 to 100+evens : List(N)+evens = [2, 4 ..... 100]++-- [5, 4, 3, ... -3, -4, -5]+down : List(Z)+down = [5 .. -5]++-- 1 + 3 + 5 + 7 = 16+s : N+s = {? a+b+c+d when [1, 3 .. 100] is (a::b::c::d::_) ?}++-- It doesn't always have to be integers+qs : List(Q)+qs = [2/3, 7/5 .. 10]
+ docs/tutorial/example/basics.disco view
@@ -0,0 +1,5 @@+approx_pi : Rational+approx_pi = 22/7++increment : N -> N+increment(n) = n + 1
+ docs/tutorial/example/case-pattern.disco view
@@ -0,0 +1,5 @@+g : Z*Z -> Z+g(p) = {? 0 when p is (3,_),+ x + y when p is (x,y) if x > 5 or y > 20,+ -100 otherwise+ ?}
+ docs/tutorial/example/case.disco view
@@ -0,0 +1,5 @@+f : Z -> Z+f(x) = {? x + 2 if x < 0,+ x^2 - 3x + 2 if 0 <= x < 10,+ 5 - x otherwise+ ?}
+ docs/tutorial/example/comment.disco view
@@ -0,0 +1,9 @@+-- This is a comment+approx_pi : Rational+approx_pi = 22/7 -- an OK approximation++-- The following function is very complicated+-- and took about three weeks to write.+-- Don't laugh.+increment : N -> N+increment(n) = n + 1 -- one more than the input
+ docs/tutorial/example/comprehension.disco view
@@ -0,0 +1,10 @@+comp1 : List(N) -> List(N) -> List(N)+comp1 xs ys = [ x + y | x in xs, 2 divides x, y in ys, 2 divides y, x + y >= 50 ]++pythagTriples : List (N*N*N)+pythagTriples = [ (a,b,c)+ | a in [1 .. 20]+ , b in [1 .. 20]+ , c in [1 .. 20]+ , a^2 + b^2 == c^2+ ]
+ docs/tutorial/example/doc.disco view
@@ -0,0 +1,14 @@+||| A reasonable approximation of pi.+approx_pi : Rational+approx_pi = 22/7 -- an OK approximation++||| Take a natural number as input, and return the natural+||| number which is one greater.+|||+||| Should not be used while operating heavy machinery.+-- This comment will be ignored.+increment : N -> N+increment(n) = n + 1++fizz : N+fizz = 1
+ docs/tutorial/example/function-desugar.disco view
@@ -0,0 +1,8 @@+gcd : N * N -> N+gcd(a,0) = a+gcd(a,b) = gcd(b, a mod b)++gcd2 : N * N -> N+gcd2 = λp. {? a when p is (a,0),+ gcd2(b, a mod b) when p is (a,b)+ ?}
+ docs/tutorial/example/function.disco view
@@ -0,0 +1,9 @@+f : N -> N+f(x) = x + 7++g : Z -> Bool+g(n) = (n - 3) > 7++factorial : N -> N+factorial(0) = 1+factorial(n) = n * factorial(n .- 1)
+ docs/tutorial/example/general-ellipsis.disco view
@@ -0,0 +1,11 @@+-- Some triangular numbers+triangular : List(N)+triangular = [1, 3, 6 .. 100]++-- Some squares+squares : List(N)+squares = [1, 4, 9 .. 100]++-- Some cubes+cubes : List(N)+cubes = [1, 8, 27, 64 .. 1000]
+ docs/tutorial/example/higher-order.disco view
@@ -0,0 +1,2 @@+thrice : (N -> N) -> (N -> N)+thrice(f)(n) = f(f(f(n)))
+ docs/tutorial/example/let.disco view
@@ -0,0 +1,6 @@+f : Nat -> List(Nat)+f n =+ let x : Nat = n//2,+ y : Nat = x + 3,+ z : List(Nat) = [3,x,y]+ in n :: z
+ docs/tutorial/example/list.disco view
@@ -0,0 +1,17 @@+emptyList : List(Bool)+emptyList = []++nums : List(N)+nums = [1, 3, 4, 6]++nums2 : List(N)+nums2 = 1 :: 3 :: 4 :: 6 :: []++ -- nums and nums2 are equal++nested : List(List(Q))+nested = [1, 5/2, -8] :: [[2, 4], [], [1/2]]++sum : List(N) -> N+sum [] = 0+sum (n :: ns) = n + sum ns
+ docs/tutorial/example/multi-arg-functions.disco view
@@ -0,0 +1,9 @@+gcd : N * N -> N+gcd(a,0) = a+gcd(a,b) = gcd(b, a mod b)++discrim : Q * Q * Q -> Q+discrim(a,b,c) = b^2 - 4*a*c++manhattan : (Q*Q) * (Q*Q) -> Q+manhattan ((x1,y1), (x2,y2)) = abs (x1-x2) + abs (y1-y2)
+ docs/tutorial/example/pair.disco view
@@ -0,0 +1,11 @@+pair1 : N * Q+pair1 = (3, -5/6)++pair2 : Z × Bool+pair2 = (17 + 22, (3,5) < (4,2))++pair3 : Bool * (Bool * Bool)+pair3 = (true, (false, true))++pair4 : Bool * Bool * Bool+pair4 = (true, false, true)
+ docs/tutorial/example/poly.disco view
@@ -0,0 +1,3 @@+maplist : (a -> b) -> List(a) -> List(b)+maplist _ [] = []+maplist f (a :: as) = f a :: (maplist f as)
+ docs/tutorial/example/property.disco view
@@ -0,0 +1,21 @@+!!! ∀ x:Bool. neg (neg x) == x+neg : Bool -> Bool+neg x = not x++!!! ∀p: N + N. plusIsoR (plusIso p) == p+plusIso : N + N -> N+plusIso (left n) = 2n+plusIso (right n) = 2n + 1++!!! ∀n:N. plusIso (plusIsoR n) == n+plusIsoR : N -> N + N+plusIsoR n =+ {? left (n // 2) if 2 divides n+ , right (n // 2) otherwise+ ?}++!!! forall x:N, y:N, z:N.+ f(f(x,y), z) == f(x, f(y,z))++f : N*N -> N+f (x,y) = x + x*y + y
+ docs/tutorial/example/sum.disco view
@@ -0,0 +1,8 @@+sum1 : N + Bool+sum1 = left(3)++sum2 : N + Bool+sum2 = right(false)++sum3 : N + N + N+sum3 = right(right(3))
+ docs/tutorial/example/tydefs-poly.disco view
@@ -0,0 +1,13 @@+import list++type Tree(a) = Unit + (a * Tree(a) * Tree(a))++treeFold : b * (a * b * b -> b) * Tree(a) -> b+treeFold (z, f, left(unit)) = z+treeFold (z, f, right (a, l, r)) = f(a, treeFold(z,f,l), treeFold(z,f,r))++sumTree : Tree(Nat) -> Nat+sumTree(t) = treeFold(0, \(a,l,r). a+l+r, t)++flattenTree : Tree(a) -> List(a)+flattenTree(t) = treeFold([], \(a,l,r). append(l, append([a], r)), t)
+ docs/tutorial/example/tydefs.disco view
@@ -0,0 +1,11 @@+type Triplet = (N * N * N)++sumTripletList : List (N * N * N) -> N+sumTripletList [] = 0+sumTripletList ((n1, n2, n3) :: rest) = (n1 + n2 + n3 + (sumTripletList rest))++type Tree = Unit + (N * Tree * Tree)++sumTree : Tree -> N+sumTree (left _) = 0+sumTree (right (n, l, r)) = n + sumTree(l) + sumTree(r)
+ docs/tutorial/example/unit-test.disco view
@@ -0,0 +1,8 @@+!!! gcd(7,6) == 1+!!! gcd(12,18) == 6+!!! gcd(0,0) == 0++gcd : N * N -> N+gcd(a,0) = a+gcd(a,b) = gcd(b, a mod b)+
+ example/TestExamples.hs view
@@ -0,0 +1,25 @@+module Main where++import System.Directory+import System.Exit+import System.FilePath+import System.Process++import Control.Monad++exampleDirs :: [FilePath]+exampleDirs = ["example", "docs/tutorial/example", "lib"]++main :: IO ()+main = mapM_ checkDir exampleDirs++checkDir :: FilePath -> IO ()+checkDir dir = do+ examples <- filter ((== ".disco") . takeExtension) <$> getDirectoryContents dir+ res <- and <$> mapM (checkExample . (dir </>)) examples+ when (not res) $ exitFailure++checkExample :: FilePath -> IO Bool+checkExample exampleFile = do+ ex <- system ("disco --check " ++ exampleFile)+ return (ex == ExitSuccess)
+ example/abs.disco view
@@ -0,0 +1,13 @@+||| This was an example of how you could implement an absolute value+||| function with the type Z -> N even if it is not built into the+||| language. The problem is that it is rather inefficient: since it+||| builds up the answer by adding 1 repeatedly, it takes time+||| proportional to its result. absolute value is now built into disco+||| so this is here just as a curiosity.++abs : Z -> N+abs x =+ {? abs (-x) if x < 0,+ 0 when x is 0,+ 1 + abs (x-1) otherwise+ ?}
+ example/catalan.disco view
@@ -0,0 +1,19 @@+import list+import oeis++-- The type of binary tree shapes: empty tree, or a pair of subtrees.+type BT = Unit + BT*BT++-- Generate the list of all binary tree shapes of a given size.+treesOfSize : N -> List(BT)+treesOfSize(0) = [left(■)]+treesOfSize(k+1) =+ [ right (l,r) | x <- [0 .. k], l <- treesOfSize(x), r <- treesOfSize(k .- x) ]++-- Compute first few Catalan numbers by brute force.+catalan1 : List(N)+catalan1 = each(\k. length(treesOfSize(k)), [0..4])++-- Extend the sequence via the OEIS.+catalan : List(N)+catalan = extendSequence(catalan1)
+ example/demo.disco view
@@ -0,0 +1,29 @@+f : Z -> Z+f x = x - 3++sum : List(Z) -> Z+sum [] = 0+sum (x::xs) = x + sum xs++g : N -> N+g (2n) = n+g (2n+1) = n++q : N -> N+q x =+ {? 3 if x < 9+ , 17 if 10 <= x < 22+ , 99 otherwise+ ?}++type S = Unit + Char × S++len : S -> N+len (left(unit)) = 0+len (right(_, s)) = 1 + len s++type P(a) = a + P(a) * P(a)++height : P(a) -> N+height (left(_)) = 0+height (right(l, r)) = 1 + (height l) max (height r)
+ example/gcd.disco view
@@ -0,0 +1,22 @@+||| The greatest common divisor of two natural numbers.+|||+||| If we take the word "greatest" to refer to the usual less-than+||| relation on natural numbers, then gcd(0,0) would be undefined:+||| every natural number evenly divides 0, and there is no greatest+||| natural number. However, we should instead think of the+||| divisibility relation: gcd is really the meet (greatest lower+||| bound) in the divisibility lattice on the natural numbers. That+||| is, gcd(a,b) = d if for every d' such that d' evenly divides both+||| a and b, we have that d' also evenly divides (NOT "is less than"!)+||| d. Under this definition, gcd(0,0) is perfectly well defined and+||| equal to 0. 0 is in fact the "greatest" natural number under the+||| divisibility relation, because it is divisible by every natural+||| number.++!!! gcd(7,6) == 1+!!! gcd(12,18) == 6+!!! gcd(0,0) == 0++gcd : N * N -> N+gcd(a,0) = a+gcd(a,b) = gcd(b, a mod b)
+ example/grid.disco view
@@ -0,0 +1,75 @@+-- Some isomorphisms between ℕ and ℕ×ℕ.++||| An isomorphism between ℕ and ℕ×ℕ, counting off "by squares", like this:+|||+||| 0 3 8+||| 1 2 7+||| 4 5 6+|||+||| and so on, where the first column contains the square numbers.+||| sqIso' is the inverse.++!!! ∀ n : Nat. sqIso (sqIso' n) == n++sqIso : ℕ×ℕ → ℕ+sqIso (x,y) =+ {? y^2 + x if x <= y,+ (x+1)^2 .- 1 .- y otherwise+ ?}++||| Inverse direction of the square isomorphism.++!!! ∀ p : Nat*Nat. sqIso' (sqIso p) == p++sqIso' : ℕ → ℕ×ℕ+sqIso' n =+ let r = sqrt n+ in {? (n .- r^2, r) if n <= r^2 + r,+ (r, (r+1)^2 .- 1 .- n) otherwise+ ?}++||| The classic "diagonal" isomorphism:+|||+||| 0 2 5+||| 1 4+||| 3+|||+||| where the first column contains the triangular numbers.++!!! ∀ n : Nat. diagIso (diagIso' n) == n++diagIso : ℕ×ℕ → ℕ+diagIso (x,y) = (x+y)*(x+y+1)//2 + x++diagIso' : ℕ → ℕ×ℕ+diagIso' n =+ let d = (sqrt(1 + 8n) .- 1)//2 : N+ in let t = d*(d+1)//2+ in (n .- t, d .- (n .- t))+++||| Every POSITIVE n can be decomposed into a power of two times an+||| odd number, n = 2^x (2y + 1). This creates an isomorphism n <->+||| (x,y). This is actually an isomorphism between {n | n : ℕ, n > 0}+||| and ℕ×ℕ, but at the moment the disco type system doesn't let us+||| say that (and it likely never will).++-- We have to be careful not to call powerIso' 0 because it gets stuck+-- in infinite recursion! One way that would work is to write the+-- test as follows:++!!! forall n:Nat. powerIso (powerIso' (n+1)) == (n+1)++-- Alternatively, since 'implies' is lazy (i.e. "short-circuiting"),+-- we can write++!!! ∀ n : Nat. (n > 0) ==> powerIso (powerIso' n) == n++powerIso : ℕ×ℕ → ℕ+powerIso (x,y) = 2^x * (2y + 1)++powerIso' : ℕ → ℕ×ℕ+powerIso' n =+ {? (0, n//2) if not (2 divides n),+ (x+1,y) when powerIso' (n//2) is (x,y)+ ?}
+ example/lists.disco view
@@ -0,0 +1,12 @@+iterateP : (a → a) → a → List(a)+iterateP f p = p :: iterateP f (f p)++fib2_helper : ℕ×ℕ → ℕ×ℕ+fib2_helper (a,b) = (b,a+b)++indexP : ℕ -> List(a) -> a+indexP 0 (p::_) = p+indexP (n+1) (_::l') = indexP n l'++fib2 : ℕ → ℕ+fib2 n = {? x when (indexP n (iterateP fib2_helper (0,1))) is (x,_) ?}
+ example/logic.disco view
@@ -0,0 +1,20 @@+-- Basic logical operators++lnot1 : Bool -> Bool+lnot1 true = false+lnot1 false = true++lnot2 : Bool -> Bool+lnot2 x =+ {? false if x,+ true otherwise+ ?}++implication : Bool -> Bool -> Bool+implication x y =+ {? false if x and not y,+ true otherwise+ ?}++exor : Bool -> Bool -> Bool+exor x y = (x && not y) || (not x && y)
+ example/nim.disco view
@@ -0,0 +1,66 @@+import list++I : Bool+I = true++O : Bool+O = false++||| Convert a natural number into a list of bits, with the *least*+||| significant bit first.++!!! toBinary 0 == ([] : List(Bool))+!!! toBinary 1 == ([I] : List(Bool))+!!! toBinary 2 == ([O,I] : List(Bool))+!!! toBinary 534 == ([O,I,I,O,I,O,O,O,O,I] : List(Bool))+toBinary : N -> List(Bool)+toBinary 0 = []+toBinary n =+ {? O :: toBinary (n // 2) if 2 divides n+ , I :: toBinary (n // 2) otherwise+ ?}++||| Convert a list of bits (with the least significant bit first) back+||| into a natural number. Left inverse of toBinary.++!!! fromBinary [O,O,I,I] == 12+!!! fromBinary [O,O,O,O] == 0+!!! ∀ n : N. fromBinary (toBinary n) == n++fromBinary : List(Bool) -> N+fromBinary [] = 0+fromBinary (false :: b) = 2 * fromBinary b+fromBinary (true :: b) = 1 + 2 * fromBinary b++xorB : Bool * Bool -> Bool+xorB = ~/=~++xor : List(Bool) * List(Bool) -> List(Bool)+xor([], bs) = bs+xor(bs, []) = bs+xor(a::as, b::bs) = (xorB(a,b)) :: xor(as, bs)++xorN : N*N -> N+xorN(a, b) = fromBinary (xor(toBinary a, toBinary b))++nimSum : List(N) -> N+nimSum ns = fromBinary(reduce(xor, [], each(toBinary, ns)))++xorPile : N -> List(N) -> List(N)+xorPile _ [] = []+xorPile x (n :: ns)+ = {? xorN(x, n) :: ns if xorN(x, n) < n+ , n :: xorPile x ns otherwise+ ?}++||| Perform the optimal nim move, or report that the position is a+||| losing position.++!!! nimMove [1,5,8] == (right [1,5,4] : Unit + List(N))+nimMove : List(N) -> Unit + List(N)+nimMove ls =+ let s = nimSum ls+ , ls' = xorPile s ls+ in {? left unit if ls == ls'+ , right ls' otherwise+ ?}
+ example/prime.disco view
@@ -0,0 +1,53 @@+-- Primality test+--+-- Taken from Jan van Eijck, "The Haskell Road to Logic, Maths, and Programming", 2nd+-- Edition, pp. 4--11++||| ldf k n calculates the least divisor of n that is at least k and+||| at most sqrt n. If no such divisor exists, then it returns n.++!!! ldf 2 10 == 2+!!! ldf 3 10 == 10+!!! ldf 2 25 == 5+!!! ldf 5 25 == 5+!!! ldf 6 25 == 25+!!! forall k:N, m:N. let n = k+m in k <= ldf k n <= n+!!! forall k:N, n:N. (ldf k n) divides n++ldf : N -> N -> N+ldf k n =+ {? k if k divides n,+ n if k^2 > n,+ ldf (k+1) n otherwise+ ?}+++||| ld n calculates the least nontrivial divisor of n, or returns n if+||| n has no nontrivial divisors.++!!! ld 14 == 2+!!! ld 15 == 3+!!! ld 16 == 2+!!! ld 17 == 17+!!! ld 25 == 5+!!! forall n:N. (ld n) divides n++ld : N -> N+ld = ldf 2++||| Tests whether n is prime or not.++!!! not (isPrime 0)+!!! not (isPrime 1)+!!! isPrime 2+!!! isPrime 3+!!! not (isPrime 4)+!!! isPrime 5+!!! not (isPrime 91)+!!! isPrime 113++isPrime : N -> Bool+isPrime n =+ {? false if n <= 1,+ ld n == n otherwise+ ?}
+ example/prog.disco view
@@ -0,0 +1,57 @@+f : (N -> N) -> N * N -> N -> Z+f g (x,y) z = x + g y - z -- here g y is function application++-- This used to be allowed, but now function application is+-- *syntactically* disambiguated from multiplication. 'g y' must be+-- function application because the left-hand term is a variable.+-- q : ℕ → ℕ×ℕ → ℕ → ℤ+-- q g (x,y) z = x + g y - z -- here g y is multiplication+++||| A naive implementation of the fibonacci function.+!!! fib 0 == 0+!!! fib 1 == 1+!!! fib 2 == 1+!!! fib 5 == 5+!!! fib 12 == 144+fib : Nat -> Nat -- a top-level recursive function+fib n =+ {? n when+ n+ is 0+ , n when n is 1 -- comment+ , fib (n.-1) + fib (n.-2) otherwise+ -- note we can't write+ -- fib (n-1) + fib (n-2) otherwise+ -- since that doesn't pass the type checker: it doesn't believe+ -- that (n-1) and (n-2) are natural numbers.+ ?}++-- Mutually recursive functions. The order of declarations and+-- definitions does not matter.+isEven : N -> Bool+isOdd : N -> Bool++-- We can either write a definition explicitly using a case...+isEven n =+ {? true when n is 0+ , isOdd m when n is m+1+ ?}++-- Or we can directly define by cases like this (which is just syntax+-- sugar for something like the former).+isOdd 0 = false+isOdd (m+1) = isEven m++-- Again, here are two equivalent definitions of fact using the two+-- different styles.++fact : N -> N+fact n =+ {? 1 when n is 0,+ n * fact m when n is m+1+ ?}++fact2 : N -> N+fact2 0 = 1+fact2 (m+1) = (m + 1) * fact2 m
+ example/prop.disco view
@@ -0,0 +1,4 @@++!!! ∀ x : N, y : N. plus x y == plus y x+plus : N -> N -> N+plus x y = x + y
+ example/rsa.disco view
@@ -0,0 +1,73 @@+import num+import list++-- Implementation of RSA encryption algorithm.+-- Reference: https://simple.wikipedia.org/wiki/RSA_(algorithm)++-- To use, first call `getKeys` with two prime numbers, which returns+-- two pairs. The first pair is the public key, the second is the+-- private key. These keys, along with the `encrypt` and `decrypt`+-- functions can be used to encrypt and decrypt lists of natural+-- numbers.++encrypt : N * N -> List(N) -> List(N)+encrypt key xs = each (encrypt1 key, xs)++decrypt : N * N -> List(N) -> List(N)+decrypt = encrypt++-- takes two primes, returns a pair of pairs containing the RSA public/private keys+-- prime -> prime -> (public key, private key)+getKeys : N -> N -> (N * N) * (N * N)+getKeys p1 p2 =+ let m = p1 * p2,+ totient = (p1 .- 1)*(p2 .- 1),+ e = getPubExp 2 totient+ in ((m, e), (m, getPrivExp e totient))++-- guess -> totient -> e+getPubExp : N -> N -> N+getPubExp e totient =+ {? e if gcd(e, totient) == 1+ , getPubExp (e+1) totient otherwise+ ?}++gcd : N*N -> N+gcd (a, 0) = a+gcd (a, b) = gcd (b, a mod b)++getPrivExp : N -> N -> N+getPrivExp e totient =+ let t = inverse (0,1) (totient,e)+ in {? abs t if t>=0+ , abs (t+totient) otherwise+ ?}+++-- Implemented using Extended Euclidean Algorithm (reference:+-- https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Computing_multiplicative_inverses_in_modular_structures)+inverse : (Z * Z) -> (Z * Z) -> Z+inverse (t,newt) (r,newr) =+ {? t if newr==0+ , let q = r // newr in (inverse (newt, t-q*newt) (newr,r-q*newr)) otherwise+ ?}++-- encrypt1 : msg -> public key (mod,exp) -> encrypted msg+-- encrypts one single number+encrypt1 : Nat * Nat -> Nat -> Nat+encrypt1 (m, e) msg = modPower msg e m++-- decrypts one single number+decrypt1 : Nat * Nat -> Nat -> Nat+decrypt1 = encrypt1++-- modPower : n -> power -> modulus -> nat+-- Exponentiating by squaring algorithm reference:+-- https://simple.wikipedia.org/wiki/Exponentiation_by_squaring+modPower : Nat -> Nat -> Nat -> Nat+modPower n p m =+ {? 1 if p==0+ , n % m if p==1+ , (modPower (n^2) (p//2) m) % m if (even p)+ , (n * (modPower (n^2) (p//2) m)) % m if (p>2) && (odd p)+ ?}
+ example/sums.disco view
@@ -0,0 +1,7 @@+foo : List(N) + Bool -> N+foo x =+ {? n when x is left (n :: _),+ 0 when x is left [],+ 1 when x is right True,+ 2 when x is right False+ ?}
+ example/tree.disco view
@@ -0,0 +1,23 @@+type Tree = Unit + N * Tree * Tree++leaf : Tree+leaf = left(■)++node : N * Tree * Tree -> Tree+node(x, l, r) = right(x, l, r)++tree1 : Tree+tree1 = node(3, node(5, node(1, leaf, leaf), leaf), node(6, node(2, leaf, leaf), node(8, leaf, leaf)))++treeFold : r * (N * r * r -> r) * Tree -> r+treeFold(x, f, left(■)) = x+treeFold(x, f, right(n,l,r)) = f(n, treeFold(x, f, l), treeFold(x, f, r))++treeSum : Tree -> N+treeSum(t) = treeFold(0, \(x,l,r). x + l + r, t)++treeSize : Tree -> N+treeSize(t) = treeFold(0, \(x,l,r). 1 + l + r, t)++treeHeight : Tree -> N+treeHeight(t) = treeFold(0, \(x,l,r). 1 + l max r, t)
+ lib/container.disco view
@@ -0,0 +1,13 @@+using NoStdLib++import list+import product++reducebag : (a × a → a) × a × Bag(a) → a+reducebag(f,z,b) = foldr(f,z,list(b))++reduceset : (a × a → a) × a × Set(a) → a+reduceset(f,z,s) = foldr(f,z,list(s))++unions : Set(Set(a)) → Set(a)+unions(ss) = foldr(~∪~, {}, list(ss))
+ lib/graph.disco view
@@ -0,0 +1,58 @@+search : N -> Graph(N) -> N -> Bool * Set(N)+search target g pos = {?+ (search' target g pos {}) if (contains g pos),+ (False, {}) otherwise+ ?}++search' : N -> Graph(N) -> N -> Set(N) -> Bool * Set(N)+search' target g pos visited = {?+ (True, visited union {pos}) if (pos == target && contains g pos),+ (False,visited) if pos ∈ visited,+ sequence target g pos (list (neighbors g pos)) (visited union {pos}) otherwise+ ?}++sequence : N -> Graph(N) -> N -> List(N) -> Set(N) -> Bool * Set(N)+sequence target g pos [] visited = (False, visited)+sequence target g pos (a::as) visited =+ {? {?+ (successful, v') if successful,+ sequence target g pos as v' otherwise+ ?} when search' target g a visited is (successful, v')+ ?}++searchDAG : N -> Graph(N) -> N -> Bool+searchDAG target g pos = (pos == target && contains g pos) || (true ∈ (each (searchDAG target g, neighbors g pos)))++contains : Graph(N) -> N -> Bool+contains g a = {?+ True when lookup (a,summary g) is right _,+ False otherwise+ ?}++neighbors : Graph(N) -> N -> Set(N)+neighbors g pos = {?+ s when lookup (pos, summary g) is right s,+ {} otherwise+ ?}++vertices : Graph(N) -> List(N)+vertices g = each ((\(k,_). k), (list (mapToSet (summary g))))++topsort : Graph(N) -> Unit + List(N) * Set(N)+topsort g = downstream g (vertices g) {} {} []++searchFrom' : Graph(N) -> N -> Set(N) -> Set(N) -> List(N) -> Unit + List(N) * Set(N)+searchFrom' g pos visited locked stack = {?+ left(unit) if pos ∈ locked,+ right (stack, visited) if pos ∈ visited,+ right (pos :: s', v' union {pos}) when downstream g (list (neighbors g pos)) visited (locked union {pos}) stack is right (s', v'),+ left(unit) otherwise+ ?}++downstream : Graph(N) -> List(N) -> Set(N) -> Set(N) -> List(N) -> Unit + List(N) * Set(N)+downstream g [] visited locked stack = right (stack, visited)+downstream g (a::as) visited locked stack =+ {?+ downstream g as v' locked s' when searchFrom' g a visited locked stack is right (s',v'),+ left(unit) otherwise+ ?}
+ lib/list.disco view
@@ -0,0 +1,72 @@+using NoStdLib++||| A right fold for lists.+||| foldr(f, z, [a,b,c]) = f(a, f(b, f(c, z)))++!!! foldr(~+~, 0, [1,2,3]) == 6+!!! foldr(~+~, 0, []) == 0++foldr : (a × b → b) × b × List(a) → b+foldr(f, z, [] ) = z+foldr(f, z, x::xs) = f(x, foldr(f, z, xs))++||| Append two lists into a single list.++!!! append([], []) == []+!!! append([1,2,3], []) == [1,2,3]+!!! append([1,2,3], [4,5,6]) == [1,2,3,4,5,6]+!!! ∀ xs : List(N). append([], xs) == xs+!!! forall xs : List(N). append(xs, []) == xs++append : List(a) × List(a) → List(a)+append([], ys) = ys+append(x::xs, ys) = x :: append(xs, ys)++||| Flatten a list of lists into a single list.++!!! concat [[1,2],[3],[],[4,5,6]] == [1,2,3,4,5,6]++concat : List(List(a)) → List(a)+concat [] = []+concat (l::ls) = append(l, concat ls)++||| Apply a function to each element of a list, returning a new list+||| of the results. Note, this is here just for illustration+||| purposes; it is much more efficient to use the builtin primitive+||| 'each' function (which also works on bags and sets).++!!! eachlist(\x.x+1, [] ) == []+!!! eachlist(\x.2, "hello" ) == [2,2,2,2,2]+!!! eachlist(\x. 5x, [2,4,1,7]) == [10,20,5,35]++eachlist : (a → b) × List(a) → List(b)+eachlist(f, []) = []+eachlist(f, x::xs) = f(x) :: eachlist(f, xs)++||| Take the first n elements of a list.+!!! take(1, [true, false, true]) == [true]+!!! take(3, [true, false]) == [true, false]+!!! take(0, [true, false]) == ([] : List(Bool))++take : ℕ × List(a) → List(a)+take(0, _) = []+take(_, []) = []+take(n+1, x :: xs) = x :: take(n, xs)++||| The length of a list.+!!! length [true, false, true] == 3+length : List(a) → ℕ+length [] = 0+length (_::l) = 1 + length l++zipWith : (a × b → c) × List(a) × List(b) -> List(c)+zipWith(_, [], _) = []+zipWith(_, _, []) = []+zipWith(f, a::as, b::bs) = f(a, b) :: zipWith(f, as, bs)++filterlist : (a -> Bool) × List(a) -> List(a)+filterlist(_, []) = []+filterlist(p, a :: as) =+ {? a :: filterlist(p, as) if p a+ , filterlist(p, as) otherwise+ ?}
+ lib/num.disco view
@@ -0,0 +1,47 @@+using Primitives++||| Test whether the given natural number is prime.+!!! not (isPrime 0)+!!! not (isPrime 1)+!!! isPrime 2+!!! isPrime 3+!!! isPrime 1000000007+isPrime : N -> Bool+isPrime = $isPrime++||| Takes naturals b and n. Calculates the base b logarithm of n rounded down+!!! log (1,1) == 0+!!! log (3,1) == 0+!!! log (2, 1024) == 10+!!! log (2, 1023) == 9+!!! log (7, 11398895185373143) == 19+log : N * N -> N+log (1,1) = 0+log (0,_) = $crash "Log base 0 is undefined"+log (1,_) = $crash "Log base 1 is undefined for inputs other than 1"+log (_,0) = $crash "Log of Zero"+log (b, x) = {?+ 0 if b > x,+ 1 + log (b, x // b) otherwise+ ?}++||| Calculate the base 2 logarithm of the given natural number rounded down+!!! lg 4 == 2+!!! lg 5 == 2+!!! lg 7 == 2+!!! lg 10 == 3+!!! lg 25 == 4+!!! lg 99887766554433221100 == 66+!!! lg (2^100 + 1) == 100+lg : N -> N+lg x = log (2,x)++||| Compute the prime factorization of the given natural number.+factor : N -> Bag(N)+factor = $factor++even : Z -> Bool+even x = 2 divides x++odd : Z -> Bool+odd x = ¬(2 divides x)
+ lib/oeis.disco view
@@ -0,0 +1,13 @@+using Primitives++||| Look up a sequence of integers using https://oeis.org+!!! lookupSequence [] == left(unit)+!!! lookupSequence [1,1,2,3] == right "https://oeis.org/A000045"+lookupSequence : List(N) -> Unit + List(Char)+lookupSequence = $lookupSequence++||| Extend a known sequence of integers with data from https://oeis.org+!!! extendSequence [] == []+!!! extendSequence [1,1,2,3,5] == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155]+extendSequence : List(N) -> List(N)+extendSequence = $extendSequence
+ lib/prim.disco view
@@ -0,0 +1,4 @@+using Primitives++crash : List(Char) -> a+crash = $crash
+ lib/product.disco view
@@ -0,0 +1,7 @@+using NoStdLib++fst : (a × b) → a+fst (a,_) = a++snd : (a × b) → b+snd (_,b) = b
+ lib/prop.disco view
@@ -0,0 +1,16 @@+using Primitives++||| Decide whether a proposition holds, *i.e.* convert it to a boolean.+||| Note, however, that for some propositions this may run forever.+||| For example, `holds (forall (x:N). x >= 0)` will run forever+||| trying to check all natural numbers to see if they are all+||| nonnegative.+|||+||| If you want to use random sampling to check whether a proposition+||| *probably* holds, the `:test` command at the REPL will give you a+||| best-effort answer. For instance, `:test (forall x:N. x >= 0)` will+||| happily report that every natural number it tried was nonnegative.++!!! holds (true : Prop) == true+holds : Prop -> Bool+holds = $holds
+ repl/REPL.hs view
@@ -0,0 +1,16 @@+-----------------------------------------------------------------------------+-- |+-- Module : REPL+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- A text-based REPL for disco.+--+-----------------------------------------------------------------------------++import Disco.Interactive.CmdLine++main :: IO ()+main = discoMain
+ src/Disco/AST/Core.hs view
@@ -0,0 +1,390 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE NondecreasingIndentation #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UndecidableInstances #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.AST.Core+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Abstract syntax trees representing the desugared, untyped core+-- language for Disco.+-----------------------------------------------------------------------------++module Disco.AST.Core+ ( -- * Core AST+ RationalDisplay(..)+ , Core(..)+ , Op(..), opArity, substQC, substsQC+ )+ where++import Control.Lens.Plated+import Data.Data (Data)+import Data.Data.Lens (uniplate)+import qualified Data.Set as S+import GHC.Generics+import Prelude hiding ((<>))+import qualified Prelude as P+import Unbound.Generics.LocallyNameless hiding (LFresh, lunbind)++import Disco.Effects.LFresh+import Polysemy (Members, Sem)+import Polysemy.Reader++import Data.Ratio+import Disco.AST.Generic (Side, selectSide)+import Disco.Names (QName)+import Disco.Pretty+import Disco.Types++-- | A type of flags specifying whether to display a rational number+-- as a fraction or a decimal.+data RationalDisplay = Fraction | Decimal+ deriving (Eq, Show, Generic, Data, Ord, Alpha)++instance Semigroup RationalDisplay where+ Decimal <> _ = Decimal+ _ <> Decimal = Decimal+ _ <> _ = Fraction++-- | The 'Monoid' instance for 'RationalDisplay' corresponds to the+-- idea that the result should be displayed as a decimal if any+-- decimal literals are used in the input; otherwise, the default is+-- to display as a fraction. So the identity element is 'Fraction',+-- and 'Decimal' always wins when combining.+instance Monoid RationalDisplay where+ mempty = Fraction+ mappend = (P.<>)++-- | AST for the desugared, untyped core language.+data Core where+ -- | A variable.+ CVar :: QName Core -> Core+ -- | A rational number.+ CNum :: RationalDisplay -> Rational -> Core+ -- | A built-in constant.+ CConst :: Op -> Core+ -- | An injection into a sum type, i.e. a value together with a tag+ -- indicating which element of a sum type we are in. For example,+ -- false is represented by @CSum L CUnit@; @right(v)@ is+ -- represented by @CSum R v@. Note we do not need to remember+ -- which type the constructor came from; if the program+ -- typechecked then we will never end up comparing constructors+ -- from different types.+ CInj :: Side -> Core -> Core+ -- | A primitive case expression on a value of a sum type.+ CCase :: Core -> Bind (Name Core) Core -> Bind (Name Core) Core -> Core+ -- | The unit value.+ CUnit :: Core+ -- | A pair of values.+ CPair :: Core -> Core -> Core+ -- | A projection from a product type, i.e. @fst@ or @snd@.+ CProj :: Side -> Core -> Core+ -- | An anonymous function.+ CAbs :: Bind [Name Core] Core -> Core+ -- | Function application.+ CApp :: Core -> Core -> Core+ -- | A "test frame" under which a test case is run. Records the+ -- types and legible names of the variables that should+ -- be reported to the user if the test fails.+ CTest :: [(String, Type, Name Core)] -> Core -> Core+ -- | A type.+ CType :: Type -> Core+ -- | Introduction form for a lazily evaluated value of type Lazy T+ -- for some type T. We can have multiple bindings to multiple+ -- terms to create a simple target for compiling mutual recursion.+ CDelay :: Bind [Name Core] [Core] -> Core+ -- | Force evaluation of a lazy value.+ CForce :: Core -> Core+ deriving (Show, Generic, Data, Alpha)++instance Plated Core where+ plate = uniplate++-- | Operators that can show up in the core language. Note that not+-- all surface language operators show up here, since some are+-- desugared into combinators of the operators here.+data Op+ = -- | Addition (@+@)+ OAdd+ | -- | Arithmetic negation (@-@)+ ONeg+ | -- | Integer square root (@sqrt@)+ OSqrt+ | -- | Floor of fractional type (@floor@)+ OFloor+ | -- | Ceiling of fractional type (@ceiling@)+ OCeil+ | -- | Absolute value (@abs@)+ OAbs+ | -- | Multiplication (@*@)+ OMul+ | -- | Division (@/@)+ ODiv+ | -- | Exponentiation (@^@)+ OExp+ | -- | Modulo (@mod@)+ OMod+ | -- | Divisibility test (@|@)+ ODivides+ | -- | Multinomial coefficient (@choose@)+ OMultinom+ | -- | Factorial (@!@)+ OFact+ | -- | Equality test (@==@)+ OEq+ | -- | Less than (@<@)+ OLt+ | -- Type operators++ -- | Enumerate the values of a type.+ OEnum+ | -- | Count the values of a type.+ OCount+ | -- Container operations++ -- | Size of two sets (@size@)+ OSize+ | -- | Power set/bag of a given set/bag+ -- (@power@).+ OPower+ | -- | Set/bag element test.+ OBagElem+ | -- | List element test.+ OListElem+ | -- | Map a function over a bag. Carries the+ -- output type of the function.+ OEachBag+ | -- | Map a function over a set. Carries the+ -- output type of the function.+ OEachSet+ | -- | Filter a bag.+ OFilterBag+ | -- | Merge two bags/sets.+ OMerge+ | -- | Bag join, i.e. union a bag of bags.+ OBagUnions+ | -- | Adjacency List of given graph+ OSummary+ | -- | Empty graph+ OEmptyGraph+ | -- | Construct a vertex with given value+ OVertex+ | -- | Graph overlay+ OOverlay+ | -- | Graph connect+ OConnect+ | -- | Map insert+ OInsert+ | -- | Map lookup+ OLookup+ | -- Ellipses++ -- | Continue until end, @[x, y, z .. e]@+ OUntil+ | -- Container conversion++ -- | set -> list conversion (sorted order).+ OSetToList+ | -- | bag -> set conversion (forget duplicates).+ OBagToSet+ | -- | bag -> list conversion (sorted order).+ OBagToList+ | -- | list -> set conversion (forget order, duplicates).+ OListToSet+ | -- | list -> bag conversion (forget order).+ OListToBag+ | -- | bag -> set of counts+ OBagToCounts+ | -- | set of counts -> bag+ OCountsToBag+ | -- | Map k v -> Set (k × v)+ OMapToSet+ | -- | Set (k × v) -> Map k v+ OSetToMap+ | -- Number theory primitives++ -- | Primality test+ OIsPrime+ | -- | Factorization+ OFactor+ | -- | Turn a rational into a (num, denom) pair+ OFrac+ | -- Propositions++ -- | Universal quantification. Applied to a closure+ -- @t1, ..., tn -> Prop@ it yields a @Prop@.+ OForall [Type]+ | -- | Existential quantification. Applied to a closure+ -- @t1, ..., tn -> Prop@ it yields a @Prop@.+ OExists [Type]+ | -- | Convert Prop -> Bool via exhaustive search.+ OHolds+ | -- | Flip success and failure for a prop.+ ONotProp+ | -- | Equality assertion, @=!=@+ OShouldEq Type+ | -- Other primitives++ -- | Error for non-exhaustive pattern match+ OMatchErr+ | -- | Crash with a user-supplied message+ OCrash+ | -- | No-op/identity function+ OId+ | -- | Lookup OEIS sequence+ OLookupSeq+ | -- | Extend a List via OEIS+ OExtendSeq+ deriving (Show, Generic, Data, Alpha, Eq, Ord)++-- | Get the arity (desired number of arguments) of a function+-- constant. A few constants have arity 0; everything else is+-- uncurried and hence has arity 1.+opArity :: Op -> Int+opArity OEmptyGraph = 0+opArity OMatchErr = 0+opArity _ = 1++substQC :: QName Core -> Core -> Core -> Core+substQC x s = transform $ \case+ CVar y+ | x == y -> s+ | otherwise -> CVar y+ t -> t++substsQC :: [(QName Core, Core)] -> Core -> Core+substsQC xs = transform $ \case+ CVar y -> case P.lookup y xs of+ Just c -> c+ _ -> CVar y+ t -> t++instance Pretty Core where+ pretty = \case+ CVar qn -> pretty qn+ CNum _ r+ | denominator r == 1 -> text (show (numerator r))+ | otherwise -> text (show (numerator r)) <> "/" <> text (show (denominator r))+ CApp (CConst op) (CPair c1 c2)+ | isInfix op -> parens (pretty c1 <+> text (opToStr op) <+> pretty c2)+ CApp (CConst op) c+ | isPrefix op -> text (opToStr op) <> pretty c+ | isPostfix op -> pretty c <> text (opToStr op)+ CConst op -> pretty op+ CInj s c -> withPA funPA $ selectSide s "left" "right" <+> rt (pretty c)+ CCase c l r -> do+ lunbind l $ \(x, lc) -> do+ lunbind r $ \(y, rc) -> do+ "case" <+> pretty c <+> "of {"+ $+$ nest 2 (+ vcat+ [ withPA funPA $ "left" <+> rt (pretty x) <+> "->" <+> pretty lc+ , withPA funPA $ "right" <+> rt (pretty y) <+> "->" <+> pretty rc+ ])+ $+$ "}"+ CUnit -> "unit"+ CPair c1 c2 -> setPA initPA $ parens (pretty c1 <> ", " <> pretty c2)+ CProj s c -> withPA funPA $ selectSide s "fst" "snd" <+> rt (pretty c)+ CAbs lam -> withPA initPA $ do+ lunbind lam $ \(xs, body) -> "λ" <> intercalate "," (map pretty xs) <> "." <+> lt (pretty body)+ CApp c1 c2 -> withPA funPA $ lt (pretty c1) <+> rt (pretty c2)+ CTest xs c -> "test" <+> prettyTestVars xs <+> pretty c+ CType ty -> pretty ty+ CDelay d -> withPA initPA $ do+ lunbind d $ \(xs, bodies) ->+ "delay" <+> intercalate "," (map pretty xs) <> "." <+> pretty (toTuple bodies)+ CForce c -> withPA funPA $ "force" <+> rt (pretty c)++toTuple :: [Core] -> Core+toTuple = foldr CPair CUnit++prettyTestVars :: Members '[Reader PA, LFresh] r => [(String, Type, Name Core)] -> Sem r Doc+prettyTestVars = brackets . intercalate "," . map prettyTestVar+ where+ prettyTestVar (s, ty, n) = parens (intercalate "," [text s, pretty ty, pretty n])++isInfix, isPrefix, isPostfix :: Op -> Bool+isInfix OShouldEq{} = True+isInfix op = op `S.member` S.fromList+ [ OAdd, OMul, ODiv, OExp, OMod, ODivides, OMultinom, OEq, OLt]++isPrefix ONeg = True+isPrefix _ = False++isPostfix OFact = True+isPostfix _ = False++instance Pretty Op where+ pretty (OForall tys) = "∀" <> intercalate "," (map pretty tys) <> "."+ pretty (OExists tys) = "∃" <> intercalate "," (map pretty tys) <> "."+ pretty op+ | isInfix op = "~" <> text (opToStr op) <> "~"+ | isPrefix op = text (opToStr op) <> "~"+ | isPostfix op = "~" <> text (opToStr op)+ | otherwise = text (opToStr op)++opToStr :: Op -> String+opToStr = \case+ OAdd -> "+"+ ONeg -> "-"+ OSqrt -> "sqrt"+ OFloor -> "floor"+ OCeil -> "ceil"+ OAbs -> "abs"+ OMul -> "*"+ ODiv -> "/"+ OExp -> "^"+ OMod -> "mod"+ ODivides -> "divides"+ OMultinom -> "choose"+ OFact -> "!"+ OEq -> "=="+ OLt -> "<"+ OEnum -> "enumerate"+ OCount -> "count"+ OSize -> "size"+ OPower -> "power"+ OBagElem -> "elem_bag"+ OListElem -> "elem_list"+ OEachBag -> "each_bag"+ OEachSet -> "each_set"+ OFilterBag -> "filter_bag"+ OMerge -> "merge"+ OBagUnions -> "unions_bag"+ OSummary -> "summary"+ OEmptyGraph -> "emptyGraph"+ OVertex -> "vertex"+ OOverlay -> "overlay"+ OConnect -> "connect"+ OInsert -> "insert"+ OLookup -> "lookup"+ OUntil -> "until"+ OSetToList -> "set2list"+ OBagToSet -> "bag2set"+ OBagToList -> "bag2list"+ OListToSet -> "list2set"+ OListToBag -> "list2bag"+ OBagToCounts -> "bag2counts"+ OCountsToBag -> "counts2bag"+ OMapToSet -> "map2set"+ OSetToMap -> "set2map"+ OIsPrime -> "isPrime"+ OFactor -> "factor"+ OFrac -> "frac"+ OHolds -> "holds"+ ONotProp -> "not"+ OShouldEq _ -> "=!="+ OMatchErr -> "matchErr"+ OCrash -> "crash"+ OId -> "id"+ OLookupSeq -> "lookupSeq"+ OExtendSeq -> "extendSeq"+ OForall{} -> "∀"+ OExists{} -> "∃"
+ src/Disco/AST/Desugared.hs view
@@ -0,0 +1,272 @@+{-# LANGUAGE PatternSynonyms #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.AST.Desugared+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Typed abstract syntax trees representing the typechecked, desugared+-- Disco language.+--+-----------------------------------------------------------------------------++module Disco.AST.Desugared+ ( -- * Desugared, type-annotated terms+ DTerm+ , pattern DTVar+ , pattern DTPrim+ , pattern DTUnit+ , pattern DTBool+ , pattern DTChar+ , pattern DTNat+ , pattern DTRat+ , pattern DTAbs+ , pattern DTApp+ , pattern DTPair+ , pattern DTCase+ , pattern DTTyOp+ , pattern DTNil+ , pattern DTTest++ , Container(..)+ , DBinding+ , pattern DBinding+ -- * Branches and guards+ , DBranch++ , DGuard+ , pattern DGPat++ , DPattern+ , pattern DPVar+ , pattern DPWild+ , pattern DPUnit+ , pattern DPPair+ , pattern DPInj++ , DProperty+ )+ where++import GHC.Generics++import Data.Void+import Unbound.Generics.LocallyNameless++import Disco.AST.Generic+import Disco.Names (QName (..))+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import Disco.Types++data DS++type DProperty = Property_ DS++-- | A @DTerm@ is a term which has been typechecked and desugared, so+-- it has fewer constructors and complex features than 'ATerm', but+-- still retains typing information.++type DTerm = Term_ DS++type instance X_Binder DS = Name DTerm++type instance X_TVar DS = Void -- names are qualified+type instance X_TPrim DS = Type+type instance X_TLet DS = Void -- Let gets translated to lambda+type instance X_TUnit DS = ()+type instance X_TBool DS = Type+type instance X_TChar DS = ()+type instance X_TString DS = Void+type instance X_TNat DS = Type+type instance X_TRat DS = ()+type instance X_TAbs DS = Type -- For lambas this is the function type but+ -- for forall/exists it's the argument type+type instance X_TApp DS = Type+type instance X_TCase DS = Type+type instance X_TChain DS = Void -- Chains are translated into conjunctions of+ -- binary comparisons+type instance X_TTyOp DS = Type+type instance X_TContainer DS = Void -- Literal containers are desugared into+ -- conversion functions applied to list literals++type instance X_TContainerComp DS = Void -- Container comprehensions are translated+ -- into monadic chains++type instance X_TAscr DS = Void -- No type ascriptions+type instance X_TTup DS = Void -- No tuples, only pairs+type instance X_TParens DS = Void -- No explicit parens++-- Extra constructors+type instance X_Term DS = X_DTerm++data X_DTerm+ = DTPair_ Type DTerm DTerm+ | DTNil_ Type+ | DTTest_ [(String, Type, Name DTerm)] DTerm+ | DTVar_ Type (QName DTerm)+ deriving (Show, Generic)++instance Subst Type X_DTerm+instance Alpha X_DTerm++pattern DTVar :: Type -> QName DTerm -> DTerm+pattern DTVar ty qname = XTerm_ (DTVar_ ty qname)++pattern DTPrim :: Type -> Prim -> DTerm+pattern DTPrim ty name = TPrim_ ty name++pattern DTUnit :: DTerm+pattern DTUnit = TUnit_ ()++pattern DTBool :: Type -> Bool -> DTerm+pattern DTBool ty bool = TBool_ ty bool++pattern DTNat :: Type -> Integer -> DTerm+pattern DTNat ty int = TNat_ ty int++pattern DTRat :: Rational -> DTerm+pattern DTRat rat = TRat_ () rat++pattern DTChar :: Char -> DTerm+pattern DTChar c = TChar_ () c++pattern DTAbs :: Quantifier -> Type -> Bind (Name DTerm) DTerm -> DTerm+pattern DTAbs q ty lam = TAbs_ q ty lam++pattern DTApp :: Type -> DTerm -> DTerm -> DTerm+pattern DTApp ty term1 term2 = TApp_ ty term1 term2++pattern DTPair :: Type -> DTerm -> DTerm -> DTerm+pattern DTPair ty t1 t2 = XTerm_ (DTPair_ ty t1 t2)++pattern DTCase :: Type -> [DBranch] -> DTerm+pattern DTCase ty branch = TCase_ ty branch++pattern DTTyOp :: Type -> TyOp -> Type -> DTerm+pattern DTTyOp ty1 tyop ty2 = TTyOp_ ty1 tyop ty2++pattern DTNil :: Type -> DTerm+pattern DTNil ty = XTerm_ (DTNil_ ty)++-- | A test frame, recording a collection of variables with their types and+-- their original user-facing names. Used for legible reporting of test+-- failures inside the enclosed term.+pattern DTTest :: [(String, Type, Name DTerm)] -> DTerm -> DTerm+pattern DTTest ns t = XTerm_ (DTTest_ ns t)++{-# COMPLETE DTVar, DTPrim, DTUnit, DTBool, DTChar, DTNat, DTRat,+ DTAbs, DTApp, DTPair, DTCase, DTTyOp,+ DTNil, DTTest #-}++type instance X_TLink DS = Void++type DBinding = Binding_ DS++pattern DBinding :: Maybe (Embed PolyType) -> Name DTerm -> Embed DTerm -> DBinding+pattern DBinding m b n = Binding_ m b n++{-# COMPLETE DBinding #-}++type DBranch = Bind (Telescope DGuard) DTerm++type DGuard = Guard_ DS++type instance X_GBool DS = Void -- Boolean guards get desugared to pattern-matching+type instance X_GPat DS = ()+type instance X_GLet DS = Void -- Let gets desugared to 'when' with a variable++pattern DGPat :: Embed DTerm -> DPattern -> DGuard+pattern DGPat embedt pat = GPat_ () embedt pat++{-# COMPLETE DGPat #-}++type DPattern = Pattern_ DS++type instance X_PVar DS = Embed Type+type instance X_PWild DS = Embed Type+type instance X_PAscr DS = Void+type instance X_PUnit DS = ()+type instance X_PBool DS = Void+type instance X_PChar DS = Void+type instance X_PString DS = Void+type instance X_PTup DS = Void+type instance X_PInj DS = Void+type instance X_PNat DS = Void+type instance X_PCons DS = Void+type instance X_PList DS = Void+type instance X_PAdd DS = Void+type instance X_PMul DS = Void+type instance X_PSub DS = Void+type instance X_PNeg DS = Void+type instance X_PFrac DS = Void++-- In the desugared language, constructor patterns (DPPair, DPInj) can+-- only contain variables, not nested patterns. This means that the+-- desugaring phase has to make explicit the order of matching by+-- exploding nested patterns into sequential guards, which makes the+-- interpreter simpler.++type instance X_Pattern DS =+ Either+ (Embed Type, Name DTerm, Name DTerm) -- DPPair+ (Embed Type, Side, Name DTerm) -- DPInj++pattern DPVar :: Type -> Name DTerm -> DPattern+pattern DPVar ty name <- PVar_ (unembed -> ty) name+ where+ DPVar ty name = PVar_ (embed ty) name++pattern DPWild :: Type -> DPattern+pattern DPWild ty <- PWild_ (unembed -> ty)+ where+ DPWild ty = PWild_ (embed ty)++pattern DPUnit :: DPattern+pattern DPUnit = PUnit_ ()++pattern DPPair :: Type -> Name DTerm -> Name DTerm -> DPattern+pattern DPPair ty x1 x2 <- XPattern_ (Left (unembed -> ty, x1, x2))+ where+ DPPair ty x1 x2 = XPattern_ (Left (embed ty, x1, x2))++pattern DPInj :: Type -> Side -> Name DTerm -> DPattern+pattern DPInj ty s x <- XPattern_ (Right (unembed -> ty, s, x))+ where+ DPInj ty s x = XPattern_ (Right (embed ty, s, x))++{-# COMPLETE DPVar, DPWild, DPUnit, DPPair, DPInj #-}++type instance X_QBind DS = Void+type instance X_QGuard DS = Void++------------------------------------------------------------+-- getType+------------------------------------------------------------++instance HasType DTerm where+ getType (DTVar ty _) = ty+ getType (DTPrim ty _) = ty+ getType DTUnit = TyUnit+ getType (DTBool ty _) = ty+ getType (DTChar _) = TyC+ getType (DTNat ty _) = ty+ getType (DTRat _) = TyF+ getType (DTAbs Lam ty _) = ty+ getType DTAbs{} = TyProp+ getType (DTApp ty _ _) = ty+ getType (DTPair ty _ _) = ty+ getType (DTCase ty _) = ty+ getType (DTTyOp ty _ _) = ty+ getType (DTNil ty) = ty+ getType (DTTest _ _) = TyProp++instance HasType DPattern where+ getType (DPVar ty _) = ty+ getType (DPWild ty) = ty+ getType DPUnit = TyUnit+ getType (DPPair ty _ _) = ty+ getType (DPInj ty _ _) = ty
+ src/Disco/AST/Generic.hs view
@@ -0,0 +1,656 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}++-- Orphan Alpha Void instance+{-# OPTIONS_GHC -fno-warn-orphans #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.AST.Generic+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- Abstract syntax trees representing the generic syntax of the Disco+-- language. Concrete AST instances may use this module as a template.+--+-- For more detail on the approach used here, see+--+-- Najd and Peyton Jones, "Trees that Grow". Journal of Universal+-- Computer Science, vol. 23 no. 1 (2017), 42-62.+-- <https://arxiv.org/abs/1610.04799>+--+-- Essentially, we define a basic generic 'Term_' type, with a type+-- index to indicate what kind of term it is, i.e. what phase the term+-- belongs to. Each constructor has a type family used to define any+-- extra data that should go in the constructor for a particular+-- phase; there is also one additional constructor which can be used+-- to store arbitrary additional information, again governed by a type+-- family. Together with the use of pattern synonyms, the result is+-- that it looks like we have a different type for each phase, each+-- with its own set of constructors, but in fact all use the same+-- underlying type. Particular instantiations of the generic+-- framework here can be found in "Disco.AST.Surface",+-- "Disco.AST.Typed", and "Disco.AST.Desugared".+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.AST.Generic+ ( -- * Telescopes++ Telescope (..), telCons+ , foldTelescope, mapTelescope+ , traverseTelescope+ , toTelescope, fromTelescope++ -- * Utility types++ , Side (..), selectSide, fromSide+ , Container (..)+ , Ellipsis (..)++ -- * Term++ , Term_ (..)++ , X_TVar+ , X_TPrim+ , X_TLet+ , X_TParens+ , X_TUnit+ , X_TBool+ , X_TNat+ , X_TRat+ , X_TChar+ , X_TString+ , X_TAbs+ , X_TApp+ , X_TTup+ , X_TCase+ , X_TChain+ , X_TTyOp+ , X_TContainer+ , X_TContainerComp+ , X_TAscr+ , X_Term++ , ForallTerm++ -- * Link++ , Link_ (..)+ , X_TLink+ , ForallLink++ -- * Qual++ , Qual_ (..)+ , X_QBind+ , X_QGuard+ , ForallQual++ -- * Binding++ , Binding_ (..)++ -- * Branch+ , Branch_++ -- * Guard++ , Guard_ (..)+ , X_GBool+ , X_GPat+ , X_GLet+ , ForallGuard++ -- * Pattern++ , Pattern_ (..)+ , X_PVar+ , X_PWild+ , X_PAscr+ , X_PUnit+ , X_PBool+ , X_PTup+ , X_PInj+ , X_PNat+ , X_PChar+ , X_PString+ , X_PCons+ , X_PList+ , X_PAdd+ , X_PMul+ , X_PSub+ , X_PNeg+ , X_PFrac+ , X_Pattern+ , ForallPattern++ -- * Quantifiers++ , Quantifier(..)+ , Binder_+ , X_Binder++ -- * Property++ , Property_+ )+ where++import Control.Lens.Plated+import Data.Data (Data)+import Data.Data.Lens (uniplate)+import Data.Typeable+import GHC.Exts (Constraint)+import GHC.Generics (Generic)++import Data.Void+import Unbound.Generics.LocallyNameless++import Disco.Pretty+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import Disco.Types++------------------------------------------------------------+-- Telescopes+------------------------------------------------------------++-- | A telescope is essentially a list, except that each item can bind+-- names in the rest of the list.+data Telescope b where++ -- | The empty telescope.+ TelEmpty :: Telescope b++ -- | A binder of type @b@ followed by zero or more @b@'s. This @b@+ -- can bind variables in the subsequent @b@'s.+ TelCons :: Rebind b (Telescope b) -> Telescope b+ deriving (Show, Generic, Alpha, Subst t, Data)++-- | Add a new item to the beginning of a 'Telescope'.+telCons :: Alpha b => b -> Telescope b -> Telescope b+telCons b tb = TelCons (rebind b tb)++-- | Fold a telescope given a combining function and a value to use+-- for the empty telescope. Analogous to 'foldr' for lists.+foldTelescope :: Alpha b => (b -> r -> r) -> r -> Telescope b -> r+foldTelescope _ z TelEmpty = z+foldTelescope f z (TelCons (unrebind -> (b,bs))) = f b (foldTelescope f z bs)++-- | Apply a function to every item in a telescope.+mapTelescope :: (Alpha a, Alpha b) => (a -> b) -> Telescope a -> Telescope b+mapTelescope f = toTelescope . map f . fromTelescope++-- | Traverse over a telescope.+traverseTelescope+ :: (Applicative f, Alpha a, Alpha b)+ => (a -> f b) -> Telescope a -> f (Telescope b)+traverseTelescope f = foldTelescope (\a ftb -> telCons <$> f a <*> ftb) (pure TelEmpty)++-- | Convert a list to a telescope.+toTelescope :: Alpha b => [b] -> Telescope b+toTelescope = foldr telCons TelEmpty++-- | Convert a telescope to a list.+fromTelescope :: Alpha b => Telescope b -> [b]+fromTelescope = foldTelescope (:) []++------------------------------------------------------------+-- Utility types+------------------------------------------------------------++-- | Injections into a sum type (@inl@ or @inr@) have a "side" (@L@ or @R@).+data Side = L | R+ deriving (Show, Eq, Ord, Enum, Bounded, Generic, Data, Alpha, Subst t)++instance Pretty Side where+ pretty = \case+ L -> text "left"+ R -> text "right"++-- | Use a 'Side' to select one of two arguments (the first argument+-- for 'L', and the second for 'R').+selectSide :: Side -> a -> a -> a+selectSide L a _ = a+selectSide R _ b = b++-- | Convert a 'Side' to a boolean.+fromSide :: Side -> Bool+fromSide s = selectSide s False True++-- | An enumeration of the different kinds of containers in disco:+-- lists, bags, and sets.+data Container where+ ListContainer :: Container+ BagContainer :: Container+ SetContainer :: Container+ deriving (Show, Eq, Enum, Generic, Data, Alpha, Subst t)++-- | An ellipsis is an "omitted" part of a literal container (such as+-- a list or set), of the form @.. t@. We don't have open-ended+-- ellipses since everything is evaluated eagerly and hence+-- containers must be finite.+data Ellipsis t where+ -- | 'Until' represents an ellipsis with a given endpoint, as in @[3 .. 20]@.+ Until :: t -> Ellipsis t -- @.. t@+ deriving (Show, Generic, Functor, Foldable, Traversable, Alpha, Subst a, Data)++------------------------------------------------------------+-- Terms+------------------------------------------------------------++type family X_TVar e+type family X_TPrim e+type family X_TLet e+type family X_TParens e+type family X_TUnit e+type family X_TBool e+type family X_TNat e+type family X_TRat e+type family X_TChar e+type family X_TString e+type family X_TAbs e+type family X_TApp e+type family X_TTup e+type family X_TCase e+type family X_TChain e+type family X_TTyOp e+type family X_TContainer e+type family X_TContainerComp e+type family X_TAscr e+type family X_Term e++-- | The base generic AST representing terms in the disco language.+-- @e@ is a type index indicating the kind of term, i.e. the phase+-- (for example, surface, typed, or desugared). Type families like+-- 'X_TVar' and so on use the phase index to determine what extra+-- information (if any) should be stored in each constructor. For+-- example, in the typed phase many constructors store an extra+-- type, giving the type of the term.+data Term_ e where++ -- | A term variable.+ TVar_ :: X_TVar e -> Name (Term_ e) -> Term_ e++ -- | A primitive, /i.e./ a constant which is interpreted specially+ -- at runtime. See "Disco.Syntax.Prims".+ TPrim_ :: X_TPrim e -> Prim -> Term_ e++ -- | A (non-recursive) let expression, @let x1 = t1, x2 = t2, ... in t@.+ TLet_ :: X_TLet e -> Bind (Telescope (Binding_ e)) (Term_ e) -> Term_ e++ -- | Explicit parentheses. We need to keep track of these in the+ -- surface syntax in order to syntactically distinguish+ -- multiplication and function application. However, note that+ -- these disappear after the surface syntax phase.+ TParens_ :: X_TParens e -> Term_ e -> Term_ e++ -- | The unit value, (), of type Unit.+ TUnit_ :: X_TUnit e -> Term_ e++ -- | A boolean value.+ TBool_ :: X_TBool e -> Bool -> Term_ e++ -- | A natural number.+ TNat_ :: X_TNat e -> Integer -> Term_ e++ -- | A nonnegative rational number, parsed as a decimal. (Note+ -- syntax like @3/5@ does not parse as a rational, but rather as+ -- the application of a division operator to two natural numbers.)+ TRat_ :: X_TRat e -> Rational -> Term_ e++ -- | A literal unicode character, /e.g./ @'d'@.+ TChar_ :: X_TChar e -> Char -> Term_ e++ -- | A string literal, /e.g./ @"disco"@.+ TString_ :: X_TString e -> [Char] -> Term_ e++ -- | A binding abstraction, of the form @Q vars. expr@ where @Q@ is+ -- a quantifier and @vars@ is a list of bound variables and+ -- optional type annotations. In particular, this could be a+ -- lambda abstraction, /i.e./ an anonymous function (/e.g./ @\x,+ -- (y:N). 2x + y@), a universal quantifier (@forall x, (y:N). x^2 ++ -- y > 0@), or an existential quantifier (@exists x, (y:N). x^2 + y+ -- == 0@).+ TAbs_ :: Quantifier -> X_TAbs e -> Binder_ e (Term_ e) -> Term_ e++ -- | Function application, @t1 t2@.+ TApp_ :: X_TApp e -> Term_ e -> Term_ e -> Term_ e++ -- | An n-tuple, @(t1, ..., tn)@.+ TTup_ :: X_TTup e -> [Term_ e] -> Term_ e++ -- | A case expression.+ TCase_ :: X_TCase e -> [Branch_ e] -> Term_ e++ -- | A chained comparison, consisting of a term followed by one or+ -- more "links", where each link is a comparison operator and+ -- another term.+ TChain_ :: X_TChain e -> Term_ e -> [Link_ e] -> Term_ e++ -- | An application of a type operator.+ TTyOp_ :: X_TTyOp e -> TyOp -> Type -> Term_ e++ -- | A containter literal (set, bag, or list).+ TContainer_ :: X_TContainer e -> Container -> [(Term_ e, Maybe (Term_ e))] -> Maybe (Ellipsis (Term_ e)) -> Term_ e++ -- | A container comprehension.+ TContainerComp_ :: X_TContainerComp e -> Container -> Bind (Telescope (Qual_ e)) (Term_ e) -> Term_ e++ -- | Type ascription, @(Term_ e : type)@.+ TAscr_ :: X_TAscr e -> Term_ e -> PolyType -> Term_ e++ -- | A data constructor with an extension descriptor that a "concrete"+ -- implementation of a generic AST may use to carry extra information.+ XTerm_ :: X_Term e -> Term_ e+ deriving (Generic)++-- A type that abstracts over constraints for generic data constructors.+-- This makes it easier to derive typeclass instances for generic types.+type ForallTerm (a :: * -> Constraint) e+ = ( a (X_TVar e)+ , a (X_TPrim e)+ , a (X_TLet e)+ , a (X_TParens e)+ , a (X_TUnit e)+ , a (X_TBool e)+ , a (X_TNat e)+ , a (X_TRat e)+ , a (X_TChar e)+ , a (X_TString e)+ , a (X_TAbs e)+ , a (X_TApp e)+ , a (X_TCase e)+ , a (X_TTup e)+ , a (X_TChain e)+ , a (X_TTyOp e)+ , a (X_TContainer e)+ , a (X_TContainerComp e)+ , a (X_TAscr e)+ , a (X_Term e)+ , a (Qual_ e)+ , a (Guard_ e)+ , a (Link_ e)+ , a (Binding_ e)+ , a (Pattern_ e)+ , a (Binder_ e (Term_ e))+ )++deriving instance ForallTerm Show e => Show (Term_ e)+instance+ ( Typeable e+ , ForallTerm (Subst Type) e+ , ForallTerm Alpha e+ )+ => Subst Type (Term_ e)+instance (Typeable e, ForallTerm Alpha e) => Alpha (Term_ e)+deriving instance (Data e, Typeable e, ForallTerm Data e) => Data (Term_ e)++instance (Data e, ForallTerm Data e) => Plated (Term_ e) where+ plate = uniplate++------------------------------------------------------------+-- Link+------------------------------------------------------------++type family X_TLink e++-- | A "link" is a comparison operator and a term; a single term+-- followed by a sequence of links makes up a comparison chain, such+-- as @2 < x < y < 10@.+data Link_ e where++ -- | Note that although the type of 'TLink_' says it can hold any+ -- 'BOp', it should really only hold comparison operators.+ TLink_ :: X_TLink e -> BOp -> Term_ e -> Link_ e+ deriving Generic++type ForallLink (a :: * -> Constraint) e+ = ( a (X_TLink e)+ , a (Term_ e)+ )++deriving instance ForallLink Show e => Show (Link_ e)+instance ForallLink (Subst Type) e => Subst Type (Link_ e)+instance (Typeable e, Show (Link_ e), ForallLink Alpha e) => Alpha (Link_ e)+deriving instance (Typeable e, Data e, ForallLink Data e) => Data (Link_ e)++------------------------------------------------------------+-- Qual+------------------------------------------------------------++type family X_QBind e+type family X_QGuard e++-- | A container comprehension consists of a head term and then a list+-- of qualifiers. Each qualifier either binds a variable to some+-- collection or consists of a boolean guard.+data Qual_ e where++ -- | A binding qualifier (i.e. @x in t@).+ QBind_ :: X_QBind e -> Name (Term_ e) -> Embed (Term_ e) -> Qual_ e++ -- | A boolean guard qualfier (i.e. @x + y > 4@).+ QGuard_ :: X_QGuard e -> Embed (Term_ e) -> Qual_ e++ deriving Generic++type ForallQual (a :: * -> Constraint) e+ = ( a (X_QBind e)+ , a (X_QGuard e)+ , a (Term_ e)+ )++deriving instance ForallQual Show e => Show (Qual_ e)+instance ForallQual (Subst Type) e => Subst Type (Qual_ e)+instance (Typeable e, ForallQual Alpha e) => Alpha (Qual_ e)+deriving instance (Typeable e, Data e, ForallQual Data e) => Data (Qual_ e)++------------------------------------------------------------+-- Binding+------------------------------------------------------------++-- | A binding is a name along with its definition, and optionally its+-- type.+data Binding_ e = Binding_ (Maybe (Embed PolyType)) (Name (Term_ e)) (Embed (Term_ e))+ deriving (Generic)++deriving instance ForallTerm Show e => Show (Binding_ e)+instance Subst Type (Term_ e) => Subst Type (Binding_ e)+instance (Typeable e, Show (Binding_ e), Alpha (Term_ e)) => Alpha (Binding_ e)+deriving instance (Typeable e, Data e, ForallTerm Data e) => Data (Binding_ e)++------------------------------------------------------------+-- Branch+------------------------------------------------------------++-- | A branch of a case is a list of guards with an accompanying term.+-- The guards scope over the term. Additionally, each guard scopes+-- over subsequent guards.++type Branch_ e = Bind (Telescope (Guard_ e)) (Term_ e)++------------------------------------------------------------+-- Guard+------------------------------------------------------------++type family X_GBool e+type family X_GPat e+type family X_GLet e++-- | Guards in case expressions.+data Guard_ e where++ -- | Boolean guard (@if <test>@)+ GBool_ :: X_GBool e -> Embed (Term_ e) -> Guard_ e++ -- | Pattern guard (@when term = pat@)+ GPat_ :: X_GPat e -> Embed (Term_ e) -> Pattern_ e -> Guard_ e++ -- | Let (@let x = term@)+ GLet_ :: X_GLet e -> Binding_ e -> Guard_ e++ deriving Generic++type ForallGuard (a :: * -> Constraint) e+ = ( a (X_GBool e)+ , a (X_GPat e)+ , a (X_GLet e)+ , a (Term_ e)+ , a (Pattern_ e)+ , a (Binding_ e)+ )++deriving instance ForallGuard Show e => Show (Guard_ e)+instance ForallGuard (Subst Type) e => Subst Type (Guard_ e)+instance (Typeable e, Show (Guard_ e), ForallGuard Alpha e) => Alpha (Guard_ e)+deriving instance (Typeable e, Data e, ForallGuard Data e) => Data (Guard_ e)++------------------------------------------------------------+-- Pattern+------------------------------------------------------------++type family X_PVar e+type family X_PWild e+type family X_PAscr e+type family X_PUnit e+type family X_PBool e+type family X_PTup e+type family X_PInj e+type family X_PNat e+type family X_PChar e+type family X_PString e+type family X_PCons e+type family X_PList e+type family X_PAdd e+type family X_PMul e+type family X_PSub e+type family X_PNeg e+type family X_PFrac e+type family X_Pattern e++-- | Patterns.+data Pattern_ e where++ -- | Variable pattern: matches anything and binds the variable.+ PVar_ :: X_PVar e -> Name (Term_ e) -> Pattern_ e++ -- | Wildcard pattern @_@: matches anything.+ PWild_ :: X_PWild e -> Pattern_ e++ -- | Type ascription pattern @pat : ty@.+ PAscr_ :: X_PAscr e -> Pattern_ e -> Type -> Pattern_ e++ -- | Unit pattern @()@: matches @()@.+ PUnit_ :: X_PUnit e -> Pattern_ e++ -- | Literal boolean pattern.+ PBool_ :: X_PBool e -> Bool -> Pattern_ e++ -- | Tuple pattern @(pat1, .. , patn)@.+ PTup_ :: X_PTup e -> [Pattern_ e] -> Pattern_ e++ -- | Injection pattern (@inl pat@ or @inr pat@).+ PInj_ :: X_PInj e -> Side -> Pattern_ e -> Pattern_ e++ -- | Literal natural number pattern.+ PNat_ :: X_PNat e -> Integer -> Pattern_ e++ -- | Unicode character pattern+ PChar_ :: X_PChar e -> Char -> Pattern_ e++ -- | String pattern.+ PString_ :: X_PString e -> String -> Pattern_ e++ -- | Cons pattern @p1 :: p2@.+ PCons_ :: X_PCons e -> Pattern_ e -> Pattern_ e -> Pattern_ e++ -- | List pattern @[p1, .., pn]@.+ PList_ :: X_PList e -> [Pattern_ e] -> Pattern_ e++ -- | Addition pattern, @p + t@ or @t + p@+ PAdd_ :: X_PAdd e -> Side -> Pattern_ e -> Term_ e -> Pattern_ e++ -- | Multiplication pattern, @p * t@ or @t * p@+ PMul_ :: X_PMul e -> Side -> Pattern_ e -> Term_ e -> Pattern_ e++ -- | Subtraction pattern, @p - t@+ PSub_ :: X_PSub e -> Pattern_ e -> Term_ e -> Pattern_ e++ -- | Negation pattern, @-p@+ PNeg_ :: X_PNeg e -> Pattern_ e -> Pattern_ e++ -- | Fraction pattern, @p1/p2@+ PFrac_ :: X_PFrac e -> Pattern_ e -> Pattern_ e -> Pattern_ e++ -- | Expansion slot.+ XPattern_ :: X_Pattern e -> Pattern_ e++ deriving (Generic)++type ForallPattern (a :: * -> Constraint) e+ = ( a (X_PVar e)+ , a (X_PWild e)+ , a (X_PAscr e)+ , a (X_PUnit e)+ , a (X_PBool e)+ , a (X_PNat e)+ , a (X_PChar e)+ , a (X_PString e)+ , a (X_PTup e)+ , a (X_PInj e)+ , a (X_PCons e)+ , a (X_PList e)+ , a (X_PAdd e)+ , a (X_PMul e)+ , a (X_PSub e)+ , a (X_PNeg e)+ , a (X_PFrac e)+ , a (X_Pattern e)+ , a (Term_ e)+ )++deriving instance ForallPattern Show e => Show (Pattern_ e)+instance ForallPattern (Subst Type) e => Subst Type (Pattern_ e)+instance (Typeable e, Show (Pattern_ e), ForallPattern Alpha e) => Alpha (Pattern_ e)+deriving instance (Typeable e, Data e, ForallPattern Data e) => Data (Pattern_ e)++------------------------------------------------------------+-- Quantifiers and binders+------------------------------------------------------------++-- | A type family specifying what the binder in an abstraction can be.+-- Should have at least variables in it, but how many variables and+-- what other information is carried along may vary.+type family X_Binder e++-- | A binder represents the stuff between the quantifier and the body+-- of a lambda, ∀, or ∃ abstraction, as in @x : N, r : F@.+type Binder_ e a = Bind (X_Binder e) a++-- | A quantifier: λ, ∀, or ∃+data Quantifier = Lam | Ex | All+ deriving (Generic, Data, Eq, Ord, Show, Alpha, Subst Type)++------------------------------------------------------------+-- Property+------------------------------------------------------------++-- | A property is just a term (of type Prop).+type Property_ e = Term_ e++------------------------------------------------------------+-- Orphan instances+------------------------------------------------------------++-- Need this if we want to put 'Void' as the type+-- of an extension slot (to kill a constructor)+instance Alpha Void
+ src/Disco/AST/Surface.hs view
@@ -0,0 +1,654 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.AST.Surface+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Abstract syntax trees representing the surface syntax of the Disco+-- language.+--+-----------------------------------------------------------------------------++module Disco.AST.Surface+ ( -- * Modules+ Module(..), TopLevel(..)+ -- ** Documentation+ , Docs, DocThing(..), Property+ -- ** Declarations+ , TypeDecl(..), TermDefn(..), TypeDefn(..)+ , Decl(..), partitionDecls, prettyTyDecl++ -- * Terms+ , UD+ , Term+ , pattern TVar+ , pattern TPrim+ , pattern TUn+ , pattern TBin+ , pattern TLet+ , pattern TParens+ , pattern TUnit+ , pattern TBool+ , pattern TChar+ , pattern TString+ , pattern TNat+ , pattern TRat+ , pattern TAbs+ , pattern TApp+ , pattern TTup+ , pattern TCase+ , pattern TChain+ , pattern TTyOp+ , pattern TContainerComp+ , pattern TContainer+ , pattern TAscr+ , pattern TWild+ , pattern TList+ , pattern TListComp++ , Quantifier(..)++ -- ** Telescopes+ , Telescope(..), foldTelescope, mapTelescope, toTelescope, fromTelescope+ -- ** Expressions+ , Side(..)++ , Link+ , pattern TLink++ , Binding++ -- ** Lists+ , Qual+ , pattern QBind+ , pattern QGuard++ , Container(..)++ , Ellipsis(..)++ -- ** Case expressions and patterns+ , Branch++ , Guard+ , pattern GBool+ , pattern GPat+ , pattern GLet++ , Pattern+ , pattern PVar+ , pattern PWild+ , pattern PAscr+ , pattern PUnit+ , pattern PBool+ , pattern PChar+ , pattern PString+ , pattern PTup+ , pattern PInj+ , pattern PNat+ , pattern PCons+ , pattern PList+ , pattern PAdd+ , pattern PMul+ , pattern PSub+ , pattern PNeg+ , pattern PFrac++ , pattern Binding+ )+ where++import Prelude hiding ((<>))++import Control.Lens (_1, _2, _3, (%~))+import Data.Char (toLower)+import qualified Data.Map as M+import Data.Set (Set)+import Data.Void++import Disco.Effects.LFresh+import Polysemy hiding (Embed)+import Polysemy.Reader++import Disco.AST.Generic+import Disco.Extensions+import Disco.Pretty+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import Disco.Types+import Unbound.Generics.LocallyNameless hiding (LFresh (..), lunbind)++-- | The extension descriptor for Surface specific AST types.+data UD++-- | A module contains all the information from one disco source file.+data Module = Module+ { modExts :: Set Ext -- ^ Enabled extensions+ , modImports :: [String] -- ^ Module imports+ , modDecls :: [Decl] -- ^ Declarations+ , modDocs :: [(Name Term, Docs)] -- ^ Documentation+ , modTerms :: [Term] -- ^ Top-level (bare) terms+ }+deriving instance ForallTerm Show UD => Show Module++-- | A @TopLevel@ is either documentation (a 'DocThing') or a+-- declaration ('Decl').+data TopLevel = TLDoc DocThing | TLDecl Decl | TLExpr Term+deriving instance ForallTerm Show UD => Show TopLevel++-- | Convenient synonym for a list of 'DocThing's.+type Docs = [DocThing]++-- | An item of documentation.+data DocThing+ = DocString [String] -- ^ A documentation string, i.e. a block+ -- of @||| text@ items+ | DocProperty Property -- ^ An example/doctest/property of the+ -- form @!!! forall (x1:ty1) ... . property@+deriving instance ForallTerm Show UD => Show DocThing++-- | A property is a universally quantified term of the form+-- @forall v1 : T1, v2 : T2. term@.+type Property = Property_ UD++-- | A type declaration, @name : type@.+data TypeDecl = TypeDecl (Name Term) PolyType++-- | A group of definition clauses of the form @name pat1 .. patn = term@. The+-- patterns bind variables in the term. For example, @f n (x,y) =+-- n*x + y@.+data TermDefn = TermDefn (Name Term) [Bind [Pattern] Term]++-- | A user-defined type (potentially recursive).+--+-- @type T arg1 arg2 ... = body+data TypeDefn = TypeDefn String [String] Type+ deriving Show++-- | A declaration is either a type declaration, a term definition, or+-- a type definition.+data Decl where+ DType :: TypeDecl -> Decl+ DDefn :: TermDefn -> Decl+ DTyDef :: TypeDefn -> Decl++deriving instance ForallTerm Show UD => Show TypeDecl+deriving instance ForallTerm Show UD => Show TermDefn+deriving instance ForallTerm Show UD => Show Decl++partitionDecls :: [Decl] -> ([TypeDecl], [TermDefn], [TypeDefn])+partitionDecls (DType tyDecl : ds) = (_1 %~ (tyDecl:)) (partitionDecls ds)+partitionDecls (DDefn def : ds) = (_2 %~ (def:)) (partitionDecls ds)+partitionDecls (DTyDef def : ds) = (_3 %~ (def:)) (partitionDecls ds)+partitionDecls [] = ([], [], [])++------------------------------------------------------------+-- Pretty-printing top-level declarations++-- prettyModule :: Module -> Doc+-- prettyModule = foldr ($+$) empty . map pretty++instance Pretty Decl where+ pretty = \case+ DType (TypeDecl x ty) -> pretty x <+> text ":" <+> pretty ty+ DTyDef (TypeDefn x args body) ->+ text "type" <+> text x <+> hsep (map text args) <+> text "=" <+> pretty body+ DDefn (TermDefn x bs) -> vcat $ map (pretty . (x,)) bs++-- | Pretty-print a single clause in a definition.+instance Pretty (Name a, Bind [Pattern] Term) where+ pretty (x, b) = withPA funPA . lunbind b $ \(ps, t) ->+ pretty x <> hcat (map prettyPatternP ps) <+> text "=" <+> setPA initPA (pretty t)++-- | Pretty-print a type declaration.+prettyTyDecl :: Members '[Reader PA, LFresh] r => Name t -> Type -> Sem r Doc+prettyTyDecl x ty = hsep [pretty x, text ":", pretty ty]++------------------------------------------------------------+-- Terms+------------------------------------------------------------+type Term = Term_ UD++-- In the surface language, abstractions bind variables using a+-- (nonempty) list of patterns. Each pattern might contain any+-- number of variables, and might have type annotations on some+-- of its components.+type instance X_Binder UD = [Pattern]++type instance X_TVar UD = ()+type instance X_TPrim UD = ()+type instance X_TLet UD = ()+type instance X_TParens UD = ()+type instance X_TUnit UD = ()+type instance X_TBool UD = ()+type instance X_TNat UD = ()+type instance X_TRat UD = ()+type instance X_TChar UD = ()+type instance X_TString UD = ()+type instance X_TAbs UD = ()+type instance X_TApp UD = ()+type instance X_TTup UD = ()+type instance X_TCase UD = ()+type instance X_TChain UD = ()+type instance X_TTyOp UD = ()+type instance X_TContainer UD = ()+type instance X_TContainerComp UD = ()+type instance X_TAscr UD = ()+type instance X_Term UD = () -- TWild++pattern TVar :: Name Term -> Term+pattern TVar name = TVar_ () name++pattern TPrim :: Prim -> Term+pattern TPrim name = TPrim_ () name++pattern TUn :: UOp -> Term -> Term+pattern TUn uop term = TApp_ () (TPrim_ () (PrimUOp uop)) term++pattern TBin :: BOp -> Term -> Term -> Term+pattern TBin bop term1 term2 = TApp_ () (TPrim_ () (PrimBOp bop)) (TTup_ () [term1, term2])++pattern TLet :: Bind (Telescope Binding) Term -> Term+pattern TLet bind = TLet_ () bind++pattern TParens :: Term -> Term+pattern TParens term = TParens_ () term++pattern TUnit :: Term+pattern TUnit = TUnit_ ()++pattern TBool :: Bool -> Term+pattern TBool bool = TBool_ () bool++pattern TNat :: Integer -> Term+pattern TNat int = TNat_ () int++pattern TRat :: Rational -> Term+pattern TRat rat = TRat_ () rat++pattern TChar :: Char -> Term+pattern TChar c = TChar_ () c++pattern TString :: String -> Term+pattern TString s = TString_ () s++pattern TAbs :: Quantifier -> Bind [Pattern] Term -> Term+pattern TAbs q bind = TAbs_ q () bind++pattern TApp :: Term -> Term -> Term+pattern TApp term1 term2 = TApp_ () term1 term2++pattern TTup :: [Term] -> Term+pattern TTup termlist = TTup_ () termlist++pattern TCase :: [Branch] -> Term+pattern TCase branch = TCase_ () branch++pattern TChain :: Term -> [Link] -> Term+pattern TChain term linklist = TChain_ () term linklist++pattern TTyOp :: TyOp -> Type -> Term+pattern TTyOp tyop ty = TTyOp_ () tyop ty++pattern TContainer :: Container -> [(Term, Maybe Term)] -> Maybe (Ellipsis Term) -> Term+pattern TContainer c tl mets = TContainer_ () c tl mets++pattern TContainerComp :: Container -> Bind (Telescope Qual) Term -> Term+pattern TContainerComp c b = TContainerComp_ () c b++pattern TAscr :: Term -> PolyType -> Term+pattern TAscr term ty = TAscr_ () term ty++-- Since we parse patterns by first parsing a term and then ensuring+-- it is a valid pattern, we have to include wildcards in the syntax+-- of terms, although they will be rejected at a later phase.+pattern TWild :: Term+pattern TWild = XTerm_ ()++{-# COMPLETE TVar, TPrim, TLet, TParens, TUnit, TBool, TNat, TRat, TChar,+ TString, TAbs, TApp, TTup, TCase, TChain, TTyOp,+ TContainer, TContainerComp, TAscr, TWild #-}++pattern TList :: [Term] -> Maybe (Ellipsis Term) -> Term+pattern TList ts e <- TContainer_ () ListContainer (map fst -> ts) e+ where+ TList ts e = TContainer_ () ListContainer (map (,Nothing) ts) e++pattern TListComp :: Bind (Telescope Qual) Term -> Term+pattern TListComp x = TContainerComp_ () ListContainer x++type Link = Link_ UD++type instance X_TLink UD = ()++pattern TLink :: BOp -> Term -> Link+pattern TLink bop term = TLink_ () bop term++{-# COMPLETE TLink #-}++type Qual = Qual_ UD++type instance X_QBind UD = ()+type instance X_QGuard UD = ()++pattern QBind :: Name Term -> Embed Term -> Qual+pattern QBind namet embedt = QBind_ () namet embedt++pattern QGuard :: Embed Term -> Qual+pattern QGuard embedt = QGuard_ () embedt++{-# COMPLETE QBind, QGuard #-}++type Binding = Binding_ UD++pattern Binding :: Maybe (Embed PolyType) -> Name Term -> Embed Term -> Binding+pattern Binding m b n = Binding_ m b n++{-# COMPLETE Binding #-}++type Branch = Branch_ UD++type Guard = Guard_ UD++type instance X_GBool UD = ()+type instance X_GPat UD = ()+type instance X_GLet UD = ()++pattern GBool :: Embed Term -> Guard+pattern GBool embedt = GBool_ () embedt++pattern GPat :: Embed Term -> Pattern -> Guard+pattern GPat embedt pat = GPat_ () embedt pat++pattern GLet :: Binding -> Guard+pattern GLet b = GLet_ () b++{-# COMPLETE GBool, GPat, GLet #-}++type Pattern = Pattern_ UD++type instance X_PVar UD = ()+type instance X_PWild UD = ()+type instance X_PAscr UD = ()+type instance X_PUnit UD = ()+type instance X_PBool UD = ()+type instance X_PTup UD = ()+type instance X_PInj UD = ()+type instance X_PNat UD = ()+type instance X_PChar UD = ()+type instance X_PString UD = ()+type instance X_PCons UD = ()+type instance X_PList UD = ()+type instance X_PAdd UD = ()+type instance X_PMul UD = ()+type instance X_PSub UD = ()+type instance X_PNeg UD = ()+type instance X_PFrac UD = ()+type instance X_Pattern UD = Void++pattern PVar :: Name Term -> Pattern+pattern PVar name = PVar_ () name++pattern PWild :: Pattern+pattern PWild = PWild_ ()++ -- (?) TAscr uses a PolyType, but without higher rank types+ -- I think we can't possibly need that here.+pattern PAscr :: Pattern -> Type -> Pattern+pattern PAscr p ty = PAscr_ () p ty++pattern PUnit :: Pattern+pattern PUnit = PUnit_ ()++pattern PBool :: Bool -> Pattern+pattern PBool b = PBool_ () b++pattern PChar :: Char -> Pattern+pattern PChar c = PChar_ () c++pattern PString :: String -> Pattern+pattern PString s = PString_ () s++pattern PTup :: [Pattern] -> Pattern+pattern PTup lp = PTup_ () lp++pattern PInj :: Side -> Pattern -> Pattern+pattern PInj s p = PInj_ () s p++pattern PNat :: Integer -> Pattern+pattern PNat n = PNat_ () n++pattern PCons :: Pattern -> Pattern -> Pattern+pattern PCons p1 p2 = PCons_ () p1 p2++pattern PList :: [Pattern] -> Pattern+pattern PList lp = PList_ () lp++pattern PAdd :: Side -> Pattern -> Term -> Pattern+pattern PAdd s p t = PAdd_ () s p t++pattern PMul :: Side -> Pattern -> Term -> Pattern+pattern PMul s p t = PMul_ () s p t++pattern PSub :: Pattern -> Term -> Pattern+pattern PSub p t = PSub_ () p t++pattern PNeg :: Pattern -> Pattern+pattern PNeg p = PNeg_ () p++pattern PFrac :: Pattern -> Pattern -> Pattern+pattern PFrac p1 p2 = PFrac_ () p1 p2++{-# COMPLETE PVar, PWild, PAscr, PUnit, PBool, PTup, PInj, PNat,+ PChar, PString, PCons, PList, PAdd, PMul, PSub, PNeg, PFrac #-}++------------------------------------------------------------+-- Pretty-printing for surface-syntax terms+--+-- The instances in this section are used to pretty-print surface+-- syntax, for example, when printing the source code definition of a+-- term (e.g. via the :doc REPL command).++-- | Pretty-print a term with guaranteed parentheses.+prettyTermP :: Members '[LFresh, Reader PA] r => Term -> Sem r Doc+prettyTermP t@TTup{} = setPA initPA $ pretty t+-- prettyTermP t@TContainer{} = setPA initPA $ "" <+> prettyTerm t+prettyTermP t = withPA initPA $ pretty t++instance Pretty Term where+ pretty = \case+ TVar x -> pretty x+ TPrim (PrimUOp uop) ->+ case M.lookup uop uopMap of+ Just (OpInfo (UOpF Pre _) (syn:_) _) -> text syn <> text "~"+ Just (OpInfo (UOpF Post _) (syn:_) _) -> text "~" <> text syn+ _ -> error $ "pretty @Term: " ++ show uop ++ " is not in the uopMap!"+ TPrim (PrimBOp bop) -> text "~" <> pretty bop <> text "~"+ TPrim p ->+ case M.lookup p primMap of+ Just (PrimInfo _ nm True) -> text nm+ Just (PrimInfo _ nm False) -> text "$" <> text nm+ Nothing -> error $ "pretty @Term: Prim " ++ show p ++ " is not in the primMap!"+ TParens t -> pretty t+ TUnit -> text "■"+ (TBool b) -> text (map toLower $ show b)+ TChar c -> text (show c)+ TString cs -> doubleQuotes $ text cs+ TAbs q bnd -> withPA initPA $+ lunbind bnd $ \(args, body) ->+ prettyQ q+ <> (hsep =<< punctuate (text ",") (map pretty args))+ <> text "."+ <+> lt (pretty body)+ where+ prettyQ Lam = text "λ"+ prettyQ All = text "∀"+ prettyQ Ex = text "∃"++ -- special case for fully applied unary operators+ TApp (TPrim (PrimUOp uop)) t ->+ case M.lookup uop uopMap of+ Just (OpInfo (UOpF Post _) _ _) -> withPA (ugetPA uop) $+ lt (pretty t) <> pretty uop+ Just (OpInfo (UOpF Pre _) _ _) -> withPA (ugetPA uop) $+ pretty uop <> rt (pretty t)+ _ -> error $ "pretty @Term: uopMap doesn't contain " ++ show uop++ -- special case for fully applied binary operators+ TApp (TPrim (PrimBOp bop)) (TTup [t1, t2]) -> withPA (getPA bop) $+ hsep+ [ lt (pretty t1)+ , pretty bop+ , rt (pretty t2)+ ]++ -- Always pretty-print function applications with parentheses+ TApp t1 t2 -> withPA funPA $+ lt (pretty t1) <> prettyTermP t2++ TTup ts -> setPA initPA $ do+ ds <- punctuate (text ",") (map pretty ts)+ parens (hsep ds)+ TContainer c ts e -> setPA initPA $ do+ ds <- punctuate (text ",") (map prettyCount ts)+ let pe = case e of+ Nothing -> []+ Just (Until t) -> [text "..", pretty t]+ containerDelims c (hsep (ds ++ pe))+ where+ prettyCount (t, Nothing) = pretty t+ prettyCount (t, Just n) = lt (pretty t) <+> text "#" <+> rt (pretty n)+ TContainerComp c bqst ->+ lunbind bqst $ \(qs,t) ->+ setPA initPA $ containerDelims c (hsep [pretty t, text "|", pretty qs])+ TNat n -> integer n+ TChain t lks -> withPA (getPA Eq) . hsep $+ lt (pretty t)+ : concatMap prettyLink lks+ where+ prettyLink (TLink op t2) =+ [ pretty op+ , setPA (getPA op) . rt $ pretty t2+ ]+ TLet bnd -> withPA initPA $+ lunbind bnd $ \(bs, t2) -> do+ ds <- punctuate (text ",") (map pretty (fromTelescope bs))+ hsep+ [ text "let"+ , hsep ds+ , text "in"+ , pretty t2+ ]++ TCase b -> withPA initPA $+ (text "{?" <+> prettyBranches b) $+$ text "?}"+ TAscr t ty -> withPA ascrPA $+ lt (pretty t) <+> text ":" <+> rt (pretty ty)+ TRat r -> text (prettyDecimal r)+ TTyOp op ty -> withPA funPA $+ pretty op <+> pretty ty+ TWild -> text "_"++-- | Print appropriate delimiters for a container literal.+containerDelims :: Member (Reader PA) r => Container -> (Sem r Doc -> Sem r Doc)+containerDelims ListContainer = brackets+containerDelims BagContainer = bag+containerDelims SetContainer = braces++prettyBranches :: Members '[Reader PA, LFresh] r => [Branch] -> Sem r Doc+prettyBranches = \case+ [] -> error "Empty branches are disallowed."+ b:bs ->+ pretty b+ $+$+ foldr (($+$) . (text "," <+>) . pretty) empty bs++-- | Pretty-print a single branch in a case expression.+instance Pretty Branch where+ pretty br = lunbind br $ \(gs,t) ->+ pretty t <+> pretty gs++-- | Pretty-print the guards in a single branch of a case expression.+instance Pretty (Telescope Guard) where+ pretty = \case+ TelEmpty -> text "otherwise"+ gs -> foldr (\g r -> pretty g <+> r) (text "") (fromTelescope gs)++instance Pretty Guard where+ pretty = \case+ GBool et -> text "if" <+> pretty (unembed et)+ GPat et p -> text "when" <+> pretty (unembed et) <+> text "is" <+> pretty p+ GLet b -> text "let" <+> pretty b++-- | Pretty-print a binding, i.e. a pairing of a name (with optional+-- type annotation) and term.+instance Pretty Binding where+ pretty = \case+ Binding Nothing x (unembed -> t) ->+ hsep [pretty x, text "=", pretty t]+ Binding (Just (unembed -> ty)) x (unembed -> t) ->+ hsep [pretty x, text ":", pretty ty, text "=", pretty t]++-- | Pretty-print the qualifiers in a comprehension.+instance Pretty (Telescope Qual) where+ pretty (fromTelescope -> qs) = do+ ds <- punctuate (text ",") (map pretty qs)+ hsep ds++-- | Pretty-print a single qualifier in a comprehension.+instance Pretty Qual where+ pretty = \case+ QBind x (unembed -> t) -> hsep [pretty x, text "in", pretty t]+ QGuard (unembed -> t) -> pretty t++-- | Pretty-print a pattern with guaranteed parentheses.+prettyPatternP :: Members '[LFresh, Reader PA] r => Pattern -> Sem r Doc+prettyPatternP p@PTup{} = setPA initPA $ pretty p+prettyPatternP p = withPA initPA $ pretty p++-- We could probably alternatively write a function to turn a pattern+-- back into a term, and pretty-print that instead of the below.+-- Unsure whether it would be worth it.++instance Pretty Pattern where+ pretty = \case+ PVar x -> pretty x+ PWild -> text "_"+ PAscr p ty -> withPA ascrPA $+ lt (pretty p) <+> text ":" <+> rt (pretty ty)+ PUnit -> text "■"+ PBool b -> text $ map toLower $ show b+ PChar c -> text (show c)+ PString s -> text (show s)+ PTup ts -> setPA initPA $ do+ ds <- punctuate (text ",") (map pretty ts)+ parens (hsep ds)+ PInj s p -> withPA funPA $+ pretty s <> prettyPatternP p+ PNat n -> integer n+ PCons p1 p2 -> withPA (getPA Cons) $+ lt (pretty p1) <+> text "::" <+> rt (pretty p2)+ PList ps -> setPA initPA $ do+ ds <- punctuate (text ",") (map pretty ps)+ brackets (hsep ds)+ PAdd L p t -> withPA (getPA Add) $+ lt (pretty p) <+> text "+" <+> rt (pretty t)+ PAdd R p t -> withPA (getPA Add) $+ lt (pretty t) <+> text "+" <+> rt (pretty p)+ PMul L p t -> withPA (getPA Mul) $+ lt (pretty p) <+> text "*" <+> rt (pretty t)+ PMul R p t -> withPA (getPA Mul) $+ lt (pretty t) <+> text "*" <+> rt (pretty p)+ PSub p t -> withPA (getPA Sub) $+ lt (pretty p) <+> text "-" <+> rt (pretty t)+ PNeg p -> withPA (ugetPA Neg) $+ text "-" <> rt (pretty p)+ PFrac p1 p2 -> withPA (getPA Div) $+ lt (pretty p1) <+> text "/" <+> rt (pretty p2)+
+ src/Disco/AST/Typed.hs view
@@ -0,0 +1,532 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE PatternSynonyms #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.AST.Typed+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Typed abstract syntax trees representing the typechecked surface+-- syntax of the Disco language. Each tree node is annotated with the+-- type of its subtree.+--+-----------------------------------------------------------------------------++module Disco.AST.Typed+ ( -- * Type-annotated terms+ ATerm+ , pattern ATVar+ , pattern ATPrim+ , pattern ATLet+ , pattern ATUnit+ , pattern ATBool+ , pattern ATNat+ , pattern ATRat+ , pattern ATChar+ , pattern ATString+ , pattern ATAbs+ , pattern ATApp+ , pattern ATTup+ , pattern ATCase+ , pattern ATChain+ , pattern ATTyOp+ , pattern ATContainer+ , pattern ATContainerComp+ , pattern ATList+ , pattern ATListComp+ , pattern ATTest++ , ALink+ , pattern ATLink++ , Container(..)+ , ABinding+ -- * Branches and guards+ , ABranch++ , AGuard+ , pattern AGBool+ , pattern AGPat+ , pattern AGLet++ , AQual+ , pattern AQBind+ , pattern AQGuard++ , APattern+ , pattern APVar+ , pattern APWild+ , pattern APUnit+ , pattern APBool+ , pattern APTup+ , pattern APInj+ , pattern APNat+ , pattern APChar+ , pattern APString+ , pattern APCons+ , pattern APList+ , pattern APAdd+ , pattern APMul+ , pattern APSub+ , pattern APNeg+ , pattern APFrac++ , pattern ABinding+ -- * Utilities+ , varsBound+ , getType+ , setType+ , substQT++ , AProperty+ )+ where++import Unbound.Generics.LocallyNameless+import Unbound.Generics.LocallyNameless.Unsafe++import Control.Arrow ((***))+import Data.Coerce (coerce)+import Data.Data (Data)+import Data.Void++import Control.Lens.Plated (transform)+import Disco.AST.Generic+import Disco.AST.Surface+import Disco.Names+import Disco.Pretty+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import Disco.Types++-- | The extension descriptor for Typed specific AST types.++data TY+ deriving Data++type AProperty = Property_ TY++------------------------------------------------------------++-- TODO: Should probably really do this with a 2-level/open recursion+-- approach, with a cofree comonad or whatever++-- | An @ATerm@ is a typechecked term where every node in the tree has+-- been annotated with the type of the subterm rooted at that node.++type ATerm = Term_ TY++type instance X_Binder TY = [APattern]++type instance X_TVar TY = Void -- Names are now qualified+type instance X_TPrim TY = Type+type instance X_TLet TY = Type+type instance X_TUnit TY = ()+type instance X_TBool TY = Type+type instance X_TNat TY = Type+type instance X_TRat TY = ()+type instance X_TChar TY = ()+type instance X_TString TY = ()+type instance X_TAbs TY = Type+type instance X_TApp TY = Type+type instance X_TCase TY = Type+type instance X_TChain TY = Type+type instance X_TTyOp TY = Type+type instance X_TContainer TY = Type+type instance X_TContainerComp TY = Type+type instance X_TAscr TY = Void -- No more type ascriptions in typechecked terms+type instance X_TTup TY = Type+type instance X_TParens TY = Void -- No more explicit parens++ -- A test frame for reporting counterexamples in a test. These don't appear+ -- in source programs, but because the deugarer manipulates partly-desugared+ -- terms it helps to be able to represent these in 'ATerm'.+type instance X_Term TY = Either ([(String, Type, Name ATerm)], ATerm) (Type, QName ATerm)++pattern ATVar :: Type -> QName ATerm -> ATerm+pattern ATVar ty qname = XTerm_ (Right (ty, qname))++pattern ATPrim :: Type -> Prim -> ATerm+pattern ATPrim ty name = TPrim_ ty name++pattern ATLet :: Type -> Bind (Telescope ABinding) ATerm -> ATerm+pattern ATLet ty bind = TLet_ ty bind++pattern ATUnit :: ATerm+pattern ATUnit = TUnit_ ()++pattern ATBool :: Type -> Bool -> ATerm+pattern ATBool ty bool = TBool_ ty bool++pattern ATNat :: Type -> Integer -> ATerm+pattern ATNat ty int = TNat_ ty int++pattern ATRat :: Rational -> ATerm+pattern ATRat rat = TRat_ () rat++pattern ATChar :: Char -> ATerm+pattern ATChar c = TChar_ () c++pattern ATString :: String -> ATerm+pattern ATString s = TString_ () s++pattern ATAbs :: Quantifier -> Type -> Bind [APattern] ATerm -> ATerm+pattern ATAbs q ty bind = TAbs_ q ty bind++pattern ATApp :: Type -> ATerm -> ATerm -> ATerm+pattern ATApp ty term1 term2 = TApp_ ty term1 term2++pattern ATTup :: Type -> [ATerm] -> ATerm+pattern ATTup ty termlist = TTup_ ty termlist++pattern ATCase :: Type -> [ABranch] -> ATerm+pattern ATCase ty branch = TCase_ ty branch++pattern ATChain :: Type -> ATerm -> [ALink] -> ATerm+pattern ATChain ty term linklist = TChain_ ty term linklist++pattern ATTyOp :: Type -> TyOp -> Type -> ATerm+pattern ATTyOp ty1 tyop ty2 = TTyOp_ ty1 tyop ty2++pattern ATContainer :: Type -> Container -> [(ATerm, Maybe ATerm)] -> Maybe (Ellipsis ATerm) -> ATerm+pattern ATContainer ty c tl mets = TContainer_ ty c tl mets++pattern ATContainerComp :: Type -> Container -> Bind (Telescope AQual) ATerm -> ATerm+pattern ATContainerComp ty c b = TContainerComp_ ty c b++pattern ATTest :: [(String, Type, Name ATerm)] -> ATerm -> ATerm+pattern ATTest ns t = XTerm_ (Left (ns, t))++{-# COMPLETE ATVar, ATPrim, ATLet, ATUnit, ATBool, ATNat, ATRat, ATChar,+ ATString, ATAbs, ATApp, ATTup, ATCase, ATChain, ATTyOp,+ ATContainer, ATContainerComp, ATTest #-}++pattern ATList :: Type -> [ATerm] -> Maybe (Ellipsis ATerm) -> ATerm+pattern ATList t xs e <- ATContainer t ListContainer (map fst -> xs) e+ where+ ATList t xs e = ATContainer t ListContainer (map (,Nothing) xs) e++pattern ATListComp :: Type -> Bind (Telescope AQual) ATerm -> ATerm+pattern ATListComp t b = ATContainerComp t ListContainer b++type ALink = Link_ TY++type instance X_TLink TY = ()++pattern ATLink :: BOp -> ATerm -> ALink+pattern ATLink bop term = TLink_ () bop term++{-# COMPLETE ATLink #-}+++type AQual = Qual_ TY++type instance X_QBind TY = ()+type instance X_QGuard TY = ()+++pattern AQBind :: Name ATerm -> Embed ATerm -> AQual+pattern AQBind namet embedt = QBind_ () namet embedt++pattern AQGuard :: Embed ATerm -> AQual+pattern AQGuard embedt = QGuard_ () embedt++{-# COMPLETE AQBind, AQGuard #-}++type ABinding = Binding_ TY++pattern ABinding :: Maybe (Embed PolyType) -> Name ATerm -> Embed ATerm -> ABinding+pattern ABinding m b n = Binding_ m b n++{-# COMPLETE ABinding #-}++type ABranch = Bind (Telescope AGuard) ATerm++type AGuard = Guard_ TY++type instance X_GBool TY = ()+type instance X_GPat TY = ()+type instance X_GLet TY = () -- ??? Type?++pattern AGBool :: Embed ATerm -> AGuard+pattern AGBool embedt = GBool_ () embedt++pattern AGPat :: Embed ATerm -> APattern -> AGuard+pattern AGPat embedt pat = GPat_ () embedt pat++pattern AGLet :: ABinding -> AGuard+pattern AGLet b = GLet_ () b++{-# COMPLETE AGBool, AGPat, AGLet #-}++type APattern = Pattern_ TY++-- We have to use Embed Type because we don't want any type variables+-- inside the types being treated as binders!++type instance X_PVar TY = Embed Type+type instance X_PWild TY = Embed Type+type instance X_PAscr TY = Void -- No more ascriptions in typechecked patterns.+type instance X_PUnit TY = ()+type instance X_PBool TY = ()+type instance X_PChar TY = ()+type instance X_PString TY = ()+type instance X_PTup TY = Embed Type+type instance X_PInj TY = Embed Type+type instance X_PNat TY = Embed Type+type instance X_PCons TY = Embed Type+type instance X_PList TY = Embed Type+type instance X_PAdd TY = Embed Type+type instance X_PMul TY = Embed Type+type instance X_PSub TY = Embed Type+type instance X_PNeg TY = Embed Type+type instance X_PFrac TY = Embed Type++type instance X_Pattern TY = ()++pattern APVar :: Type -> Name ATerm -> APattern+pattern APVar ty name <- PVar_ (unembed -> ty) name+ where+ APVar ty name = PVar_ (embed ty) name++pattern APWild :: Type -> APattern+pattern APWild ty <- PWild_ (unembed -> ty)+ where+ APWild ty = PWild_ (embed ty)++pattern APUnit :: APattern+pattern APUnit = PUnit_ ()++pattern APBool :: Bool -> APattern+pattern APBool b = PBool_ () b++pattern APChar :: Char -> APattern+pattern APChar c = PChar_ () c++pattern APString :: String -> APattern+pattern APString s = PString_ () s++pattern APTup :: Type -> [APattern] -> APattern+pattern APTup ty lp <- PTup_ (unembed -> ty) lp+ where+ APTup ty lp = PTup_ (embed ty) lp++pattern APInj :: Type -> Side -> APattern -> APattern+pattern APInj ty s p <- PInj_ (unembed -> ty) s p+ where+ APInj ty s p = PInj_ (embed ty) s p++pattern APNat :: Type -> Integer -> APattern+pattern APNat ty n <- PNat_ (unembed -> ty) n+ where+ APNat ty n = PNat_ (embed ty) n++pattern APCons :: Type -> APattern -> APattern -> APattern+pattern APCons ty p1 p2 <- PCons_ (unembed -> ty) p1 p2+ where+ APCons ty p1 p2 = PCons_ (embed ty) p1 p2++pattern APList :: Type -> [APattern] -> APattern+pattern APList ty lp <- PList_ (unembed -> ty) lp+ where+ APList ty lp = PList_ (embed ty) lp++pattern APAdd :: Type -> Side -> APattern -> ATerm -> APattern+pattern APAdd ty s p t <- PAdd_ (unembed -> ty) s p t+ where+ APAdd ty s p t = PAdd_ (embed ty) s p t++pattern APMul :: Type -> Side -> APattern -> ATerm -> APattern+pattern APMul ty s p t <- PMul_ (unembed -> ty) s p t+ where+ APMul ty s p t = PMul_ (embed ty) s p t++pattern APSub :: Type -> APattern -> ATerm -> APattern+pattern APSub ty p t <- PSub_ (unembed -> ty) p t+ where+ APSub ty p t = PSub_ (embed ty) p t++pattern APNeg :: Type -> APattern -> APattern+pattern APNeg ty p <- PNeg_ (unembed -> ty) p+ where+ APNeg ty p = PNeg_ (embed ty) p++pattern APFrac :: Type -> APattern -> APattern -> APattern+pattern APFrac ty p1 p2 <- PFrac_ (unembed -> ty) p1 p2+ where+ APFrac ty p1 p2 = PFrac_ (embed ty) p1 p2++{-# COMPLETE APVar, APWild, APUnit, APBool, APChar, APString,+ APTup, APInj, APNat, APCons, APList, APAdd, APMul, APSub, APNeg, APFrac #-}++varsBound :: APattern -> [(Name ATerm, Type)]+varsBound (APVar ty n) = [(n, ty)]+varsBound (APWild _) = []+varsBound APUnit = []+varsBound (APBool _) = []+varsBound (APChar _) = []+varsBound (APString _) = []+varsBound (APTup _ ps) = varsBound =<< ps+varsBound (APInj _ _ p) = varsBound p+varsBound (APNat _ _) = []+varsBound (APCons _ p q) = varsBound p ++ varsBound q+varsBound (APList _ ps) = varsBound =<< ps+varsBound (APAdd _ _ p _) = varsBound p+varsBound (APMul _ _ p _) = varsBound p+varsBound (APSub _ p _) = varsBound p+varsBound (APNeg _ p) = varsBound p+varsBound (APFrac _ p q) = varsBound p ++ varsBound q++------------------------------------------------------------+-- getType+------------------------------------------------------------++instance HasType ATerm where+ getType (ATVar ty _) = ty+ getType (ATPrim ty _) = ty+ getType ATUnit = TyUnit+ getType (ATBool ty _) = ty+ getType (ATNat ty _) = ty+ getType (ATRat _) = TyF+ getType (ATChar _) = TyC+ getType (ATString _) = TyList TyC+ getType (ATAbs _ ty _) = ty+ getType (ATApp ty _ _) = ty+ getType (ATTup ty _) = ty+ getType (ATTyOp ty _ _) = ty+ getType (ATChain ty _ _) = ty+ getType (ATContainer ty _ _ _) = ty+ getType (ATContainerComp ty _ _) = ty+ getType (ATLet ty _) = ty+ getType (ATCase ty _) = ty+ getType (ATTest _ _ ) = TyProp++ setType ty (ATVar _ x ) = ATVar ty x+ setType ty (ATPrim _ x ) = ATPrim ty x+ setType _ ATUnit = ATUnit+ setType ty (ATBool _ b) = ATBool ty b+ setType ty (ATNat _ x ) = ATNat ty x+ setType _ (ATRat r) = ATRat r+ setType _ (ATChar c) = ATChar c+ setType _ (ATString cs) = ATString cs+ setType ty (ATAbs q _ x ) = ATAbs q ty x+ setType ty (ATApp _ x y ) = ATApp ty x y+ setType ty (ATTup _ x ) = ATTup ty x+ setType ty (ATTyOp _ x y ) = ATTyOp ty x y+ setType ty (ATChain _ x y ) = ATChain ty x y+ setType ty (ATContainer _ x y z) = ATContainer ty x y z+ setType ty (ATContainerComp _ x y) = ATContainerComp ty x y+ setType ty (ATLet _ x ) = ATLet ty x+ setType ty (ATCase _ x ) = ATCase ty x+ setType _ (ATTest vs x) = ATTest vs x++instance HasType APattern where+ getType (APVar ty _) = ty+ getType (APWild ty) = ty+ getType APUnit = TyUnit+ getType (APBool _) = TyBool+ getType (APChar _) = TyC+ getType (APString _) = TyList TyC+ getType (APTup ty _) = ty+ getType (APInj ty _ _) = ty+ getType (APNat ty _) = ty+ getType (APCons ty _ _) = ty+ getType (APList ty _) = ty+ getType (APAdd ty _ _ _) = ty+ getType (APMul ty _ _ _) = ty+ getType (APSub ty _ _) = ty+ getType (APNeg ty _) = ty+ getType (APFrac ty _ _) = ty++instance HasType ABranch where+ getType = getType . snd . unsafeUnbind++------------------------------------------------------------+-- subst+------------------------------------------------------------++substQT :: QName ATerm -> ATerm -> ATerm -> ATerm+substQT x s = transform $ \case+ t@(ATVar _ y)+ | x == y -> s+ | otherwise -> t+ t -> t++------------------------------------------------------------+-- Exploding a typed term into a surface term with annotations+------------------------------------------------------------++instance Pretty ATerm where+ pretty = pretty . explode++explode :: ATerm -> Term+explode = \case+ ATVar ty x -> TAscr (TVar (coerce (qname x))) (toPolyType ty)+ ATPrim ty x -> TAscr (TPrim x) (toPolyType ty)+ ATLet ty tel -> TAscr (TLet (explodeTelescope explodeBinding tel)) (toPolyType ty)+ ATUnit -> TUnit+ ATBool _ty b -> TBool b+ ATNat ty x -> TAscr (TNat x) (toPolyType ty)+ ATRat r -> TRat r+ ATChar c -> TChar c+ ATString cs -> TString cs+ ATAbs q ty a -> TAscr (TAbs q (explodeAbs a)) (toPolyType ty)+ ATApp ty x y -> TAscr (TApp (explode x) (explode y)) (toPolyType ty)+ ATTup ty xs -> TAscr (TTup (map explode xs)) (toPolyType ty)+ ATCase ty bs -> TAscr (TCase (map explodeBranch bs)) (toPolyType ty)+ ATChain ty t ls -> TAscr (TChain (explode t) (map explodeLink ls)) (toPolyType ty)+ ATTyOp ty x y -> TAscr (TTyOp x y) (toPolyType ty)+ ATContainer ty c ts el ->+ TAscr+ (TContainer c (map (explode *** fmap explode) ts) (fmap (fmap explode) el))+ (toPolyType ty)+ ATContainerComp ty c b -> TAscr (TContainerComp c (explodeTelescope explodeQual b)) (toPolyType ty)+ ATTest _vs x -> TAscr (explode x) (toPolyType TyProp)++explodeTelescope+ :: (Alpha a, Alpha b)+ => (a -> b) -> Bind (Telescope a) ATerm -> Bind (Telescope b) Term+explodeTelescope explodeBinder (unsafeUnbind -> (xs,at)) = bind (mapTelescope explodeBinder xs) (explode at)++explodeBinding :: ABinding -> Binding+explodeBinding (ABinding m b (unembed -> n)) = Binding m (coerce b) (embed (explode n))++explodeAbs :: Bind [APattern] ATerm -> Bind [Pattern] Term+explodeAbs (unsafeUnbind -> (aps, at)) = bind (map explodePattern aps) (explode at)++explodePattern :: APattern -> Pattern+explodePattern = \case+ APVar ty x -> PAscr (PVar (coerce x)) ty+ APWild ty -> PAscr PWild ty+ APUnit -> PUnit+ APBool b -> PBool b+ APChar c -> PChar c+ APString s -> PString s+ APTup ty ps -> PAscr (PTup (map explodePattern ps)) ty+ APInj ty s p -> PAscr (PInj s (explodePattern p)) ty+ APNat ty n -> PAscr (PNat n) ty+ APCons ty p1 p2 -> PAscr (PCons (explodePattern p1) (explodePattern p2)) ty+ APList ty ps -> PAscr (PList (map explodePattern ps)) ty+ APAdd ty s p t -> PAscr (PAdd s (explodePattern p) (explode t)) ty+ APMul ty s p t -> PAscr (PMul s (explodePattern p) (explode t)) ty+ APSub ty p t -> PAscr (PSub (explodePattern p) (explode t)) ty+ APNeg ty p -> PAscr (PNeg (explodePattern p)) ty+ APFrac ty p q -> PAscr (PFrac (explodePattern p) (explodePattern q)) ty++explodeBranch :: ABranch -> Branch+explodeBranch = explodeTelescope explodeGuard++explodeGuard :: AGuard -> Guard+explodeGuard (AGBool (unembed -> at)) = GBool (embed (explode at))+explodeGuard (AGPat (unembed -> at) ap) = GPat (embed (explode at)) (explodePattern ap)+explodeGuard (AGLet ab) = GLet (explodeBinding ab)++explodeLink :: ALink -> Link+explodeLink (ATLink bop at) = TLink bop (explode at)++explodeQual :: AQual -> Qual+explodeQual (AQBind x (unembed -> at)) = QBind (coerce x) (embed (explode at))+explodeQual (AQGuard (unembed -> at)) = QGuard (embed (explode at))
+ src/Disco/Compile.hs view
@@ -0,0 +1,490 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Compile+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Compiling the typechecked, desugared AST to the untyped core+-- language.+-----------------------------------------------------------------------------++module Disco.Compile where++import Control.Monad ((<=<))+import Data.Bool (bool)+import Data.Coerce+import qualified Data.Map as M+import Data.Ratio+import Data.Set (Set)+import qualified Data.Set as S+import Data.Set.Lens (setOf)++import Disco.Effects.Fresh+import Polysemy (Member, Sem, run)+import Unbound.Generics.LocallyNameless (Name, bind, string2Name,+ unembed)++import Disco.AST.Core+import Disco.AST.Desugared+import Disco.AST.Generic+import Disco.AST.Typed+import Disco.Context as Ctx+import Disco.Desugar+import Disco.Module+import Disco.Names+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import qualified Disco.Typecheck.Graph as G+import Disco.Types+import Disco.Util++------------------------------------------------------------+-- Convenience operations+------------------------------------------------------------++-- | Utility function to desugar and compile a thing, given a+-- desugaring function for it.+compileThing :: (a -> Sem '[Fresh] DTerm) -> a -> Core+compileThing desugarThing = run . runFresh . (compileDTerm <=< desugarThing)++-- | Compile a typechecked term ('ATerm') directly to a 'Core' term,+-- by desugaring and then compiling.+compileTerm :: ATerm -> Core+compileTerm = compileThing desugarTerm++-- | Compile a typechecked property ('AProperty') directly to a 'Core' term,+-- by desugaring and then compilling.+compileProperty :: AProperty -> Core+compileProperty = compileThing desugarProperty++------------------------------------------------------------+-- Compiling definitions+------------------------------------------------------------++-- | Compile a context of typechecked definitions ('Defn') to a+-- sequence of compiled 'Core' bindings, such that the body of each+-- binding depends only on previous ones in the list. First+-- topologically sorts the definitions into mutually recursive+-- groups, then compiles recursive definitions specially in terms of+-- 'delay' and 'force'.+compileDefns :: Ctx ATerm Defn -> [(QName Core, Core)]+compileDefns defs = run . runFresh $ do+ let vars = Ctx.keysSet defs++ -- Get a list of pairs of the form (y,x) where x uses y in its+ -- definition. We want them in the order (y,x) since y needs to+ -- be evaluated before x. These will be the edges in our+ -- dependency graph. Note that some of these edges may refer to+ -- things that were imported, and hence not in the set of+ -- definitions; those edges will simply be dropped by G.mkGraph.+ deps :: Set (QName ATerm, QName ATerm)+ deps = S.unions . map (\(x, body) -> S.map (,x) (setOf (fvQ @Defn @ATerm) body)) . Ctx.assocs $ defs++ -- Do a topological sort of the condensation of the dependency+ -- graph. Each SCC corresponds to a group of mutually recursive+ -- definitions; each such group depends only on groups that come+ -- before it in the topsort.+ defnGroups :: [Set (QName ATerm)]+ defnGroups = G.topsort (G.condensation (G.mkGraph vars deps))++ concat <$> mapM (compileDefnGroup . Ctx.assocs . Ctx.restrictKeys defs) defnGroups++-- | Compile a group of mutually recursive definitions, using @delay@+-- to compile recursion via references to memory cells.+compileDefnGroup :: Member Fresh r => [(QName ATerm, Defn)] -> Sem r [(QName Core, Core)]+compileDefnGroup [(f, defn)]+ -- Informally, a recursive definition f = body compiles to+ --+ -- f = force (delay f. [force f / f] body).+ --+ -- However, we have to be careful: in the informal notation above,+ -- all the variables are named 'f', but in fully renamed syntax they+ -- are different. Writing fT for the top-level f bound in a+ -- specific module etc. and fL for a locally bound f, we really+ -- have+ --+ -- fT = force (delay fL. [force fL / fT] body)+ | f `S.member` setOf fvQ defn = return . (:[]) $+ (fT, CForce (CProj L (CDelay (bind [qname fL] [substQC fT (CForce (CVar fL)) cdefn]))))++ -- A non-recursive definition just compiles simply.+ | otherwise =+ return [(fT, cdefn)]++ where+ fT, fL :: QName Core+ fT = coerce f+ fL = localName (coerce (qname f))++ cdefn = compileThing desugarDefn defn++-- A group of mutually recursive definitions {f = fbody, g = gbody, ...}+-- compiles to+-- { _grp = delay fL gL ... . (forceVars fbody, forceVars gbody, ...)+-- , fT = fst (force _grp)+-- , gT = snd (force _grp)+-- , ...+-- }+-- where forceVars is the substitution [force fL / fT, force gL / gT, ...]++compileDefnGroup defs = do+ grp :: QName Core <- freshQ "__grp"+ let (vars, bodies) = unzip defs+ varsT, varsL :: [QName Core]+ varsT = coerce vars+ varsL = map (localName . qname) varsT+ forceVars :: [(QName Core, Core)]+ forceVars = zipWith (\t l -> (t, CForce (CVar l))) varsT varsL+ bodies' :: [Core]+ bodies' = map (substsQC forceVars . compileThing desugarDefn) bodies+ return $+ (grp, CDelay (bind (map qname varsL) bodies')) :+ zip varsT (for [0 ..] $ CForce . flip proj (CVar grp))+ where+ proj :: Int -> Core -> Core+ proj 0 = CProj L+ proj n = proj (n -1) . CProj R++------------------------------------------------------------+-- Compiling terms+------------------------------------------------------------++-- | Compile a typechecked, desugared 'DTerm' to an untyped 'Core'+-- term.+compileDTerm :: Member Fresh r => DTerm -> Sem r Core+compileDTerm (DTVar _ x) = return $ CVar (coerce x)+compileDTerm (DTPrim ty x) = compilePrim ty x+compileDTerm DTUnit = return CUnit+compileDTerm (DTBool _ b) = return $ CInj (bool L R b) CUnit+compileDTerm (DTChar c) = return $ CNum Fraction (toInteger (fromEnum c) % 1)+compileDTerm (DTNat _ n) = return $ CNum Fraction (n % 1) -- compileNat ty n+compileDTerm (DTRat r) = return $ CNum Decimal r+compileDTerm term@(DTAbs q _ _) = do+ (xs, tys, body) <- unbindDeep term+ cbody <- compileDTerm body+ case q of+ Lam -> return $ abstract xs cbody+ Ex -> return $ quantify (OExists tys) (abstract xs cbody)+ All -> return $ quantify (OForall tys) (abstract xs cbody)+ where+ -- Gather nested abstractions with the same quantifier.+ unbindDeep :: Member Fresh r => DTerm -> Sem r ([Name DTerm], [Type], DTerm)+ unbindDeep (DTAbs q' ty l) | q == q' = do+ (name, inner) <- unbind l+ (ns, tys, body) <- unbindDeep inner+ return (name : ns, ty : tys, body)+ unbindDeep t = return ([], [], t)++ abstract :: [Name DTerm] -> Core -> Core+ abstract xs body = CAbs (bind (map coerce xs) body)++ quantify :: Op -> Core -> Core+ quantify op = CApp (CConst op)++-- Special case for Cons, which compiles to a constructor application+-- rather than a function application.+compileDTerm (DTApp _ (DTPrim _ (PrimBOp Cons)) (DTPair _ t1 t2)) =+ CInj R <$> (CPair <$> compileDTerm t1 <*> compileDTerm t2)+-- Special cases for left and right, which also compile to constructor applications.+compileDTerm (DTApp _ (DTPrim _ PrimLeft) t) =+ CInj L <$> compileDTerm t+compileDTerm (DTApp _ (DTPrim _ PrimRight) t) =+ CInj R <$> compileDTerm t+compileDTerm (DTApp _ t1 t2) = CApp <$> compileDTerm t1 <*> compileDTerm t2+compileDTerm (DTPair _ t1 t2) =+ CPair <$> compileDTerm t1 <*> compileDTerm t2+compileDTerm (DTCase _ bs) = CApp <$> compileCase bs <*> pure CUnit+compileDTerm (DTTyOp _ op ty) = return $ CApp (CConst (tyOps ! op)) (CType ty)+ where+ tyOps =+ M.fromList+ [ Enumerate ==> OEnum,+ Count ==> OCount+ ]+compileDTerm (DTNil _) = return $ CInj L CUnit+compileDTerm (DTTest info t) = CTest (coerce info) <$> compileDTerm t++------------------------------------------------------------++-- | Compile a natural number. A separate function is needed in+-- case the number is of a finite type, in which case we must+-- mod it by its type.+-- compileNat :: Member Fresh r => Type -> Integer -> Sem r Core+-- compileNat (TyFin n) x = return $ CNum Fraction ((x `mod` n) % 1)+-- compileNat _ x = return $ CNum Fraction (x % 1)++------------------------------------------------------------++-- | Compile a primitive. Typically primitives turn into a+-- corresponding function constant in the core language, but+-- sometimes the particular constant it turns into may depend on the+-- type.+compilePrim :: Member Fresh r => Type -> Prim -> Sem r Core+compilePrim (argTy :->: _) (PrimUOp uop) = return $ compileUOp argTy uop+compilePrim ty p@(PrimUOp _) = compilePrimErr p ty+-- This special case for Cons only triggers if we didn't hit the case+-- for fully saturated Cons; just fall back to generating a lambda. Have to+-- do it here, not in compileBOp, since we need to generate fresh names.+compilePrim _ (PrimBOp Cons) = do+ hd <- fresh (string2Name "hd")+ tl <- fresh (string2Name "tl")+ return $ CAbs $ bind [hd, tl] $ CInj R (CPair (CVar (localName hd)) (CVar (localName tl)))++compilePrim _ PrimLeft = do+ a <- fresh (string2Name "a")+ return $ CAbs $ bind [a] $ CInj L (CVar (localName a))++compilePrim _ PrimRight = do+ a <- fresh (string2Name "a")+ return $ CAbs $ bind [a] $ CInj R (CVar (localName a))++compilePrim (ty1 :*: ty2 :->: resTy) (PrimBOp bop) = return $ compileBOp ty1 ty2 resTy bop+compilePrim ty p@(PrimBOp _) = compilePrimErr p ty+compilePrim _ PrimSqrt = return $ CConst OSqrt+compilePrim _ PrimFloor = return $ CConst OFloor+compilePrim _ PrimCeil = return $ CConst OCeil+compilePrim _ PrimAbs = return $ CConst OAbs+compilePrim _ PrimSize = return $ CConst OSize+compilePrim (TySet _ :->: _) PrimPower = return $ CConst OPower+compilePrim (TyBag _ :->: _) PrimPower = return $ CConst OPower+compilePrim ty PrimPower = compilePrimErr PrimPower ty+compilePrim (TySet _ :->: _) PrimList = return $ CConst OSetToList+compilePrim (TyBag _ :->: _) PrimSet = return $ CConst OBagToSet+compilePrim (TyBag _ :->: _) PrimList = return $ CConst OBagToList+compilePrim (TyList _ :->: _) PrimSet = return $ CConst OListToSet+compilePrim (TyList _ :->: _) PrimBag = return $ CConst OListToBag+compilePrim _ p | p `elem` [PrimList, PrimBag, PrimSet] = return $ CConst OId+compilePrim ty PrimList = compilePrimErr PrimList ty+compilePrim ty PrimBag = compilePrimErr PrimBag ty+compilePrim ty PrimSet = compilePrimErr PrimSet ty+compilePrim _ PrimB2C = return $ CConst OBagToCounts+compilePrim (_ :->: TyBag _) PrimC2B = return $ CConst OCountsToBag+compilePrim ty PrimC2B = compilePrimErr PrimC2B ty+compilePrim (TyMap _ _ :->: _) PrimMapToSet = return $ CConst OMapToSet+compilePrim (_ :->: TyMap _ _) PrimSetToMap = return $ CConst OSetToMap+compilePrim ty PrimMapToSet = compilePrimErr PrimMapToSet ty+compilePrim ty PrimSetToMap = compilePrimErr PrimSetToMap ty+compilePrim _ PrimSummary = return $ CConst OSummary+compilePrim (_ :->: TyGraph _) PrimVertex = return $ CConst OVertex+compilePrim (TyGraph _) PrimEmptyGraph = return $ CConst OEmptyGraph+compilePrim (_ :->: TyGraph _) PrimOverlay = return $ CConst OOverlay+compilePrim (_ :->: TyGraph _) PrimConnect = return $ CConst OConnect+compilePrim ty PrimVertex = compilePrimErr PrimVertex ty+compilePrim ty PrimEmptyGraph = compilePrimErr PrimEmptyGraph ty+compilePrim ty PrimOverlay = compilePrimErr PrimOverlay ty+compilePrim ty PrimConnect = compilePrimErr PrimConnect ty+compilePrim _ PrimInsert = return $ CConst OInsert+compilePrim _ PrimLookup = return $ CConst OLookup+compilePrim (_ :*: TyList _ :->: _) PrimEach = return $+ CVar (Named Stdlib "list" .- string2Name "eachlist")+compilePrim (_ :*: TyBag _ :->: TyBag _) PrimEach = return $ CConst OEachBag+compilePrim (_ :*: TySet _ :->: TySet _) PrimEach = return $ CConst OEachSet+compilePrim ty PrimEach = compilePrimErr PrimEach ty+compilePrim (_ :*: _ :*: TyList _ :->: _) PrimReduce =+ return $ CVar (Named Stdlib "list" .- string2Name "foldr")+compilePrim (_ :*: _ :*: TyBag _ :->: _) PrimReduce = return $+ CVar (Named Stdlib "container" .- string2Name "reducebag")+compilePrim (_ :*: _ :*: TySet _ :->: _) PrimReduce = return $+ CVar (Named Stdlib "container" .- string2Name "reduceset")+compilePrim ty PrimReduce = compilePrimErr PrimReduce ty+compilePrim (_ :*: TyList _ :->: _) PrimFilter = return $+ CVar (Named Stdlib "list" .- string2Name "filterlist")+compilePrim (_ :*: TyBag _ :->: _) PrimFilter = return $ CConst OFilterBag+compilePrim (_ :*: TySet _ :->: _) PrimFilter = return $ CConst OFilterBag+compilePrim ty PrimFilter = compilePrimErr PrimFilter ty+compilePrim (_ :->: TyList _) PrimJoin = return $+ CVar (Named Stdlib "list" .- string2Name "concat")+compilePrim (_ :->: TyBag _) PrimJoin = return $ CConst OBagUnions+compilePrim (_ :->: TySet _) PrimJoin = return $+ CVar (Named Stdlib "container" .- string2Name "unions")+compilePrim ty PrimJoin = compilePrimErr PrimJoin ty++compilePrim (_ :*: TyBag _ :*: _ :->: _) PrimMerge = return $ CConst OMerge+compilePrim (_ :*: TySet _ :*: _ :->: _) PrimMerge = return $ CConst OMerge+compilePrim ty PrimMerge = compilePrimErr PrimMerge ty++compilePrim _ PrimIsPrime = return $ CConst OIsPrime+compilePrim _ PrimFactor = return $ CConst OFactor+compilePrim _ PrimFrac = return $ CConst OFrac+compilePrim _ PrimCrash = return $ CConst OCrash+compilePrim _ PrimUntil = return $ CConst OUntil+compilePrim _ PrimHolds = return $ CConst OHolds+compilePrim _ PrimLookupSeq = return $ CConst OLookupSeq+compilePrim _ PrimExtendSeq = return $ CConst OExtendSeq++compilePrimErr :: Prim -> Type -> a+compilePrimErr p ty = error $ "Impossible! compilePrim " ++ show p ++ " on bad type " ++ show ty++------------------------------------------------------------+-- Case expressions+------------------------------------------------------------++-- | Compile a case expression of type τ to a core language expression+-- of type (Unit → τ), in order to delay evaluation until explicitly+-- applying it to the unit value.+compileCase :: Member Fresh r => [DBranch] -> Sem r Core+compileCase [] = return $ CAbs (bind [string2Name "_"] (CConst OMatchErr))+-- empty case ==> λ _ . error++compileCase (b : bs) = do+ c1 <- compileBranch b+ c2 <- compileCase bs+ return $ CAbs (bind [string2Name "_"] (CApp c1 c2))++-- | Compile a branch of a case expression of type τ to a core+-- language expression of type (Unit → τ) → τ. The idea is that it+-- takes a failure continuation representing the subsequent branches+-- in the case expression. If the branch succeeds, it just returns+-- the associated expression of type τ; if it fails, it calls the+-- continuation to proceed with the case analysis.+compileBranch :: Member Fresh r => DBranch -> Sem r Core+compileBranch b = do+ (gs, e) <- unbind b+ c <- compileDTerm e+ k <- fresh (string2Name "k") -- Fresh name for the failure continuation+ bc <- compileGuards (fromTelescope gs) k c+ return $ CAbs (bind [k] bc)++-- | 'compileGuards' takes a list of guards, the name of the failure+-- continuation of type (Unit → τ), and a Core term of type τ to+-- return in the case of success, and compiles to an expression of+-- type τ which evaluates the guards in sequence, ultimately+-- returning the given expression if all guards succeed, or calling+-- the failure continuation at any point if a guard fails.+compileGuards :: Member Fresh r => [DGuard] -> Name Core -> Core -> Sem r Core+compileGuards [] _ e = return e+compileGuards (DGPat (unembed -> s) p : gs) k e = do+ e' <- compileGuards gs k e+ s' <- compileDTerm s+ compileMatch p s' k e'++-- | 'compileMatch' takes a pattern, the compiled scrutinee, the name+-- of the failure continuation, and a Core term representing the+-- compilation of any guards which come after this one, and returns+-- a Core expression of type τ that performs the match and either+-- calls the failure continuation in the case of failure, or the+-- rest of the guards in the case of success.+compileMatch :: Member Fresh r => DPattern -> Core -> Name Core -> Core -> Sem r Core+compileMatch (DPVar _ x) s _ e = return $ CApp (CAbs (bind [coerce x] e)) s+-- Note in the below two cases that we can't just discard s since+-- that would result in a lazy semantics. With an eager/strict+-- semantics, we have to make sure s gets evaluated even if its+-- value is then discarded.+compileMatch (DPWild _) s _ e = return $ CApp (CAbs (bind [string2Name "_"] e)) s+compileMatch DPUnit s _ e = return $ CApp (CAbs (bind [string2Name "_"] e)) s+compileMatch (DPPair _ x1 x2) s _ e = do+ y <- fresh (string2Name "y")++ -- {? e when s is (x1,x2) ?} ==> (\y. (\x1.\x2. e) (fst y) (snd y)) s+ return $+ CApp+ ( CAbs+ ( bind+ [y]+ ( CApp+ ( CApp+ (CAbs (bind [coerce x1, coerce x2] e))+ (CProj L (CVar (localName y)))+ )+ (CProj R (CVar (localName y)))+ )+ )+ )+ s+compileMatch (DPInj _ L x) s k e =+ -- {? e when s is left(x) ?} ==> case s of {left x -> e; right _ -> k unit}+ return $ CCase s (bind (coerce x) e) (bind (string2Name "_") (CApp (CVar (localName k)) CUnit))+compileMatch (DPInj _ R x) s k e =+ -- {? e when s is right(x) ?} ==> case s of {left _ -> k unit; right x -> e}+ return $ CCase s (bind (string2Name "_") (CApp (CVar (localName k)) CUnit)) (bind (coerce x) e)++------------------------------------------------------------+-- Unary and binary operators+------------------------------------------------------------++-- | Compile a unary operator.+compileUOp ::+ -- | Type of the operator argument+ Type ->+ UOp ->+ Core+compileUOp _ op = CConst (coreUOps ! op)+ where+ -- Just look up the corresponding core operator.+ coreUOps =+ M.fromList+ [ Neg ==> ONeg,+ Fact ==> OFact+ ]++-- | Compile a binary operator. This function needs to know the types+-- of the arguments and result since some operators are overloaded+-- and compile to different code depending on their type.+--+-- @arg1 ty -> arg2 ty -> result ty -> op -> result@+compileBOp :: Type -> Type -> Type -> BOp -> Core+-- First, compile some operators specially for modular arithmetic.+-- Most operators on TyFun (add, mul, sub, etc.) have already been+-- desugared to an operation followed by a mod. The only operators+-- here are the ones that have a special runtime behavior for Zn that+-- can't be implemented in terms of other, existing operators:+--+-- - Division on Zn needs to find modular inverses.+-- - Divisibility testing on Zn similarly needs to find a gcd etc.+-- - Exponentiation on Zn could in theory be implemented as a normal+-- exponentiation on naturals followed by a mod, but that would be+-- silly and inefficient. Instead we compile to a special modular+-- exponentiation operator which takes mods along the way. Also,+-- negative powers have similar requirements to division.+--+-- We match on the type of arg1 because that is the only one which+-- will consistently be TyFin in the case of Div, Exp, and Divides.+-- compileBOp (TyFin n) _ _ op+-- | op `elem` [Div, Exp, Divides]+-- = CConst ((omOps ! op) n)+-- where+-- omOps = M.fromList+-- [ Div ==> OMDiv+-- , Exp ==> OMExp+-- , Divides ==> OMDivides+-- ]++-- Graph operations are separate, but use the same syntax, as traditional+-- addition and multiplication.+compileBOp (TyGraph _) (TyGraph _) (TyGraph _) op+ | op `elem` [Add, Mul] =+ CConst (regularOps ! op)+ where+ regularOps =+ M.fromList+ [ Add ==> OOverlay,+ Mul ==> OConnect+ ]++-- Some regular arithmetic operations that just translate straightforwardly.+compileBOp _ _ _ op+ | op `M.member` regularOps = CConst (regularOps ! op)+ where+ regularOps =+ M.fromList+ [ Add ==> OAdd,+ Mul ==> OMul,+ Div ==> ODiv,+ Exp ==> OExp,+ Mod ==> OMod,+ Divides ==> ODivides,+ Choose ==> OMultinom,+ Eq ==> OEq,+ Lt ==> OLt+ ]++-- ShouldEq needs to know the type at which the comparison is+-- occurring, so values can be correctly pretty-printed if the test+-- fails.+compileBOp ty _ _ ShouldEq = CConst (OShouldEq ty)+compileBOp _ty (TyList _) _ Elem = CConst OListElem+compileBOp _ty _ _ Elem = CConst OBagElem+compileBOp ty1 ty2 resTy op =+ error $ "Impossible! missing case in compileBOp: " ++ show (ty1, ty2, resTy, op)
+ src/Disco/Context.hs view
@@ -0,0 +1,232 @@+{-# LANGUAGE DeriveTraversable #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Context+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- A *context* is a mapping from names to other things (such as types+-- or values). This module defines a generic type of contexts which+-- is used in many different places throughout the disco codebase.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Context+ ( -- * Context type+ Ctx++ -- * Construction+ , emptyCtx+ , singleCtx+ , fromList+ , ctxForModule+ , localCtx++ -- * Insertion+ , insert+ , extend+ , extends++ -- * Query+ , null+ , lookup, lookup'+ , lookupNonLocal, lookupNonLocal'+ , lookupAll, lookupAll'++ -- * Conversion+ , names+ , elems+ , assocs+ , keysSet++ -- * Traversal+ , coerceKeys+ , restrictKeys++ -- * Combination+ , joinCtx+ , joinCtxs++ -- * Filter+ , filter++ ) where++import Control.Monad ((<=<))+import Data.Bifunctor (first, second)+import Data.Coerce+import Data.Map (Map)+import qualified Data.Map as M+import Data.Map.Merge.Lazy as MM+import Data.Set (Set)+import qualified Data.Set as S+import Prelude hiding (filter, lookup, null)++import Unbound.Generics.LocallyNameless (Name)++import Polysemy+import Polysemy.Reader++import Disco.Names (ModuleName,+ NameProvenance (..),+ QName (..))++-- | A context maps qualified names to things. In particular a @Ctx a+-- b@ maps qualified names for @a@s to values of type @b@.+newtype Ctx a b = Ctx { getCtx :: M.Map NameProvenance (M.Map (Name a) b) }+ deriving (Eq, Show, Functor, Foldable, Traversable)++ -- Note that we implement a context as a nested map from+ -- NameProvenance to Name to b, rather than as a Map QName b. They+ -- are isomorphic, but this way it is easier to do name resolution,+ -- because given an (unqualified) Name, we can look it up in each+ -- inner map corresponding to modules that are in scope.++instance Semigroup (Ctx a b) where+ (<>) = joinCtx++instance Monoid (Ctx a b) where+ mempty = emptyCtx+ mappend = (<>)++------------------------------------------------------------+-- Construction+------------------------------------------------------------++-- | The empty context.+emptyCtx :: Ctx a b+emptyCtx = Ctx M.empty++-- | A singleton context, mapping a qualified name to a thing.+singleCtx :: QName a -> b -> Ctx a b+singleCtx (QName p n) = Ctx . M.singleton p . M.singleton n++-- | Create a context from a list of (qualified name, value) pairs.+fromList :: [(QName a, b)] -> Ctx a b+fromList = Ctx . M.fromListWith M.union . map (\(QName p n, b) -> (p, M.singleton n b))++-- | Create a context for bindings from a single module.+ctxForModule :: ModuleName -> [(Name a, b)] -> Ctx a b+ctxForModule m = Ctx . M.singleton (QualifiedName m) . M.fromList++-- | Create a context with local bindings.+localCtx :: [(Name a, b)] -> Ctx a b+localCtx = Ctx . M.singleton LocalName . M.fromList++------------------------------------------------------------+-- Insertion+------------------------------------------------------------++-- | Insert a new binding into a context. The new binding shadows any+-- old binding for the same qualified name.+insert :: QName a -> b -> Ctx a b -> Ctx a b+insert (QName p n) b = Ctx . M.insertWith M.union p (M.singleton n b) . getCtx++-- | Run a computation under a context extended with a new binding.+-- The new binding shadows any old binding for the same name.+extend :: Member (Reader (Ctx a b)) r => QName a -> b -> Sem r c -> Sem r c+extend qn b = local (insert qn b)++-- | Run a computation in a context extended with an additional+-- context. Bindings in the additional context shadow any bindings+-- with the same names in the existing context.+extends :: Member (Reader (Ctx a b)) r => Ctx a b -> Sem r c -> Sem r c+extends = local . joinCtx++------------------------------------------------------------+-- Query+------------------------------------------------------------++-- | Check if a context is empty.+null :: Ctx a b -> Bool+null = all M.null . getCtx++-- | Look up a qualified name in an ambient context.+lookup :: Member (Reader (Ctx a b)) r => QName a -> Sem r (Maybe b)+lookup x = lookup' x <$> ask++-- | Look up a qualified name in a context.+lookup' :: QName a -> Ctx a b -> Maybe b+lookup' (QName p n) = (M.lookup n <=< M.lookup p) . getCtx++-- | Look up all the non-local bindings of a name in an ambient context.+lookupNonLocal :: Member (Reader (Ctx a b)) r => Name a -> Sem r [(ModuleName, b)]+lookupNonLocal n = lookupNonLocal' n <$> ask++-- | Look up all the non-local bindings of a name in a context.+lookupNonLocal' :: Name a -> Ctx a b -> [(ModuleName, b)]+lookupNonLocal' n = nonLocal . lookupAll' n+ where+ nonLocal bs = [(m,b) | (QName (QualifiedName m) _, b) <- bs]++-- | Look up all the bindings of an (unqualified) name in an ambient context.+lookupAll :: Member (Reader (Ctx a b)) r => Name a -> Sem r [(QName a, b)]+lookupAll n = lookupAll' n <$> ask++-- | Look up all the bindings of an (unqualified) name in a context.+lookupAll' :: Name a -> Ctx a b -> [(QName a, b)]+lookupAll' n = map (first (`QName` n)) . M.assocs . M.mapMaybe (M.lookup n) . getCtx++------------------------------------------------------------+-- Conversion+------------------------------------------------------------++-- | Return a list of the names defined by the context.+names :: Ctx a b -> [Name a]+names = concatMap M.keys . M.elems . getCtx++-- | Return a list of all the values bound by the context.+elems :: Ctx a b -> [b]+elems = concatMap M.elems . M.elems . getCtx++-- | Return a list of the qualified name-value associations in the+-- context.+assocs :: Ctx a b -> [(QName a, b)]+assocs = concatMap (uncurry modAssocs) . M.assocs . getCtx+ where+ modAssocs :: NameProvenance -> Map (Name a) b -> [(QName a, b)]+ modAssocs p = map (first (QName p)) . M.assocs++-- | Return a set of all qualified names in the context.+keysSet :: Ctx a b -> Set (QName a)+keysSet = S.unions . map (uncurry (S.map . QName) . second M.keysSet) . M.assocs . getCtx++------------------------------------------------------------+-- Traversal+------------------------------------------------------------++-- | Coerce the type of the qualified name keys in a context.+coerceKeys :: Ctx a1 b -> Ctx a2 b+coerceKeys = Ctx . M.map (M.mapKeys coerce) . getCtx++-- | Restrict a context to only the keys in the given set.+restrictKeys :: Ctx a b -> Set (QName a) -> Ctx a b+restrictKeys ctx xs = Ctx . restrict m . getCtx $ ctx+ where+ restrict = MM.merge MM.dropMissing MM.dropMissing (MM.zipWithMatched (\_ ns m' -> M.restrictKeys m' ns))+ m = M.fromListWith S.union . map (\(QName p n) -> (p, S.singleton n)) . S.toList $ xs++------------------------------------------------------------+-- Combination+------------------------------------------------------------++-- | Join two contexts (left-biased, /i.e./ if the same qualified name+-- exists in both contexts, the result will use the value from the+-- first context, and throw away the value from the second.).+joinCtx :: Ctx a b -> Ctx a b -> Ctx a b+joinCtx a b = joinCtxs [a,b]++-- | Join a list of contexts (left-biased).+joinCtxs :: [Ctx a b] -> Ctx a b+joinCtxs = Ctx . M.unionsWith M.union . map getCtx++------------------------------------------------------------+-- Filter+------------------------------------------------------------++-- | Filter a context using a predicate.+filter :: (b -> Bool) -> Ctx a b -> Ctx a b+filter p = Ctx . M.map (M.filter p) . getCtx
+ src/Disco/Data.hs view
@@ -0,0 +1,32 @@+{-# OPTIONS_GHC -Wno-orphans #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE StandaloneDeriving #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Data+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- Some orphan 'Data' instances.+--+-----------------------------------------------------------------------------++module Disco.Data where++import Unbound.Generics.LocallyNameless.Bind+import Unbound.Generics.LocallyNameless.Embed+import Unbound.Generics.LocallyNameless.Name++import Data.Data (Data)+import Unbound.Generics.LocallyNameless.Rebind++------------------------------------------------------------+-- Some orphan instances+------------------------------------------------------------++deriving instance (Data a, Data b) => Data (Bind a b)+deriving instance Data t => Data (Embed t)+deriving instance (Data a, Data b) => Data (Rebind a b)+deriving instance Data a => Data (Name a)+
+ src/Disco/Desugar.hs view
@@ -0,0 +1,822 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Desugar+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Desugaring the typechecked surface language to a (still typed)+-- simpler language.+--+-----------------------------------------------------------------------------++module Disco.Desugar+ ( -- * Running desugaring computations+ runDesugar++ -- * Programs, terms, and properties+ , desugarDefn, desugarTerm, desugarProperty++ -- * Case expressions and patterns+ , desugarBranch, desugarGuards+ )+ where++import Control.Monad.Cont+import Data.Bool (bool)+import Data.Coerce+import Data.Maybe (fromMaybe, isJust)++import Disco.AST.Desugared+import Disco.AST.Surface+import Disco.AST.Typed+import Disco.Module+import Disco.Names+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import Disco.Typecheck (containerTy)+import Disco.Types++import Disco.Effects.Fresh+import Polysemy (Member, Sem, run)+import Unbound.Generics.LocallyNameless (Bind, Name, bind,+ embed, name2String,+ string2Name, unembed,+ unrebind)+import Unbound.Generics.LocallyNameless.Unsafe (unsafeUnbind)++------------------------------------------------------------+-- Running desugaring computations+------------------------------------------------------------++-- | Run a desugaring computation.+runDesugar :: Sem '[Fresh] a -> a+runDesugar = run . runFresh1+ -- Using runFresh1 is a bit of a hack; that way we won't+ -- ever pick a name with #0 (which is what is generated by default+ -- by string2Name), hence won't conflict with any existing free+ -- variables which came from the parser.++------------------------------------------------------------+-- ATerm DSL+------------------------------------------------------------++-- A tiny DSL for building certain ATerms, which is helpful for+-- writing desugaring rules.++-- Make a local ATVar.+atVar :: Type -> Name ATerm -> ATerm+atVar ty x = ATVar ty (QName LocalName x)++tapp :: ATerm -> ATerm -> ATerm+tapp t1 t2 = ATApp resTy t1 t2+ where+ resTy = case getType t1 of+ (_ :->: r) -> r+ ty -> error $ "Impossible! Got non-function type " ++ show ty ++ " in tapp"++mkBin :: Type -> BOp -> ATerm -> ATerm -> ATerm+mkBin resTy bop t1 t2+ = tapp (ATPrim (getType t1 :*: getType t2 :->: resTy) (PrimBOp bop)) (mkPair t1 t2)++mkUn :: Type -> UOp -> ATerm -> ATerm+mkUn resTy uop t = tapp (ATPrim (getType t :->: resTy) (PrimUOp uop)) t++mkPair :: ATerm -> ATerm -> ATerm+mkPair t1 t2 = mkTup [t1,t2]++mkTup :: [ATerm] -> ATerm+mkTup ts = ATTup (foldr1 (:*:) (map getType ts)) ts++tapps :: ATerm -> [ATerm] -> ATerm+tapps t ts = tapp t (mkTup ts)++infixr 2 ||.+(||.) :: ATerm -> ATerm -> ATerm+(||.) = mkBin TyBool Or++infixl 6 -., +.+(-.) :: ATerm -> ATerm -> ATerm+at1 -. at2 = mkBin (getType at1) Sub at1 at2++(+.) :: ATerm -> ATerm -> ATerm+at1 +. at2 = mkBin (getType at1) Add at1 at2++infixl 7 /.+(/.) :: ATerm -> ATerm -> ATerm+at1 /. at2 = mkBin (getType at1) Div at1 at2++infix 4 <., >=.+(<.) :: ATerm -> ATerm -> ATerm+(<.) = mkBin TyBool Lt++(>=.) :: ATerm -> ATerm -> ATerm+(>=.) = mkBin TyBool Geq++(|.) :: ATerm -> ATerm -> ATerm+(|.) = mkBin TyBool Divides++infix 4 ==.+(==.) :: ATerm -> ATerm -> ATerm+(==.) = mkBin TyBool Eq++tnot :: ATerm -> ATerm+tnot = tapp (ATPrim (TyBool :->: TyBool) (PrimUOp Not))++(<==.) :: ATerm -> [AGuard] -> ABranch+t <==. gs = bind (toTelescope gs) t++fls :: ATerm+fls = ATBool TyBool False++tru :: ATerm+tru = ATBool TyBool True++tif :: ATerm -> AGuard+tif t = AGBool (embed t)++ctrNil :: Container -> Type -> ATerm+ctrNil ctr ty = ATContainer (containerTy ctr ty) ctr [] Nothing++ctrSingleton :: Container -> ATerm -> ATerm+ctrSingleton ctr t = ATContainer (containerTy ctr (getType t)) ctr [(t, Nothing)] Nothing++------------------------------------------------------------+-- Making DTerms+------------------------------------------------------------++dtVar :: Type -> Name DTerm -> DTerm+dtVar ty x = DTVar ty (QName LocalName x)++dtapp :: DTerm -> DTerm -> DTerm+dtapp t1 t2 = DTApp resTy t1 t2+ where+ resTy = case getType t1 of+ (_ :->: r) -> r+ ty -> error $ "Impossible! Got non-function type " ++ show ty ++ " in dtapp"++dtbin :: Type -> Prim -> DTerm -> DTerm -> DTerm+dtbin resTy p dt1 dt2+ = dtapp (DTPrim (getType dt1 :*: getType dt2 :->: resTy) p) (mkDTPair dt1 dt2)++mkDTPair :: DTerm -> DTerm -> DTerm+mkDTPair dt1 dt2 = DTPair (getType dt1 :*: getType dt2) dt1 dt2++------------------------------------------------------------+-- Definition desugaring+------------------------------------------------------------++-- | Desugar a definition (consisting of a collection of pattern+-- clauses with bodies) into a core language term.+desugarDefn :: Member Fresh r => Defn -> Sem r DTerm+desugarDefn (Defn _ patTys bodyTy def) =+ desugarAbs Lam (foldr (:->:) bodyTy patTys) def++------------------------------------------------------------+-- Abstraction desugaring+------------------------------------------------------------++-- | Desugar an abstraction -- that is, a collection of clauses+-- with their corresponding patterns. Definitions are abstractions+-- (which happen to be named), and source-level lambdas are also+-- abstractions (which happen to have only one clause).++desugarAbs :: Member Fresh r => Quantifier -> Type -> [Clause] -> Sem r DTerm+-- Special case for compiling a single lambda with no pattern matching directly to a lambda+desugarAbs Lam ty [cl@(unsafeUnbind -> ([APVar _ _], _))] = do+ (ps, at) <- unbind cl+ d <- desugarTerm at+ return $ DTAbs Lam ty (bind (getVar (head ps)) d)+ where+ getVar (APVar _ x) = coerce x+-- General case+desugarAbs quant overallTy body = do+ clausePairs <- unbindClauses body+ let (pats, bodies) = unzip clausePairs+ let patTys = map getType (head pats)+ let bodyTy = getType (head bodies)++ -- generate dummy variables for lambdas+ args <- zipWithM (\_ i -> fresh (string2Name ("arg" ++ show i))) (head pats) [0 :: Int ..]++ -- Create lambdas and one big case. Recursively desugar the case to+ -- deal with arithmetic patterns.+ let branches = zipWith (mkBranch (zip args patTys)) bodies pats+ dcase <- desugarTerm $ ATCase bodyTy branches+ return $ mkAbs quant overallTy patTys (coerce args) dcase++ where+ mkBranch :: [(Name ATerm, Type)] -> ATerm -> [APattern] -> ABranch+ mkBranch xs b ps = bind (mkGuards xs ps) b++ mkGuards :: [(Name ATerm, Type)] -> [APattern] -> Telescope AGuard+ mkGuards xs ps = toTelescope $ zipWith AGPat (map (\(x,ty) -> embed (atVar ty x)) xs) ps++ -- To make searches fairer, we lift up directly nested abstractions+ -- with the same quantifier when there's only a single clause. That+ -- way, we generate a chain of abstractions followed by a case, instead+ -- of a bunch of alternating abstractions and cases.+ unbindClauses :: Member Fresh r => [Clause] -> Sem r [([APattern], ATerm)]+ unbindClauses [c] | quant `elem` [All, Ex] = do+ (ps, t) <- liftClause c+ return [(ps, addDbgInfo ps t)]+ unbindClauses cs = mapM unbind cs++ liftClause :: Member Fresh r => Bind [APattern] ATerm -> Sem r ([APattern], ATerm)+ liftClause c = unbind c >>= \case+ (ps, ATAbs q _ c') | q == quant -> do+ (ps', b) <- liftClause c'+ return (ps ++ ps', b)+ (ps, b) -> return (ps, b)++ -- Wrap a term in a test frame to report the values of all variables+ -- bound in the patterns.+ addDbgInfo :: [APattern] -> ATerm -> ATerm+ addDbgInfo ps t = ATTest (map withName $ concatMap varsBound ps) t+ where withName (n, ty) = (name2String n, ty, n)++------------------------------------------------------------+-- Term desugaring+------------------------------------------------------------++-- | Desugar a typechecked term.+desugarTerm :: Member Fresh r => ATerm -> Sem r DTerm+desugarTerm (ATVar ty x) = return $ DTVar ty (coerce x)+desugarTerm (ATPrim (ty1 :->: resTy) (PrimUOp uop))+ | uopDesugars ty1 resTy uop = desugarPrimUOp ty1 resTy uop+desugarTerm (ATPrim (ty1 :*: ty2 :->: resTy) (PrimBOp bop))+ | bopDesugars ty1 ty2 resTy bop = desugarPrimBOp ty1 ty2 resTy bop+desugarTerm (ATPrim ty@(TyList cts :->: TyBag b) PrimC2B) = do+ c <- fresh (string2Name "c")+ body <- desugarTerm $+ tapp (ATPrim (TyBag cts :->: TyBag b) PrimC2B)+ (tapp (ATPrim (TyList cts :->: TyBag cts) PrimBag)+ (atVar (TyList cts) c)+ )+ return $ mkLambda ty [c] body++desugarTerm (ATPrim ty x) = return $ DTPrim ty x+desugarTerm ATUnit = return DTUnit+desugarTerm (ATBool ty b) = return $ DTBool ty b+desugarTerm (ATChar c) = return $ DTChar c+desugarTerm (ATString cs) =+ desugarContainer (TyList TyC) ListContainer (map (\c -> (ATChar c, Nothing)) cs) Nothing+desugarTerm (ATAbs q ty lam) = desugarAbs q ty [lam]++-- Special cases for fully applied operators+desugarTerm (ATApp resTy (ATPrim _ (PrimUOp uop)) t)+ | uopDesugars (getType t) resTy uop = desugarUnApp resTy uop t+desugarTerm (ATApp resTy (ATPrim _ (PrimBOp bop)) (ATTup _ [t1,t2]))+ | bopDesugars (getType t1) (getType t2) resTy bop = desugarBinApp resTy bop t1 t2++desugarTerm (ATApp ty t1 t2) =+ DTApp ty <$> desugarTerm t1 <*> desugarTerm t2+desugarTerm (ATTup ty ts) = desugarTuples ty ts+desugarTerm (ATNat ty n) = return $ DTNat ty n+desugarTerm (ATRat r) = return $ DTRat r++desugarTerm (ATTyOp ty op t) = return $ DTTyOp ty op t++desugarTerm (ATChain _ t1 links) = desugarTerm $ expandChain t1 links++desugarTerm (ATContainer ty c es mell) = desugarContainer ty c es mell++desugarTerm (ATContainerComp _ ctr bqt) = do+ (qs, t) <- unbind bqt+ desugarComp ctr t qs++desugarTerm (ATLet _ t) = do+ (bs, t2) <- unbind t+ desugarLet (fromTelescope bs) t2++desugarTerm (ATCase ty bs) = DTCase ty <$> mapM desugarBranch bs++desugarTerm (ATTest info t) = DTTest (coerce info) <$> desugarTerm t++-- | Desugar a property by wrapping its corresponding term in a test+-- frame to catch its exceptions & convert booleans to props.+desugarProperty :: Member Fresh r => AProperty -> Sem r DTerm+desugarProperty p = DTTest [] <$> desugarTerm p++------------------------------------------------------------+-- Desugaring operators+------------------------------------------------------------++-- | Test whether a given unary operator is one that needs to be+-- desugared, given the type of the argument and result.+uopDesugars :: Type -> Type -> UOp -> Bool+-- uopDesugars _ (TyFin _) Neg = True+uopDesugars _ _ uop = uop == Not++desugarPrimUOp :: Member Fresh r => Type -> Type -> UOp -> Sem r DTerm+desugarPrimUOp argTy resTy op = do+ x <- fresh (string2Name "arg")+ body <- desugarUnApp resTy op (atVar argTy x)+ return $ mkLambda (argTy :->: resTy) [x] body++-- | Test whether a given binary operator is one that needs to be+-- desugared, given the two types of the arguments and the type of the result.+bopDesugars :: Type -> Type -> Type -> BOp -> Bool+bopDesugars _ TyN _ Choose = True+-- bopDesugars _ _ (TyFin _) bop | bop `elem` [Add, Mul] = True+bopDesugars _ _ _ bop = bop `elem`+ [ And, Or, Impl+ , Neq, Gt, Leq, Geq, Min, Max+ , IDiv+ , Sub, SSub+ , Inter, Diff, Union, Subset+ ]++-- | Desugar a primitive binary operator at the given type.+desugarPrimBOp :: Member Fresh r => Type -> Type -> Type -> BOp -> Sem r DTerm+desugarPrimBOp ty1 ty2 resTy op = do+ p <- fresh (string2Name "pair1")+ x <- fresh (string2Name "arg1")+ y <- fresh (string2Name "arg2")+ let argsTy = ty1 :*: ty2+ body <- desugarBinApp resTy op (atVar ty1 x) (atVar ty2 y)+ return $ mkLambda (argsTy :->: resTy) [p] $+ DTCase resTy+ [ bind+ (toTelescope [DGPat (embed (dtVar argsTy (coerce p))) (DPPair argsTy (coerce x) (coerce y))])+ body+ ]++-- | Desugar a saturated application of a unary operator.+-- The first argument is the type of the result.+desugarUnApp :: Member Fresh r => Type -> UOp -> ATerm -> Sem r DTerm++-- Desugar negation on TyFin to a negation on TyZ followed by a mod.+-- See the comments below re: Add and Mul on TyFin.+-- desugarUnApp (TyFin n) Neg t =+-- desugarTerm $ mkBin (TyFin n) Mod (mkUn TyZ Neg t) (ATNat TyN n)++-- XXX This should be turned into a standard library definition.+-- not t ==> {? false if t, true otherwise ?}+desugarUnApp _ Not t = desugarTerm $+ ATCase TyBool+ [ fls <==. [AGBool (embed t)]+ , tru <==. []+ ]++desugarUnApp ty uop t = error $ "Impossible! desugarUnApp " ++ show ty ++ " " ++ show uop ++ " " ++ show t++-- | Desugar a saturated application of a binary operator.+-- The first argument is the type of the result.+desugarBinApp :: Member Fresh r => Type -> BOp -> ATerm -> ATerm -> Sem r DTerm++-- Implies, and, or should all be turned into a standard library+-- definition. This will require first (1) adding support for+-- modules/a standard library, including (2) the ability to define+-- infix operators.++-- t1 and t2 ==> {? t2 if t1, false otherwise ?}+desugarBinApp _ And t1 t2 = desugarTerm $+ ATCase TyBool+ [ t2 <==. [tif t1]+ , fls <==. []+ ]++-- (t1 implies t2) ==> (not t1 or t2)+desugarBinApp _ Impl t1 t2 = desugarTerm $ tnot t1 ||. t2++-- t1 or t2 ==> {? true if t1, t2 otherwise ?})+desugarBinApp _ Or t1 t2 = desugarTerm $+ ATCase TyBool+ [ tru <==. [tif t1]+ , t2 <==. []+ ]++desugarBinApp _ Neq t1 t2 = desugarTerm $ tnot (t1 ==. t2)+desugarBinApp _ Gt t1 t2 = desugarTerm $ t2 <. t1+desugarBinApp _ Leq t1 t2 = desugarTerm $ tnot (t2 <. t1)+desugarBinApp _ Geq t1 t2 = desugarTerm $ tnot (t1 <. t2)++-- XXX sharing!+desugarBinApp ty Min t1 t2 = desugarTerm $+ ATCase ty+ [ t1 <==. [tif (t1 <. t2)]+ , t2 <==. []+ ]++desugarBinApp ty Max t1 t2 = desugarTerm $+ ATCase ty+ [ t1 <==. [tif (t2 <. t1)]+ , t2 <==. []+ ]++-- t1 // t2 ==> floor (t1 / t2)+desugarBinApp resTy IDiv t1 t2 = desugarTerm $+ ATApp resTy (ATPrim (getType t1 :->: resTy) PrimFloor) (mkBin (getType t1) Div t1 t2)++-- Desugar normal binomial coefficient (n choose k) to a multinomial+-- coefficient with a singleton list, (n choose [k]).+-- Note this will only be called when (getType t2 == TyN); see bopDesugars.+desugarBinApp _ Choose t1 t2+ = desugarTerm $ mkBin TyN Choose t1 (ctrSingleton ListContainer t2)++desugarBinApp ty Sub t1 t2 = desugarTerm $ mkBin ty Add t1 (mkUn ty Neg t2)+desugarBinApp ty SSub t1 t2 = desugarTerm $+ -- t1 -. t2 ==> {? 0 if t1 < t2, t1 - t2 otherwise ?}+ ATCase ty+ [ ATNat ty 0 <==. [tif (t1 <. t2)]+ , mkBin ty Sub t1 t2 <==. []+ -- NOTE, the above is slightly bogus since the whole point of SSub is+ -- because we can't subtract naturals. However, this will+ -- immediately desugar to a DTerm. When we write a linting+ -- typechecker for DTerms we should allow subtraction on TyN!+ ]++-- Addition and multiplication on TyFin just desugar to the operation+-- followed by a call to mod.+-- desugarBinApp (TyFin n) op t1 t2+-- | op `elem` [Add, Mul] = desugarTerm $+-- mkBin (TyFin n) Mod+-- (mkBin TyN op t1 t2)+-- (ATNat TyN n)+ -- Note the typing of this is a bit funny: t1 and t2 presumably+ -- have type (TyFin n), and now we are saying that applying 'op'+ -- to them results in TyN, then applying 'mod' results in a TyFin+ -- n again. Using TyN as the intermediate result is necessary so+ -- we don't fall into an infinite desugaring loop, and intuitively+ -- makes sense because the idea is that we first do the operation+ -- as a normal operation in "natural land" and then do a mod.+ --+ -- We will have to think carefully about how the linting+ -- typechecker for DTerms should treat TyN and TyFin. Probably+ -- something like this will work: TyFin is a subtype of TyN, and+ -- TyN can be turned into TyFin with mod. (We don't want such+ -- typing rules in the surface disco language itself because+ -- implicit coercions from TyFin -> N don't commute with+ -- operations like addition and multiplication, e.g. 3+3 yields 1+ -- if we add them in Z5 and then coerce to Nat, but 6 if we first+ -- coerce both and then add.++-- Intersection, difference, and union all desugar to an application+-- of 'merge' with an appropriate combining operation.+desugarBinApp ty op t1 t2+ | op `elem` [Inter, Diff, Union] =+ desugarTerm $+ tapps (ATPrim ((TyN :*: TyN :->: TyN) :*: ty :*: ty :->: ty) PrimMerge)+ [ ATPrim (TyN :*: TyN :->: TyN) (mergeOp ty op)+ , t1+ , t2+ ]+ where+ mergeOp _ Inter = PrimBOp Min+ mergeOp _ Diff = PrimBOp SSub+ mergeOp (TySet _) Union = PrimBOp Max+ mergeOp (TyBag _) Union = PrimBOp Add+ mergeOp _ _ = error $ "Impossible! mergeOp " ++ show ty ++ " " ++ show op++-- A ⊆ B <==> (A ⊔ B = B)+-- where ⊔ denotes 'merge max'.+-- Note it is NOT union, since this doesn't work for bags.+-- e.g. bag [1] union bag [1,2] = bag [1,1,2] /= bag [1,2].+desugarBinApp _ Subset t1 t2 = desugarTerm $+ tapps (ATPrim (ty :*: ty :->: TyBool) (PrimBOp Eq))+ [ tapps (ATPrim ((TyN :*: TyN :->: TyN) :*: ty :*: ty :->: ty) PrimMerge)+ [ ATPrim (TyN :*: TyN :->: TyN) (PrimBOp Max)+ , t1+ , t2+ ]+ , t2 -- XXX sharing+ ]+ where+ ty = getType t1++desugarBinApp ty bop t1 t2 = error $ "Impossible! desugarBinApp " ++ show ty ++ " " ++ show bop ++ " " ++ show t1 ++ " " ++ show t2++------------------------------------------------------------+-- Desugaring other stuff+------------------------------------------------------------++-- | Desugar a container comprehension. First translate it into an+-- expanded ATerm and then recursively desugar that.+desugarComp :: Member Fresh r => Container -> ATerm -> Telescope AQual -> Sem r DTerm+desugarComp ctr t qs = expandComp ctr t qs >>= desugarTerm++-- | Expand a container comprehension into an equivalent ATerm.+expandComp :: Member Fresh r => Container -> ATerm -> Telescope AQual -> Sem r ATerm++-- [ t | ] = [ t ]+expandComp ctr t TelEmpty = return $ ctrSingleton ctr t++-- [ t | q, qs ] = ...+expandComp ctr t (TelCons (unrebind -> (q,qs)))+ = case q of+ -- [ t | x in l, qs ] = join (map (\x -> [t | qs]) l)+ AQBind x (unembed -> lst) -> do+ tqs <- expandComp ctr t qs+ let c = containerTy ctr+ tTy = getType t+ xTy = case getType lst of+ TyContainer _ e -> e+ _ -> error "Impossible! Not a container in expandComp"+ joinTy = c (c tTy) :->: c tTy+ mapTy = (xTy :->: c tTy) :*: c xTy :->: c (c tTy)+ return $ tapp (ATPrim joinTy PrimJoin) $+ tapp+ (ATPrim mapTy PrimEach)+ (mkPair+ (ATAbs Lam (xTy :->: c tTy) (bind [APVar xTy x] tqs))+ lst+ )++ -- [ t | g, qs ] = if g then [ t | qs ] else []+ AQGuard (unembed -> g) -> do+ tqs <- expandComp ctr t qs+ return $ ATCase (containerTy ctr (getType t))+ [ tqs <==. [tif g]+ , ctrNil ctr (getType t) <==. []+ ]++-- | Desugar a let into applications of a chain of nested lambdas.+-- /e.g./+--+-- @let x = s, y = t in q@+--+-- desugars to+--+-- @(\x. (\y. q) t) s@+desugarLet :: Member Fresh r => [ABinding] -> ATerm -> Sem r DTerm+desugarLet [] t = desugarTerm t+desugarLet ((ABinding _ x (unembed -> t1)) : ls) t =+ dtapp+ <$> (DTAbs Lam (getType t1 :->: getType t)+ <$> (bind (coerce x) <$> desugarLet ls t)+ )+ <*> desugarTerm t1++-- | Desugar a lambda from a list of argument names and types and the+-- desugared @DTerm@ expression for its body. It will be desugared+-- to a chain of one-argument lambdas. /e.g./+--+-- @\x y z. q@+--+-- desugars to+--+-- @\x. \y. \z. q@+mkLambda :: Type -> [Name ATerm] -> DTerm -> DTerm+mkLambda funty args c = go funty args+ where+ go _ [] = c+ go ty@(_ :->: ty2) (x:xs) = DTAbs Lam ty (bind (coerce x) (go ty2 xs))+ go ty as = error $ "Impossible! mkLambda.go " ++ show ty ++ " " ++ show as++mkQuant :: Quantifier -> [Type] -> [Name ATerm] -> DTerm -> DTerm+mkQuant q argtys args c = foldr quantify c (zip args argtys)+ where+ quantify (x, ty) body = DTAbs q ty (bind (coerce x) body)++mkAbs :: Quantifier -> Type -> [Type] -> [Name ATerm] -> DTerm -> DTerm+mkAbs Lam funty _ args c = mkLambda funty args c+mkAbs q _ argtys args c = mkQuant q argtys args c++-- | Desugar a tuple to nested pairs, /e.g./ @(a,b,c,d) ==> (a,(b,(c,d)))@.a+desugarTuples :: Member Fresh r => Type -> [ATerm] -> Sem r DTerm+desugarTuples _ [t] = desugarTerm t+desugarTuples ty@(_ :*: ty2) (t:ts) = DTPair ty <$> desugarTerm t <*> desugarTuples ty2 ts+desugarTuples ty ats+ = error $ "Impossible! desugarTuples " ++ show ty ++ " " ++ show ats++-- | Expand a chain of comparisons into a sequence of binary+-- comparisons combined with @and@. Note we only expand it into+-- another 'ATerm' (which will be recursively desugared), because+-- @and@ itself also gets desugared.+--+-- For example, @a < b <= c > d@ becomes @a < b and b <= c and c > d@.+expandChain :: ATerm -> [ALink] -> ATerm+expandChain _ [] = error "Can't happen! expandChain _ []"+expandChain t1 [ATLink op t2] = mkBin TyBool op t1 t2+expandChain t1 (ATLink op t2 : links) =+ mkBin TyBool And+ (mkBin TyBool op t1 t2)+ (expandChain t2 links)++-- | Desugar a branch of a case expression.+desugarBranch :: Member Fresh r => ABranch -> Sem r DBranch+desugarBranch b = do+ (ags, at) <- unbind b+ dgs <- desugarGuards ags+ d <- desugarTerm at+ return $ bind dgs d++-- | Desugar the list of guards in one branch of a case expression.+-- Pattern guards essentially remain as they are; boolean guards get+-- turned into pattern guards which match against @true@.+desugarGuards :: Member Fresh r => Telescope AGuard -> Sem r (Telescope DGuard)+desugarGuards = fmap (toTelescope . concat) . mapM desugarGuard . fromTelescope+ where+ desugarGuard :: Member Fresh r => AGuard -> Sem r [DGuard]++ -- A Boolean guard is desugared to a pattern-match on @true = right(unit)@.+ desugarGuard (AGBool (unembed -> at)) = do+ dt <- desugarTerm at+ desugarMatch dt (APInj TyBool R APUnit)++ -- 'let x = t' is desugared to 'when t is x'.+ desugarGuard (AGLet (ABinding _ x (unembed -> at))) = do+ dt <- desugarTerm at+ varMatch dt (coerce x)++ -- Desugaring 'when t is p' is the most complex case; we have to+ -- break down the pattern and match it incrementally.+ desugarGuard (AGPat (unembed -> at) p) = do+ dt <- desugarTerm at+ desugarMatch dt p++ -- Desugar a guard of the form 'when dt is p'. An entire match is+ -- the right unit to desugar --- as opposed to, say, writing a+ -- function to desugar a pattern --- since a match may desugar to+ -- multiple matches, and on recursive calls we need to know what+ -- term/variable should be bound to the pattern.+ --+ -- A match may desugar to multiple matches for two reasons:+ --+ -- 1. Nested patterns 'explode' into a 'telescope' matching one+ -- constructor at a time, for example, 'when t is (x,y,3)'+ -- becomes 'when t is (x,x0) when x0 is (y,x1) when x1 is 3'.+ -- This makes the order of matching explicit and enables lazy+ -- matching without requiring special support from the+ -- interpreter other than WHNF reduction.+ --+ -- 2. Matches against arithmetic patterns desugar to a+ -- combination of matching, computation, and boolean checks.+ -- For example, 'when t is (y+1)' becomes 'when t is x0 if x0 >=+ -- 1 let y = x0-1'.+ desugarMatch :: Member Fresh r => DTerm -> APattern -> Sem r [DGuard]+ desugarMatch dt (APVar ty x) = mkMatch dt (DPVar ty (coerce x))+ desugarMatch _ (APWild _) = return []+ desugarMatch dt APUnit = mkMatch dt DPUnit+ desugarMatch dt (APBool b) = desugarMatch dt (APInj TyBool (bool L R b) APUnit)+ desugarMatch dt (APNat ty n) = desugarMatch (dtbin TyBool (PrimBOp Eq) dt (DTNat ty n)) (APBool True)+ desugarMatch dt (APChar c) = desugarMatch (dtbin TyBool (PrimBOp Eq) dt (DTChar c)) (APBool True)+ desugarMatch dt (APString s) = desugarMatch dt (APList (TyList TyC) (map APChar s))+ desugarMatch dt (APTup tupTy pat) = desugarTuplePats tupTy dt pat+ where+ desugarTuplePats :: Member Fresh r => Type -> DTerm -> [APattern] -> Sem r [DGuard]+ desugarTuplePats _ _ [] = error "Impossible! desugarTuplePats []"+ desugarTuplePats _ t [p] = desugarMatch t p+ desugarTuplePats ty@(_ :*: ty2) t (p:ps) = do+ (x1,gs1) <- varForPat p+ (x2,gs2) <- case ps of+ [APVar _ px2] -> return (coerce px2, [])+ _ -> do+ x <- fresh (string2Name "x")+ (x,) <$> desugarTuplePats ty2 (dtVar ty2 x) ps+ fmap concat . sequence $+ [ mkMatch t $ DPPair ty x1 x2+ , return gs1+ , return gs2+ ]+ desugarTuplePats ty _ _+ = error $ "Impossible! desugarTuplePats with non-pair type " ++ show ty++ desugarMatch dt (APInj ty s p) = do+ (x,gs) <- varForPat p+ fmap concat . sequence $+ [ mkMatch dt $ DPInj ty s x+ , return gs+ ]++ desugarMatch dt (APCons ty p1 p2) = do+ y <- fresh (string2Name "y")+ (x1, gs1) <- varForPat p1+ (x2, gs2) <- varForPat p2++ let eltTy = getType p1+ unrolledTy = eltTy :*: ty+ fmap concat . sequence $+ [ mkMatch dt (DPInj ty R y)+ , mkMatch (dtVar unrolledTy y) (DPPair unrolledTy x1 x2)+ , return gs1+ , return gs2+ ]++ desugarMatch dt (APList ty []) = desugarMatch dt (APInj ty L APUnit)+ desugarMatch dt (APList ty ps) =+ desugarMatch dt $ foldr (APCons ty) (APList ty []) ps++ -- when dt is (p + t) ==> when dt is x0; let v = t; [if x0 >= v]; when x0-v is p+ desugarMatch dt (APAdd ty _ p t) = arithBinMatch posRestrict (-.) dt ty p t+ where+ posRestrict plusty+ | plusty `elem` [TyN, TyF] = Just (>=.)+ | otherwise = Nothing++ -- when dt is (p * t) ==> when dt is x0; let v = t; [if v divides x0]; when x0 / v is p+ desugarMatch dt (APMul ty _ p t) = arithBinMatch intRestrict (/.) dt ty p t+ where+ intRestrict plusty+ | plusty `elem` [TyN, TyZ] = Just (flip (|.))+ | otherwise = Nothing++ -- when dt is (p - t) ==> when dt is x0; let v = t; when x0 + v is p+ desugarMatch dt (APSub ty p t) = arithBinMatch (const Nothing) (+.) dt ty p t++ -- when dt is (p/q) ==> when $frac(dt) is (p, q)+ desugarMatch dt (APFrac _ p q)+ = desugarMatch+ (dtapp (DTPrim (TyQ :->: TyZ :*: TyN) PrimFrac) dt)+ (APTup (TyZ :*: TyN) [p, q])++ -- when dt is (-p) ==> when dt is x0; if x0 < 0; when -x0 is p+ desugarMatch dt (APNeg ty p) = do++ -- when dt is x0+ (x0, g1) <- varFor dt++ -- if x0 < 0+ g2 <- desugarGuard $ AGBool (embed (atVar ty (coerce x0) <. ATNat ty 0))++ -- when -x0 is p+ neg <- desugarTerm $ mkUn ty Neg (atVar ty (coerce x0))+ g3 <- desugarMatch neg p++ return (g1 ++ g2 ++ g3)++ mkMatch :: Member Fresh r => DTerm -> DPattern -> Sem r [DGuard]+ mkMatch dt dp = return [DGPat (embed dt) dp]++ varMatch :: Member Fresh r => DTerm -> Name DTerm -> Sem r [DGuard]+ varMatch dt x = mkMatch dt (DPVar (getType dt) x)++ varFor :: Member Fresh r => DTerm -> Sem r (Name DTerm, [DGuard])+ varFor (DTVar _ (QName _ x)) = return (x, []) -- XXX return a name + provenance??+ varFor dt = do+ x <- fresh (string2Name "x")+ g <- varMatch dt x+ return (x, g)++ varForPat :: Member Fresh r => APattern -> Sem r (Name DTerm, [DGuard])+ varForPat (APVar _ x) = return (coerce x, [])+ varForPat p = do+ x <- fresh (string2Name "px") -- changing this from x fixed a bug and I don't know why =(+ (x,) <$> desugarMatch (dtVar (getType p) x) p++ arithBinMatch+ :: Member Fresh r+ => (Type -> Maybe (ATerm -> ATerm -> ATerm))+ -> (ATerm -> ATerm -> ATerm)+ -> DTerm -> Type -> APattern -> ATerm -> Sem r [DGuard]+ arithBinMatch restrict inverse dt ty p t = do+ (x0, g1) <- varFor dt++ -- let v = t+ t' <- desugarTerm t+ (v, g2) <- varFor t'++ g3 <- case restrict ty of+ Nothing -> return []++ -- if x0 `cmp` v+ Just cmp ->+ desugarGuard $+ AGBool (embed (atVar ty (coerce x0) `cmp` atVar (getType t) (coerce v)))++ -- when x0 `inverse` v is p+ inv <- desugarTerm (atVar ty (coerce x0) `inverse` atVar (getType t) (coerce v))+ g4 <- desugarMatch inv p++ return (g1 ++ g2 ++ g3 ++ g4)++-- | Desugar a container literal such as @[1,2,3]@ or @{1,2,3}@.+desugarContainer :: Member Fresh r => Type -> Container -> [(ATerm, Maybe ATerm)] -> Maybe (Ellipsis ATerm) -> Sem r DTerm++-- Literal list containers desugar to nested applications of cons.+desugarContainer ty ListContainer es Nothing =+ foldr (dtbin ty (PrimBOp Cons)) (DTNil ty) <$> mapM (desugarTerm . fst) es++-- A list container with an ellipsis @[x, y, z .. e]@ desugars to an+-- application of the primitive 'until' function.+desugarContainer ty@(TyList _) ListContainer es (Just (Until t)) =+ dtbin ty PrimUntil+ <$> desugarTerm t+ <*> desugarContainer ty ListContainer es Nothing++-- If desugaring a bag and there are any counts specified, desugar to+-- an application of bagFromCounts to a bag of pairs (with a literal+-- value of 1 filled in for missing counts as needed).+desugarContainer (TyBag eltTy) BagContainer es mell+ | any (isJust . snd) es =+ dtapp (DTPrim (TySet (eltTy :*: TyN) :->: TyBag eltTy) PrimC2B)+ <$> desugarContainer (TyBag (eltTy :*: TyN)) BagContainer counts mell++ where+ -- turn e.g. x # 3, y into (x, 3), (y, 1)+ counts = [ (ATTup (eltTy :*: TyN) [t, fromMaybe (ATNat TyN 1) n], Nothing)+ | (t, n) <- es+ ]++-- Other containers desugar to an application of the appropriate+-- container conversion function to the corresponding desugared list.+desugarContainer ty _ es mell =+ dtapp (DTPrim (TyList eltTy :->: ty) conv)+ <$> desugarContainer (TyList eltTy) ListContainer es mell+ where+ (conv, eltTy) = case ty of+ TyBag e -> (PrimBag, e)+ TySet e -> (PrimSet, e)+ _ -> error $ "Impossible! Non-container type " ++ show ty ++ " in desugarContainer"
+ src/Disco/Effects/Counter.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Effects.Counter+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Polysemy effect for integer counter.+--+-----------------------------------------------------------------------------++module Disco.Effects.Counter where++import Polysemy+import Polysemy.State++data Counter m a where++ -- | Return the next integer in sequence.+ Next :: Counter m Integer++makeSem ''Counter++-- | Dispatch a counter effect, starting the counter from the given+-- Integer.+runCounter' :: Integer -> Sem (Counter ': r) a -> Sem r a+runCounter' i+ = evalState i+ . reinterpret \case+ Next -> do+ n <- get+ put (n+1)+ return n++-- | Dispatch a counter effect, starting the counter from zero.+runCounter :: Sem (Counter ': r) a -> Sem r a+runCounter = runCounter' 0
+ src/Disco/Effects/Fresh.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Effects.Fresh+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Polysemy effect for fresh name generation, compatible with the+-- unbound-generics library.+--+-----------------------------------------------------------------------------++module Disco.Effects.Fresh where++import Disco.Effects.Counter+import Disco.Names (QName, localName)+import Polysemy+import Polysemy.ConstraintAbsorber+import qualified Unbound.Generics.LocallyNameless as U+import Unbound.Generics.LocallyNameless.Name++-- | Fresh name generation effect, supporting raw generation of fresh+-- names, and opening binders with automatic freshening. Simply+-- increments a global counter every time 'fresh' is called and+-- makes a variable with that numeric suffix.+data Fresh m a where+ Fresh :: Name x -> Fresh m (Name x)++makeSem ''Fresh++-- | Dispatch the fresh name generation effect, starting at a given+-- integer.+runFresh' :: Integer -> Sem (Fresh ': r) a -> Sem r a+runFresh' i+ = runCounter' i+ . reinterpret \case+ Fresh x -> case x of+ Fn s _ -> Fn s <$> next+ nm@Bn{} -> return nm++ -- Above code copied from+ -- https://hackage.haskell.org/package/unbound-generics-0.4.1/docs/src/Unbound.Generics.LocallyNameless.Fresh.html ;+ -- see instance Monad m => Fresh (FreshMT m) .++ -- It turns out to make things much simpler to reimplement the+ -- Fresh effect ourselves in terms of a state effect, since then+ -- we can immediately dispatch it. The alternative would be to+ -- implement it in terms of (Embed U.FreshM), but then we are+ -- stuck with that constraint. Given the constraint-absorbing+ -- machinery below, just impementing the 'fresh' effect itself+ -- means we can then reuse other things from unbound-generics that+ -- depend on a Fresh constraint, such as the 'unbind' function+ -- below.++-- | Run a computation requiring fresh name generation, beginning with+-- 0 for the initial freshly generated name.+runFresh :: Sem (Fresh ': r) a -> Sem r a+runFresh = runFresh' 0++-- | Run a computation requiring fresh name generation, beginning with+-- 1 instead of 0 for the initial freshly generated name.+runFresh1 :: Sem (Fresh ': r) a -> Sem r a+runFresh1 = runFresh' 1++------------------------------------------------------------+-- Other functions++-- | Open a binder, automatically creating fresh names for the bound+-- variables.+unbind :: (Member Fresh r, U.Alpha p, U.Alpha t) => U.Bind p t -> Sem r (p, t)+unbind b = absorbFresh (U.unbind b)++-- | Generate a fresh (local, free) qualified name based on a given+-- string.+freshQ :: (Member Fresh r) => String -> Sem r (QName a)+freshQ s = localName <$> fresh (string2Name s)++------------------------------------------------------------+-- Machinery for absorbing MTL-style constraint.+-- See https://hackage.haskell.org/package/polysemy-zoo-0.7.0.1/docs/Polysemy-ConstraintAbsorber.html+-- Used https://hackage.haskell.org/package/polysemy-zoo-0.7.0.1/docs/src/Polysemy.ConstraintAbsorber.MonadState.html#absorbState as a template.++-- | Run a 'Sem' computation requiring a 'U.Fresh' constraint (from+-- the @unbound-generics@ library) in terms of an available 'Fresh'+-- effect.+absorbFresh :: Member Fresh r => (U.Fresh (Sem r) => Sem r a) -> Sem r a+absorbFresh = absorbWithSem @U.Fresh @Action (FreshDict fresh) (Sub Dict)+{-# INLINEABLE absorbFresh #-}++newtype FreshDict m = FreshDict { fresh_ :: forall x. Name x -> m (Name x) }++-- | Wrapper for a monadic action with phantom type parameter for reflection.+-- Locally defined so that the instance we are going to build with reflection+-- must be coherent, that is there cannot be orphans.+newtype Action m s' a = Action (m a)+ deriving (Functor, Applicative, Monad)++instance ( Monad m+ , Reifies s' (FreshDict m)+ ) => U.Fresh (Action m s') where+ fresh x = Action $ fresh_ (reflect $ Proxy @s') x+ {-# INLINEABLE fresh #-}
+ src/Disco/Effects/Input.hs view
@@ -0,0 +1,26 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Effects.Input+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Utility functions for input effect.+--+-----------------------------------------------------------------------------++module Disco.Effects.Input+ ( module Polysemy.Input+ , inputToState+ )+ where++import Polysemy+import Polysemy.Input+import Polysemy.State++-- | Run an input effect in terms of an ambient state effect.+inputToState :: forall s r a. Member (State s) r => Sem (Input s ': r) a -> Sem r a+inputToState = interpret (\case { Input -> get @s })+
+ src/Disco/Effects/LFresh.hs view
@@ -0,0 +1,149 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Effects.LFresh+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Polysemy effect for local fresh name generation, compatible with+-- the unbound-generics library.+--+-----------------------------------------------------------------------------++module Disco.Effects.LFresh where++import Data.Set (Set)+import qualified Data.Set as S+import Data.Typeable (Typeable)+import Polysemy+import Polysemy.ConstraintAbsorber+import Polysemy.Reader+import qualified Unbound.Generics.LocallyNameless as U+import Unbound.Generics.LocallyNameless.Name++-- | Local fresh name generation effect.+data LFresh m a where+ Lfresh :: Typeable a => Name a -> LFresh m (Name a)+ Avoid :: [AnyName] -> m a -> LFresh m a+ GetAvoids :: LFresh m (Set AnyName)++makeSem ''LFresh++-- | Dispatch an 'LFresh' effect via a 'Reader' effect to keep track+-- of a set of in-scope names.+runLFresh :: Sem (LFresh ': r) a -> Sem r a+runLFresh = runReader S.empty . runLFresh'++runLFresh' :: Sem (LFresh ': r) a -> Sem (Reader (Set AnyName) ': r) a+runLFresh'+ = reinterpretH @_ @(Reader (Set AnyName)) \case+ Lfresh nm -> do+ let s = name2String nm+ used <- ask+ pureT $ head (filter (\x -> not (S.member (AnyName x) used))+ (map (makeName s) [0..]))+ Avoid names m -> do+ m' <- runT m+ raise (subsume (runLFresh' (local (S.union (S.fromList names)) m')))+ GetAvoids -> ask >>= pureT++ -- Much of the above code copied from+ -- https://hackage.haskell.org/package/unbound-generics-0.4.1/docs/src/Unbound.Generics.LocallyNameless.LFresh.html+ -- (see instance Monad m => LFresh (LFreshMT m))++ -- It turns out to make things much simpler to reimplement the+ -- LFresh effect ourselves in terms of a reader effect, since then+ -- we can immediately dispatch it as above. The alternative would+ -- be to implement it in terms of (Final U.LFreshM) (see the+ -- commented code at the bottom of this file), but then we are stuck+ -- with that constraint. Given the constraint-absorbing machinery+ -- below, just impementing the 'LFresh' effect itself means we can+ -- then reuse other things from unbound-generics that depend on a+ -- Fresh constraint, such as the 'lunbind' function below.++ -- NOTE: originally, there was a single function runLFresh which+ -- called reinterpretH and then immediately dispatched the Reader+ -- (Set AnyName) effect. However, since runLFresh is recursive,+ -- this means that the recursive calls were running with a+ -- completely *separate* Reader effect that started over from the+ -- empty set! This meant that LFresh basically never changed any+ -- names, leading to all sorts of name clashes and crashes.+ --+ -- Instead, we need to organize things as above: runLFresh' is+ -- recursive, and keeps the Reader effect (using 'subsume' to squash+ -- the duplicated Reader effects together). Then a top-level+ -- runLFresh function finally runs the Reader effect.++--------------------------------------------------+-- Other functions++-- | Open a binder, automatically freshening the names of the bound+-- variables, and providing the opened pattern and term to the+-- provided continuation. The bound variables are also added to the+-- set of in-scope variables within in the continuation.+lunbind+ :: (Member LFresh r, U.Alpha p, U.Alpha t)+ => U.Bind p t -> ((p,t) -> Sem r c) -> Sem r c+lunbind b k = absorbLFresh (U.lunbind b k)++------------------------------------------------------------+-- Machinery for absorbing MTL-style constraint.+-- See https://hackage.haskell.org/package/polysemy-zoo-0.7.0.1/docs/Polysemy-ConstraintAbsorber.html+-- Used https://hackage.haskell.org/package/polysemy-zoo-0.7.0.1/docs/src/Polysemy.ConstraintAbsorber.MonadState.html#absorbState as a template.++absorbLFresh :: Member LFresh r => (U.LFresh (Sem r) => Sem r a) -> Sem r a+absorbLFresh = absorbWithSem @U.LFresh @Action (LFreshDict lfresh avoid getAvoids) (Sub Dict)+{-# INLINEABLE absorbLFresh #-}++data LFreshDict m = LFreshDict+ { lfresh_ :: forall a. Typeable a => Name a -> m (Name a)+ , avoid_ :: forall a. [AnyName] -> m a -> m a+ , getAvoids_ :: m (Set AnyName)+ }++-- | Wrapper for a monadic action with phantom type parameter for reflection.+-- Locally defined so that the instance we are going to build with reflection+-- must be coherent, that is there cannot be orphans.+newtype Action m s' a = Action (m a)+ deriving (Functor, Applicative, Monad)++instance ( Monad m+ , Reifies s' (LFreshDict m)+ ) => U.LFresh (Action m s') where+ lfresh x = Action $ lfresh_ (reflect $ Proxy @s') x+ {-# INLINEABLE lfresh #-}+ avoid xs (Action m) = Action $ avoid_ (reflect $ Proxy @s') xs m+ {-# INLINEABLE avoid #-}+ getAvoids = Action $ getAvoids_ (reflect $ Proxy @s')+ {-# INLINEABLE getAvoids #-}++----------------------------------------------------------------------+-- Old code I don't want to delete because I spent so much time+-- banging my head against it. It wasn't wasted, though, since I used+-- some of my hard-earned knowledge to write runLFresh' above.++-- -- | Dispatch the local fresh name generation effect in an effect stack+-- -- containing the 'LFreshM' monad from @unbound-generics@.+-- runLFreshR :: Member (Final U.LFreshM) r => Sem (LFresh ': r) a -> Sem r a+-- runLFreshR = interpretFinal @U.LFreshM $ \case+-- Avoid xs m -> do+-- m' <- runS m+-- pure (U.avoid xs m')+-- Lunbind b k -> do+-- s <- getInitialStateS+-- k' <- bindS k+-- pure (U.lunbind b (k' . (<$ s)))++-- -- The above code took me a long time to figure out how to write.+-- -- lunbind is a higher-order effect, so we have to use more+-- -- complicated machinery. See my Stack Overflow question,+-- -- https://stackoverflow.com/questions/68384508/how-to-incorporate-mtl-style-cps-style-higher-order-effect-into-polysemy/68397358#68397358++-- -- | Run a computation requiring only fresh name generation.+-- runLFresh :: Sem '[LFresh, Final U.LFreshM] a -> a+-- runLFresh = U.runLFreshM . runFinal . runLFreshR
+ src/Disco/Effects/Random.hs view
@@ -0,0 +1,32 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Effects.Random+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Utility functions for random effect.+--+-----------------------------------------------------------------------------++module Disco.Effects.Random+ ( module Polysemy.Random+ , runGen+ )+ where++import Polysemy+import Polysemy.Random+import qualified System.Random.SplitMix as SM+import qualified Test.QuickCheck.Gen as QC+import qualified Test.QuickCheck.Random as QCR++import Data.Word (Word64)++-- | Run a QuickCheck generator using a 'Random' effect.+runGen :: Member Random r => QC.Gen a -> Sem r a+runGen g = do+ n <- random @_ @Int+ w <- random @_ @Word64+ return $ QC.unGen g (QCR.QCGen (SM.mkSMGen w)) n
+ src/Disco/Effects/State.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE BlockArguments #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Effects.State+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Utility functions for state effect.+--+-----------------------------------------------------------------------------++module Disco.Effects.State+ ( module Polysemy.State+ , zoom+ , use+ ,(%=),(.=))+ where++import Control.Lens (Getter, Lens', view, (%~), (.~))++import Polysemy+import Polysemy.State++-- | Use a lens to zoom into a component of a state.+zoom :: forall s a r c. Member (State s) r => Lens' s a -> Sem (State a ': r) c -> Sem r c+zoom l = interpret \case+ Get -> view l <$> get+ Put a -> modify (l .~ a)++use :: Member (State s) r => Getter s a -> Sem r a+use g = gets (view g)++infix 4 .=, %=++(.=) :: Member (State s) r => Lens' s a -> a -> Sem r ()+l .= a = modify (l .~ a)++(%=) :: Member (State s) r => Lens' s a -> (a -> a) -> Sem r ()+l %= f = modify (l %~ f)
+ src/Disco/Effects/Store.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Effects.Store+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Polysemy effect for a memory store with integer keys.+--+-----------------------------------------------------------------------------++module Disco.Effects.Store where++import qualified Data.IntMap.Lazy as IntMap+import Data.IntSet (IntSet)+import qualified Data.IntSet as IntSet++import Disco.Effects.Counter+import Polysemy+import Polysemy.State++data Store v m a where++ ClearStore :: Store v m ()+ New :: v -> Store v m Int+ LookupStore :: Int -> Store v m (Maybe v)+ InsertStore :: Int -> v -> Store v m ()+ MapStore :: (v -> v) -> Store v m ()+ AssocsStore :: Store v m [(Int, v)]+ KeepKeys :: IntSet -> Store v m ()++makeSem ''Store++-- | Dispatch a store effect.+runStore :: forall v r a. Sem (Store v ': r) a -> Sem r a+runStore+ = runCounter+ . evalState @(IntMap.IntMap v) IntMap.empty+ . reinterpret2 \case+ ClearStore -> put IntMap.empty+ New v -> do+ loc <- fromIntegral <$> next+ modify $ IntMap.insert loc v+ return loc+ LookupStore k -> gets (IntMap.lookup k)+ InsertStore k v -> modify (IntMap.insert k v)+ MapStore f -> modify (IntMap.map f)+ AssocsStore -> gets IntMap.assocs+ KeepKeys ks -> modify (\m -> IntMap.withoutKeys m (IntMap.keysSet m `IntSet.difference` ks))
+ src/Disco/Enumerate.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE NondecreasingIndentation #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Enumerate+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Enumerate values inhabiting Disco types.+--+-----------------------------------------------------------------------------++module Disco.Enumerate+ (+ ValueEnumeration+ -- * Base types+ , enumVoid+ , enumUnit+ , enumBool+ , enumN+ , enumZ+ , enumF+ , enumQ+ , enumC++ -- * Containers+ , enumSet+ -- , enumBag+ , enumList++ -- * Any type+ , enumType+ , enumTypes++ -- * Lifted functions that return lists+ , enumerateType+ , enumerateTypes+ )+ where++import qualified Data.Enumeration.Invertible as E+import Disco.AST.Generic (Side (..))+import Disco.Types+import Disco.Value++type ValueEnumeration = E.IEnumeration Value++-- | Enumerate all values of type @Void@ (none).+enumVoid :: ValueEnumeration+enumVoid = E.void++-- | Enumerate all values of type @Unit@ (the single value @unit@).+enumUnit :: ValueEnumeration+enumUnit = E.singleton VUnit++-- | Enumerate the values of type @Bool@ as @[false, true]@.+enumBool :: ValueEnumeration+enumBool = E.mapE toV fromV $ E.finiteList [L, R]+ where+ toV i = VInj i VUnit+ fromV (VInj i VUnit) = i+ fromV _ = error "enumBool.fromV: value isn't a bool"++-- | Unsafely extract the numeric value of a @Value@+-- (assumed to be a VNum).+valToRat :: Value -> Rational+valToRat (VNum _ r) = r+valToRat _ = error "valToRat: value isn't a number"++ratToVal :: Rational -> Value+ratToVal = VNum mempty++-- | Enumerate all values of type @Nat@ (0, 1, 2, ...).+enumN :: ValueEnumeration+enumN = E.mapE (ratToVal . fromInteger) (floor . valToRat) E.nat++-- | Enumerate all values of type @Integer@ (0, 1, -1, 2, -2, ...).+enumZ :: ValueEnumeration+enumZ = E.mapE (ratToVal . fromInteger) (floor . valToRat) E.int++-- | Enumerate all values of type @Fractional@ in the Calkin-Wilf+-- order (1, 1/2, 2, 1/3, 3/2, 2/3, 3, ...).+enumF :: ValueEnumeration+enumF = E.mapE ratToVal valToRat E.cw++-- | Enumerate all values of type @Rational@ in the Calkin-Wilf order,+-- with negatives interleaved (0, 1, -1, 1/2, -1/2, 2, -2, ...).+enumQ :: ValueEnumeration+enumQ = E.mapE ratToVal valToRat E.rat++-- | Enumerate all Unicode characters.+enumC :: ValueEnumeration+enumC = E.mapE toV fromV (E.boundedEnum @Char)+ where+ toV = ratToVal . fromIntegral . fromEnum+ fromV = toEnum . floor . valToRat++-- | Enumerate all *finite* sets over a certain element type, given an+-- enumeration of the elements. If we think of each finite set as a+-- binary string indicating which elements in the enumeration are+-- members, the sets are enumerated in order of the binary strings.+enumSet :: ValueEnumeration -> ValueEnumeration+enumSet e = E.mapE toV fromV (E.finiteSubsetOf e)+ where+ toV = VBag . map (,1)+ fromV (VBag vs) = map fst vs+ fromV _ = error "enumSet.fromV: value isn't a set"++-- | Enumerate all *finite* lists over a certain element type, given+-- an enumeration of the elements. It is very difficult to describe+-- the order in which the lists are generated.+enumList :: ValueEnumeration -> ValueEnumeration+enumList e = E.mapE toV fromV (E.listOf e)+ where+ toV = foldr VCons VNil+ fromV (VCons h t) = h : fromV t+ fromV VNil = []+ fromV _ = error "enumList.fromV: value isn't a list"++-- | Enumerate all functions from a finite domain, given enumerations+-- for the domain and codomain.+enumFunction :: ValueEnumeration -> ValueEnumeration -> ValueEnumeration+enumFunction xs ys =+ case (E.card xs, E.card ys) of+ (E.Finite 0, _) -> E.singleton (VFun $ \_ -> error "enumFunction: void function called")+ (_, E.Finite 0) -> E.void+ (_, E.Finite 1) -> E.singleton (VFun $ \_ -> E.select ys 0)+ _ -> E.mapE toV fromV (E.functionOf xs ys)++ -- XXX TODO: better error message on functions with an infinite domain+ where+ toV = VFun+ fromV (VFun f) = f+ fromV _ = error "enumFunction.fromV: value isn't a VFun"++-- | Enumerate all values of a product type, given enumerations of the+-- two component types. Uses a fair interleaving for infinite+-- component types.+enumProd :: ValueEnumeration -> ValueEnumeration -> ValueEnumeration+enumProd xs ys = E.mapE toV fromV $ (E.><) xs ys+ where+ toV (x, y) = VPair x y+ fromV (VPair x y) = (x, y)+ fromV _ = error "enumProd.fromV: value isn't a pair"++-- | Enumerate all values of a sum type, given enumerations of the two+-- component types.+enumSum :: ValueEnumeration -> ValueEnumeration -> ValueEnumeration+enumSum xs ys = E.mapE toV fromV $ (E.<+>) xs ys+ where+ toV (Left x) = VInj L x+ toV (Right y) = VInj R y+ fromV (VInj L x) = Left x+ fromV (VInj R y) = Right y+ fromV _ = error "enumSum.fromV: value isn't a sum"++-- | Enumerate the values of a given type.+enumType :: Type -> ValueEnumeration+enumType TyVoid = enumVoid+enumType TyUnit = enumUnit+enumType TyBool = enumBool+enumType TyN = enumN+enumType TyZ = enumZ+enumType TyF = enumF+enumType TyQ = enumQ+enumType TyC = enumC+enumType (TySet t) = enumSet (enumType t)+enumType (TyList t) = enumList (enumType t)+enumType (a :*: b) = enumProd (enumType a) (enumType b)+enumType (a :+: b) = enumSum (enumType a) (enumType b)+enumType (a :->: b) = enumFunction (enumType a) (enumType b)+enumType ty = error $ "enumType: can't enumerate " ++ show ty++-- | Enumerate a finite product of types.+enumTypes :: [Type] -> E.IEnumeration [Value]+enumTypes [] = E.singleton []+enumTypes (t:ts) = E.mapE toL fromL $ (E.><) (enumType t) (enumTypes ts)+ where+ toL (x, xs) = x:xs+ fromL (x:xs) = (x, xs)+ fromL [] = error "enumTypes.fromL: empty list not in enumeration range"++-- | Produce an actual list of the values of a type.+enumerateType :: Type -> [Value]+enumerateType = E.enumerate . enumType++-- | Produce an actual list of values enumerated from a finite product+-- of types.+enumerateTypes :: [Type] -> [[Value]]+enumerateTypes = E.enumerate . enumTypes
+ src/Disco/Error.hs view
@@ -0,0 +1,318 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Error+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Type for collecting all potential Disco errors at the top level,+-- and a type for runtime errors.+--+-----------------------------------------------------------------------------++module Disco.Error (DiscoError(..), EvalError(..), panic, outputDiscoErrors) where++import Prelude hiding ((<>))++import Text.Megaparsec (ParseErrorBundle,+ errorBundlePretty)+import Unbound.Generics.LocallyNameless (Name)++import Disco.Effects.LFresh+import Polysemy+import Polysemy.Error+import Polysemy.Output+import Polysemy.Reader++import Disco.Messages+import Disco.Names (ModuleName)+import Disco.Parser (DiscoParseError)+import Disco.Pretty+import Disco.Typecheck.Solve+import Disco.Typecheck.Util (TCError (..))+import Disco.Types+import Disco.Types.Qualifiers++-- | Top-level error type for Disco.+data DiscoError where++ -- | Module not found.+ ModuleNotFound :: String -> DiscoError++ -- | Cyclic import encountered.+ CyclicImport :: [ModuleName] -> DiscoError++ -- | Error encountered during typechecking.+ TypeCheckErr :: TCError -> DiscoError++ -- | Error encountered during parsing.+ ParseErr :: ParseErrorBundle String DiscoParseError -> DiscoError++ -- | Error encountered at runtime.+ EvalErr :: EvalError -> DiscoError++ -- | Something that shouldn't happen; indicates the presence of a+ -- bug.+ Panic :: String -> DiscoError++ deriving Show++-- | Errors that can be generated at runtime.+data EvalError where++ -- | An unbound name. This shouldn't happen.+ UnboundError :: Name core -> EvalError++ -- | Division by zero.+ DivByZero :: EvalError++ -- | Overflow, e.g. (2^66)!+ Overflow :: EvalError++ -- | Non-exhaustive case analysis.+ NonExhaustive :: EvalError++ -- | Infinite loop detected via black hole.+ InfiniteLoop :: EvalError++ -- | User-generated crash.+ Crash :: String -> EvalError++deriving instance Show EvalError++panic :: Member (Error DiscoError) r => String -> Sem r a+panic = throw . Panic++outputDiscoErrors :: Member (Output Message) r => Sem (Error DiscoError ': r) () -> Sem r ()+outputDiscoErrors m = do+ e <- runError m+ either (err . pretty') return e++instance Pretty DiscoError where+ pretty = \case+ ModuleNotFound m -> "Error: couldn't find a module named '" <> text m <> "'."+ CyclicImport ms -> cyclicImportError ms+ TypeCheckErr te -> prettyTCError te+ ParseErr pe -> text (errorBundlePretty pe)+ EvalErr ee -> prettyEvalError ee+ Panic s ->+ hcat+ [ "Bug! " <> text s+ , "Please report this as a bug at https://github.com/disco-lang/disco/issues/ ."+ ]++rtd :: String -> Sem r Doc+rtd page = "https://disco-lang.readthedocs.io/en/latest/reference/" <> text page <> ".html"++cyclicImportError+ :: Members '[Reader PA, LFresh] r+ => [ModuleName] -> Sem r Doc+cyclicImportError ms =+ vcat+ [ "Error: module imports form a cycle:"+ , nest 2 $ intercalate " ->" (map pretty ms)+ ]++prettyEvalError :: Members '[Reader PA, LFresh] r => EvalError -> Sem r Doc+prettyEvalError = \case+ UnboundError x ->+ ("Bug! No variable found named" <+> pretty' x <> ".")+ $+$+ "Please report this as a bug at https://github.com/disco-lang/disco/issues/ ."+ DivByZero -> "Error: division by zero."+ Overflow -> "Error: that number would not even fit in the universe!"+ NonExhaustive -> "Error: value did not match any of the branches in a case expression."+ InfiniteLoop -> "Error: infinite loop detected!"+ Crash s -> "User crash:" <+> text s++-- [X] Step 1: nice error messages, make sure all are tested+-- [ ] Step 2: link to wiki/website with more info on errors!+-- [ ] Step 3: improve error messages according to notes below+-- [ ] Step 4: get it to return multiple error messages+-- [ ] Step 5: save parse locations, display with errors+prettyTCError :: Members '[Reader PA, LFresh] r => TCError -> Sem r Doc+prettyTCError = \case++ -- XXX include some potential misspellings along with Unbound+ Unbound x -> vcat+ [ "Error: there is nothing named" <+> pretty' x <> "."+ , rtd "unbound"+ ]++ Ambiguous x ms -> vcat+ [ "Error: the name" <+> pretty' x <+> "is ambiguous. It could refer to:"+ , nest 2 (vcat . map (\m -> pretty' m <> "." <> pretty' x) $ ms)+ , rtd "ambiguous"+ ]++ NoType x -> vcat+ [ "Error: the definition of" <+> pretty' x <+> "must have an accompanying type signature."+ , "Try writing something like '" <> pretty' x <+> ": Int' (or whatever the type of"+ <+> pretty' x <+> "should be) first."+ , rtd "missingtype"+ ]++ NotCon c t ty -> vcat+ [ "Error: the expression"+ , nest 2 $ pretty' t+ , "must have both a" <+> conWord c <+> "type and also the incompatible type"+ , nest 2 $ pretty' ty <> "."+ , rtd "notcon"+ ]++ EmptyCase -> vcat+ [ "Error: empty case expressions {? ?} are not allowed."+ , rtd "empty-case"+ ]++ PatternType c pat ty -> vcat+ [ "Error: the pattern"+ , nest 2 $ pretty' pat+ , "is supposed to have type"+ , nest 2 $ pretty' ty <> ","+ , "but instead it has a" <+> conWord c <+> "type."+ , rtd "pattern-type"+ ]++ DuplicateDecls x -> vcat+ [ "Error: duplicate type signature for" <+> pretty' x <> "."+ , rtd "dup-sig"+ ]++ DuplicateDefns x -> vcat+ [ "Error: duplicate definition for" <+> pretty' x <> "."+ , rtd "dup-def"+ ]++ DuplicateTyDefns s -> vcat+ [ "Error: duplicate definition for type" <+> text s <> "."+ , rtd "dup-tydef"+ ]++ -- XXX include all types involved in the cycle.+ CyclicTyDef s -> vcat+ [ "Error: cyclic type definition for" <+> text s <> "."+ , rtd "cyc-ty"+ ]++ -- XXX lots more info! & Split into several different errors.+ NumPatterns -> vcat+ [ "Error: number of arguments does not match."+ , rtd "num-args"+ ]++ NoSearch ty ->+ vcat+ [ "Error: the type"+ , nest 2 $ pretty' ty+ , "is not searchable (i.e. it cannot be used in a forall)."+ , rtd "no-search"+ ]++ Unsolvable solveErr -> prettySolveError solveErr++ -- XXX maybe include close edit-distance alternatives?+ NotTyDef s -> vcat+ [ "Error: there is no built-in or user-defined type named '" <> text s <> "'."+ , rtd "no-tydef"+ ]++ NoTWild -> vcat+ [ "Error: wildcards (_) are not allowed in expressions."+ , rtd "wildcard-expr"+ ]++ -- XXX say how many are expected, how many there were, what the actual arguments were?+ -- XXX distinguish between built-in and user-supplied type constructors in the error+ -- message?+ NotEnoughArgs con -> vcat+ [ "Error: not enough arguments for the type '" <> pretty' con <> "'."+ , rtd "num-args-type"+ ]++ TooManyArgs con -> vcat+ [ "Error: too many arguments for the type '" <> pretty' con <> "'."+ , rtd "num-args-type"+ ]++ -- XXX Mention the definition in which it was found, suggest adding the variable+ -- as a parameter+ UnboundTyVar v -> vcat+ [ "Error: Unknown type variable '" <> pretty' v <> "'."+ , rtd "unbound-tyvar"+ ]++ NoPolyRec s ss tys -> vcat+ [ "Error: in the definition of " <> text s <> parens (intercalate "," (map text ss)) <> ": recursive occurrences of" <+> text s <+> "may only have type variables as arguments."+ , nest 2 (+ text s <> parens (intercalate "," (map pretty' tys)) <+> "does not follow this rule."+ )+ , rtd "no-poly-rec"+ ]++ NoError -> empty++conWord :: Con -> Sem r Doc+conWord = \case+ CArr -> "function"+ CProd -> "product"+ CSum -> "sum"+ CSet -> "set"+ CBag -> "bag"+ CList -> "list"+ CContainer _ -> "container"+ CMap -> "map"+ CGraph -> "graph"+ CUser s -> text s++prettySolveError :: Members '[Reader PA, LFresh] r => SolveError -> Sem r Doc+prettySolveError = \case++ -- XXX say which types!+ NoWeakUnifier -> vcat+ [ "Error: the shape of two types does not match."+ , rtd "shape-mismatch"+ ]++ -- XXX say more! XXX HIGHEST PRIORITY!+ NoUnify -> vcat+ [ "Error: typechecking failed."+ , rtd "typecheck-fail"+ ]++ UnqualBase q b -> vcat+ [ "Error: values of type" <+> pretty' b <+> qualPhrase False q <> "."+ , rtd "not-qual"+ ]++ Unqual q ty -> vcat+ [ "Error: values of type" <+> pretty' ty <+> qualPhrase False q <> "."+ , rtd "not-qual"+ ]++ QualSkolem q a -> vcat+ [ "Error: type variable" <+> pretty' a <+> "represents any type, so we cannot assume values of that type"+ , nest 2 (qualPhrase True q) <> "."+ , rtd "qual-skolem"+ ]++qualPhrase :: Bool -> Qualifier -> Sem r Doc+qualPhrase b q+ | q `elem` [QBool, QBasic, QSimple] = "are" <+> (if b then empty else "not") <+> qualAction q+ | otherwise = "can" <> (if b then empty else "not") <+> "be" <+> qualAction q++qualAction :: Qualifier -> Sem r Doc+qualAction = \case+ QNum -> "added and multiplied"+ QSub -> "subtracted"+ QDiv -> "divided"+ QCmp -> "compared"+ QEnum -> "enumerated"+ QBool -> "boolean"+ QBasic -> "basic"+ QSimple -> "simple"+
+ src/Disco/Eval.hs view
@@ -0,0 +1,422 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeSynonymInstances #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Eval+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Top-level evaluation utilities.+-----------------------------------------------------------------------------++module Disco.Eval+ (+ -- * Effects++ EvalEffects+ , DiscoEffects++ -- * Top-level info record and associated lenses++ , DiscoConfig, initDiscoConfig, debugMode+ , TopInfo+ , replModInfo, topEnv, topModMap, lastFile, discoConfig++ -- * Running things++ , runDisco+ , runTCM, runTCMWith+ , inputTopEnv+ , parseDiscoModule+ , typecheckTop++ -- * Loading modules++ , loadDiscoModule+ , loadParsedDiscoModule+ , loadFile+ , addToREPLModule+ , setREPLModule+ , loadDefsFrom+ , loadDef++ )+ where++import Control.Arrow ((&&&))+import Control.Exception (SomeException, handle)+import Control.Lens (makeLenses, toListOf, view, (%~),+ (.~), (^.))+import Control.Monad (unless, void, when)+import Control.Monad.IO.Class (liftIO)+import Data.Bifunctor+import Data.Map (Map)+import qualified Data.Map as M+import qualified Data.Set as S+import Prelude+import System.FilePath ((-<.>))++import qualified System.Console.Haskeline as H++import Disco.Effects.Fresh+import Disco.Effects.Input+import Disco.Effects.LFresh+import Disco.Effects.State+import Polysemy+import Polysemy.Embed+import Polysemy.Error+import Polysemy.Fail+import Polysemy.Output+import Polysemy.Random+import Polysemy.Reader++import Disco.AST.Core+import Disco.AST.Surface+import Disco.Compile (compileDefns)+import Disco.Context as Ctx+import Disco.Error+import Disco.Extensions+import Disco.Interpret.CESK+import Disco.Messages+import Disco.Module+import Disco.Names+import Disco.Parser+import Disco.Pretty hiding ((<>))+import qualified Disco.Pretty as Pretty+import Disco.Typecheck (checkModule)+import Disco.Typecheck.Util+import Disco.Types+import Disco.Value++------------------------------------------------------------+-- Configuation options+------------------------------------------------------------++data DiscoConfig = DiscoConfig+ { _debugMode :: Bool+ }++makeLenses ''DiscoConfig++initDiscoConfig :: DiscoConfig+initDiscoConfig = DiscoConfig+ { _debugMode = False+ }++------------------------------------------------------------+-- Top level info record+------------------------------------------------------------++-- | A record of information about the current top-level environment.+data TopInfo = TopInfo+ { _replModInfo :: ModuleInfo+ -- ^ Info about the top-level module collecting stuff entered at+ -- the REPL.++ , _topEnv :: Env+ -- ^ Top-level environment mapping names to values. Set by+ -- 'loadDefs'.++ , _topModMap :: Map ModuleName ModuleInfo+ -- ^ Mapping from loaded module names to their 'ModuleInfo'+ -- records.++ , _lastFile :: Maybe FilePath+ -- ^ The most recent file which was :loaded by the user.++ , _discoConfig :: DiscoConfig+ }++-- | The initial (empty) record of top-level info.+initTopInfo :: DiscoConfig -> TopInfo+initTopInfo cfg = TopInfo+ { _replModInfo = emptyModuleInfo+ , _topEnv = emptyCtx+ , _topModMap = M.empty+ , _lastFile = Nothing+ , _discoConfig = cfg+ }++makeLenses ''TopInfo++------------------------------------------------------------+-- Top-level effects+------------------------------------------------------------++-- | Append two effect rows.+type family AppendEffects (r :: EffectRow) (s :: EffectRow) :: EffectRow where+ AppendEffects '[] s = s+ AppendEffects (e ': r) s = e ': AppendEffects r s++-- Didn't seem like this already existed in @polysemy@, though I+-- might have missed it. Of course we could also use a polymorphic+-- version from somewhere --- it is just type-level list append.+-- However, just manually implementing it here seems easier.++-- | Effects needed at the top level.+type TopEffects = '[Error DiscoError, State TopInfo, Output Message, Embed IO, Final (H.InputT IO)]++-- | Effects needed for evaluation.+type EvalEffects = [Error EvalError, Random, LFresh, Output Message, State Mem]+ -- XXX write about order.+ -- memory, counter etc. should not be reset by errors.++-- | All effects needed for the top level + evaluation.+type DiscoEffects = AppendEffects EvalEffects TopEffects++------------------------------------------------------------+-- Running top-level Disco computations+------------------------------------------------------------++-- | Settings for running the 'InputT' monad from @haskeline@. Just+-- uses the defaults and sets the history file to @.disco_history@.+inputSettings :: H.Settings IO+inputSettings =+ H.defaultSettings+ { H.historyFile = Just ".disco_history"+ }++-- | Run a top-level computation.+runDisco :: DiscoConfig -> (forall r. Members DiscoEffects r => Sem r ()) -> IO ()+runDisco cfg m =+ void+ . H.runInputT inputSettings+ . runFinal @(H.InputT IO)+ . embedToFinal+ . runEmbedded @_ @(H.InputT IO) liftIO+ . runOutputSem (handleMsg msgFilter) -- Handle Output Message via printing to console+ . stateToIO (initTopInfo cfg) -- Run State TopInfo via an IORef+ . inputToState -- Dispatch Input TopInfo effect via State effect+ . runState emptyMem -- Start with empty memory+ . outputDiscoErrors -- Output any top-level errors+ . runLFresh -- Generate locally fresh names+ . runRandomIO -- Generate randomness via IO+ . mapError EvalErr -- Embed runtime errors into top-level error type+ . failToError Panic -- Turn pattern-match failures into a Panic error+ . runReader emptyCtx -- Keep track of current Env+ $ m+ where+ msgFilter+ | cfg ^. debugMode = const True+ | otherwise = (/= Debug) . view messageType++------------------------------------------------------------+-- Environment utilities+------------------------------------------------------------++-- XXX change name to inputREPLEnv, modify to actually get the Env+-- from the REPL module info?++-- | Run a computation that needs an input environment, grabbing the+-- current top-level environment from the 'TopInfo' records.+inputTopEnv :: Member (Input TopInfo) r => Sem (Input Env ': r) a -> Sem r a+inputTopEnv m = do+ e <- inputs (view topEnv)+ runInputConst e m++------------------------------------------------------------+-- High-level disco phases+------------------------------------------------------------++--------------------------------------------------+-- Parsing++-- | Parse a module from a file, re-throwing a parse error if it+-- fails.+parseDiscoModule :: Members '[Error DiscoError, Embed IO] r => FilePath -> Sem r Module+parseDiscoModule file = do+ str <- liftIO $ readFile file+ fromEither . first ParseErr $ runParser (wholeModule Standalone) file str++--------------------------------------------------+-- Type checking++-- | Run a typechecking computation, providing it with local+-- (initially empty) contexts for variable types and type+-- definitions.+runTCM ::+ Member (Error DiscoError) r =>+ Sem (Reader TyCtx ': Reader TyDefCtx ': Fresh ': Error TCError ': r) a ->+ Sem r a+runTCM = runTCMWith emptyCtx M.empty++-- | Run a typechecking computation, providing it with local contexts+-- (initialized to the provided arguments) for variable types and+-- type definitions.+runTCMWith ::+ Member (Error DiscoError) r =>+ TyCtx ->+ TyDefCtx ->+ Sem (Reader TyCtx ': Reader TyDefCtx ': Fresh ': Error TCError ': r) a ->+ Sem r a+runTCMWith tyCtx tyDefCtx =+ mapError TypeCheckErr+ . runFresh+ . runReader @TyDefCtx tyDefCtx+ . runReader @TyCtx tyCtx++-- | Run a typechecking computation in the context of the top-level+-- REPL module, re-throwing a wrapped error if it fails.+typecheckTop+ :: Members '[Input TopInfo, Error DiscoError] r+ => Sem (Reader TyCtx ': Reader TyDefCtx ': Fresh ': Error TCError ': r) a+ -> Sem r a+typecheckTop tcm = do+ tyctx <- inputs (view (replModInfo . miTys))+ imptyctx <- inputs (toListOf (replModInfo . miImports . traverse . miTys))+ tydefs <- inputs (view (replModInfo . miTydefs))+ imptydefs <- inputs (toListOf (replModInfo . miImports . traverse . miTydefs))+ runTCMWith (tyctx <> mconcat imptyctx) (tydefs <> mconcat imptydefs) tcm++--------------------------------------------------+-- Loading++-- | Recursively loads a given module by first recursively loading and+-- typechecking its imported modules, adding the obtained+-- 'ModuleInfo' records to a map from module names to info records,+-- and then typechecking the parent module in an environment with+-- access to this map. This is really just a depth-first search.+--+-- The 'Resolver' argument specifies where to look for imported+-- modules.+loadDiscoModule+ :: Members '[State TopInfo, Output Message, Random, State Mem, Error DiscoError, Embed IO] r+ => Bool -> Resolver -> FilePath -> Sem r ModuleInfo+loadDiscoModule quiet resolver =+ loadDiscoModule' quiet resolver []++-- | Like 'loadDiscoModule', but start with an already parsed 'Module'+-- instead of loading a module from disk by name. Also, check it in+-- a context that includes the current top-level context (unlike a+-- module loaded from disk). Used for e.g. blocks/modules entered+-- at the REPL prompt.+loadParsedDiscoModule+ :: Members '[State TopInfo, Output Message, Random, State Mem, Error DiscoError, Embed IO] r+ => Bool -> Resolver -> ModuleName -> Module -> Sem r ModuleInfo+loadParsedDiscoModule quiet resolver =+ loadParsedDiscoModule' quiet REPL resolver []++-- | Recursively load a Disco module while keeping track of an extra+-- Map from module names to 'ModuleInfo' records, to avoid loading+-- any imported module more than once. Resolve the module, load and+-- parse it, then call 'loadParsedDiscoModule''.+loadDiscoModule'+ :: Members '[State TopInfo, Output Message, Random, State Mem, Error DiscoError, Embed IO] r+ => Bool -> Resolver -> [ModuleName] -> FilePath+ -> Sem r ModuleInfo+loadDiscoModule' quiet resolver inProcess modPath = do+ (resolvedPath, prov) <- resolveModule resolver modPath+ >>= maybe (throw $ ModuleNotFound modPath) return+ let name = Named prov modPath+ when (name `elem` inProcess) (throw $ CyclicImport (name:inProcess))+ modMap <- use @TopInfo topModMap+ case M.lookup name modMap of+ Just mi -> return mi+ Nothing -> do+ unless quiet $ info $ "Loading" <+> text (modPath -<.> "disco") Pretty.<> "..."+ cm <- parseDiscoModule resolvedPath+ loadParsedDiscoModule' quiet Standalone resolver (name : inProcess) name cm++-- | A list of standard library module names, which should always be+-- loaded implicitly.+stdLib :: [String]+stdLib = ["list", "container"]++-- | Recursively load an already-parsed Disco module while keeping+-- track of an extra Map from module names to 'ModuleInfo' records,+-- to avoid loading any imported module more than once. Typecheck+-- it in the context of the top-level type context iff the+-- 'LoadingMode' parameter is 'REPL'. Recursively load all its+-- imports, then typecheck it.+loadParsedDiscoModule'+ :: Members '[State TopInfo, Output Message, Random, State Mem, Error DiscoError, Embed IO] r+ => Bool -> LoadingMode -> Resolver -> [ModuleName] -> ModuleName -> Module -> Sem r ModuleInfo+loadParsedDiscoModule' quiet mode resolver inProcess name cm@(Module _ mns _ _ _) = do++ -- Recursively load any modules imported by this one, plus standard+ -- library modules (unless NoStdLib is enabled), and build a map with the results.+ mis <- mapM (loadDiscoModule' quiet (withStdlib resolver) inProcess) mns+ stdmis <- case NoStdLib `S.member` modExts cm of+ True -> return []+ False -> mapM (loadDiscoModule' True FromStdlib inProcess) stdLib+ let modImps = M.fromList (map (view miName &&& id) (mis ++ stdmis))++ -- Get context and type definitions from the REPL, in case we are in REPL mode.+ topImports <- use (replModInfo . miImports)+ topTyCtx <- use (replModInfo . miTys)+ topTyDefns <- use (replModInfo . miTydefs)++ -- Choose the contexts to use based on mode: if we are loading a+ -- standalone module, we should start it in an empty context. If we+ -- are loading something entered at the REPL, we need to include any+ -- existing top-level REPL context.+ let importMap = case mode of { Standalone -> modImps; REPL -> topImports <> modImps }+ tyctx = case mode of { Standalone -> emptyCtx ; REPL -> topTyCtx }+ tydefns = case mode of { Standalone -> M.empty ; REPL -> topTyDefns }++ -- Typecheck (and resolve names in) the module.+ m <- runTCMWith tyctx tydefns $ checkModule name importMap cm++ -- Evaluate all the module definitions and add them to the topEnv.+ mapError EvalErr $ loadDefsFrom m++ -- Record the ModuleInfo record in the top-level map.+ modify (topModMap %~ M.insert name m)+ return m++-- | Try loading the contents of a file from the filesystem, emitting+-- an error if it's not found.+loadFile :: Members '[Output Message, Embed IO] r => FilePath -> Sem r (Maybe String)+loadFile file = do+ res <- liftIO $ handle @SomeException (return . Left) (Right <$> readFile file)+ case res of+ Left _ -> info ("File not found:" <+> text file) >> return Nothing+ Right s -> return (Just s)++-- | Add things from the given module to the set of currently loaded+-- things.+addToREPLModule+ :: Members '[Error DiscoError, State TopInfo, Random, State Mem, Output Message] r+ => ModuleInfo -> Sem r ()+addToREPLModule mi = do+ curMI <- use @TopInfo replModInfo+ mi' <- mapError TypeCheckErr $ combineModuleInfo [curMI, mi]+ modify @TopInfo $ replModInfo .~ mi'++-- | Set the given 'ModuleInfo' record as the currently loaded+-- module. This also includes updating the top-level state with new+-- term definitions, documentation, types, and type definitions.+-- Replaces any previously loaded module.+setREPLModule+ :: Members '[State TopInfo, Random, Error EvalError, State Mem, Output Message] r+ => ModuleInfo -> Sem r ()+setREPLModule mi = do+ modify @TopInfo $ replModInfo .~ mi++-- | Populate various pieces of the top-level info record (docs, type+-- context, type and term definitions) from the 'ModuleInfo' record+-- corresponding to the currently loaded module, and load all the+-- definitions into the current top-level environment.+loadDefsFrom ::+ Members '[State TopInfo, Random, Error EvalError, State Mem] r =>+ ModuleInfo ->+ Sem r ()+loadDefsFrom mi = do++ -- Note that the compiled definitions we get back from compileDefns+ -- are topologically sorted by mutually recursive group. Each+ -- definition needs to be evaluated in an environment containing the+ -- previous ones.++ mapM_ (uncurry loadDef) (compileDefns (mi ^. miTermdefs))++loadDef ::+ Members '[State TopInfo, Random, Error EvalError, State Mem] r =>+ QName Core -> Core -> Sem r ()+loadDef x body = do+ v <- inputToState @TopInfo . inputTopEnv $ eval body+ modify @TopInfo $ topEnv %~ Ctx.insert x v
+ src/Disco/Extensions.hs view
@@ -0,0 +1,48 @@+-- |+-- Module : Disco.Extensions+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Optional extensions to the disco language.+module Disco.Extensions+ ( Ext (..),+ ExtSet,+ defaultExts,+ allExts,+ allExtsList,+ addExtension,+ )+where++import Data.Set (Set)+import qualified Data.Set as S++type ExtSet = Set Ext++-- | Enumeration of optional language extensions.+data Ext+ = -- | Allow primitives, i.e. @$prim@+ Primitives+ | -- | Don't automatically import standard library modules+ NoStdLib+ | -- | Allow randomness. This is not implemented yet.+ Randomness+ deriving (Eq, Ord, Show, Read, Enum, Bounded)++-- | The default set of language extensions (currently, the empty set).+defaultExts :: ExtSet+defaultExts = S.empty++-- | A set of all possible language extensions, provided for convenience.+allExts :: ExtSet+allExts = S.fromList allExtsList++-- | All possible language extensions in the form of a list.+allExtsList :: [Ext]+allExtsList = [minBound .. maxBound]++-- | Add an extension to an extension set.+addExtension :: Ext -> ExtSet -> ExtSet+addExtension = S.insert
+ src/Disco/Interactive/CmdLine.hs view
@@ -0,0 +1,193 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Interactive.CmdLine+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Definition of the command-line REPL interface for Disco.+--+-----------------------------------------------------------------------------++module Disco.Interactive.CmdLine+ ( -- * Command-line options record++ DiscoOpts(..)++ -- * optparse-applicative command line parsers+ , discoOpts, discoInfo++ -- * main++ , discoMain++ ) where++import Control.Lens hiding (use)+import Control.Monad (unless)+import qualified Control.Monad.Catch as CMC+import Control.Monad.IO.Class (MonadIO (..))+import Data.Foldable (forM_)+import Data.List (isPrefixOf)+import Data.Maybe (isJust)+import System.Exit (exitFailure,+ exitSuccess)++import qualified Options.Applicative as O+import System.Console.Haskeline as H++import Disco.Error+import Disco.Eval+import Disco.Interactive.Commands+import Disco.Messages+import Disco.Module (miExts)+import Disco.Pretty++import Disco.Effects.State+import Polysemy+import Polysemy.ConstraintAbsorber.MonadCatch+import Polysemy.Error++------------------------------------------------------------+-- Command-line options parser+------------------------------------------------------------++-- | Command-line options for disco.+data DiscoOpts = DiscoOpts+ { evaluate :: Maybe String -- ^ A single expression to evaluate+ , cmdFile :: Maybe String -- ^ Execute the commands in a given file+ , checkFile :: Maybe String -- ^ Check a file and then exit+ , debugFlag :: Bool+ }++discoOpts :: O.Parser DiscoOpts+discoOpts = DiscoOpts+ <$> O.optional (+ O.strOption (mconcat+ [ O.long "evaluate"+ , O.short 'e'+ , O.help "evaluate an expression"+ , O.metavar "TERM"+ ])+ )+ <*> O.optional (+ O.strOption (mconcat+ [ O.long "file"+ , O.short 'f'+ , O.help "execute the commands in a file"+ , O.metavar "FILE"+ ])+ )+ <*> O.optional (+ O.strOption (mconcat+ [ O.long "check"+ , O.help "check a file without starting the interactive REPL"+ , O.metavar "FILE"+ ])+ )+ <*> O.switch (+ mconcat+ [ O.long "debug"+ , O.help "print debugging information"+ , O.short 'd'+ ]+ )++discoInfo :: O.ParserInfo DiscoOpts+discoInfo = O.info (O.helper <*> discoOpts) $ mconcat+ [ O.fullDesc+ , O.progDesc "Command-line interface for Disco, a programming language for discrete mathematics."+ , O.header "disco v0.1"+ ]++optsToCfg :: DiscoOpts -> DiscoConfig+optsToCfg opts = initDiscoConfig & debugMode .~ debugFlag opts++------------------------------------------------------------+-- Command-line interface+------------------------------------------------------------++banner :: String+banner = "Welcome to Disco!\n\nA language for programming discrete mathematics.\n\n"++discoMain :: IO ()+discoMain = do+ opts <- O.execParser discoInfo++ let batch = any isJust [evaluate opts, cmdFile opts, checkFile opts]+ unless batch $ putStr banner+ runDisco (optsToCfg opts) $ do+ case checkFile opts of+ Just file -> do+ res <- handleLoad file+ liftIO $ if res then exitSuccess else exitFailure+ Nothing -> return ()+ case cmdFile opts of+ Just file -> do+ mcmds <- loadFile file+ case mcmds of+ Nothing -> return ()+ Just cmds -> mapM_ handleCMD (lines cmds)+ Nothing -> return ()+ forM_ (evaluate opts) handleCMD+ unless batch loop++ where++ -- These types used to involve InputT Disco, but we now use Final+ -- (InputT IO) in the list of effects. see+ -- https://github.com/polysemy-research/polysemy/issues/395 for+ -- inspiration.++ ctrlC :: MonadIO m => m a -> SomeException -> m a+ ctrlC act e = do+ liftIO $ print e+ act++ withCtrlC :: (MonadIO m, CMC.MonadCatch m) => m a -> m a -> m a+ withCtrlC resume act = CMC.catch act (ctrlC resume)++ loop :: Members DiscoEffects r => Sem r ()+ loop = do+ minput <- embedFinal $ withCtrlC (return $ Just "") (getInputLine "Disco> ")+ case minput of+ Nothing -> return ()+ Just input+ | ":q" `isPrefixOf` input && input `isPrefixOf` ":quit" -> do+ liftIO $ putStrLn "Goodbye!"+ return ()+ | ":{" `isPrefixOf` input -> do+ multiLineLoop []+ loop+ | otherwise -> do+ mapError @_ @DiscoError (Panic . show) $+ absorbMonadCatch $+ withCtrlC (return ()) $+ handleCMD input+ loop++ multiLineLoop :: Members DiscoEffects r => [String] -> Sem r ()+ multiLineLoop ls = do+ minput <- embedFinal $ withCtrlC (return Nothing) (getInputLine "Disco| ")+ case minput of+ Nothing -> return ()+ Just input+ | ":}" `isPrefixOf` input -> do+ mapError @_ @DiscoError (Panic . show) $+ absorbMonadCatch $+ withCtrlC (return ()) $+ handleCMD (unlines (reverse ls))+ | otherwise -> do+ multiLineLoop (input:ls)++-- | Parse and run the command corresponding to some REPL input.+handleCMD :: Members DiscoEffects r => String -> Sem r ()+handleCMD "" = return ()+handleCMD s = do+ exts <- use @TopInfo (replModInfo . miExts)+ case parseLine discoCommands exts s of+ Left m -> info (text m)+ Right l -> catch @DiscoError (dispatch discoCommands l) (info . pretty')+ -- The above has to be catch, not outputErrors, because+ -- the latter won't resume afterwards.
+ src/Disco/Interactive/Commands.hs view
@@ -0,0 +1,785 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Interactive.Commands+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Defining and dispatching all commands/functionality available at+-- the REPL prompt.+-----------------------------------------------------------------------------++module Disco.Interactive.Commands+ ( dispatch,+ discoCommands,+ handleLoad,+ loadFile,+ parseLine+ ) where++import Control.Arrow ((&&&))+import Control.Lens (to, view, (%~), (.~), (?~),+ (^.))+import Control.Monad.Except+import Data.Char (isSpace)+import Data.Coerce+import Data.List (find, isPrefixOf, sortBy)+import Data.Map ((!))+import qualified Data.Map as M+import Data.Typeable+import Prelude as P+import System.FilePath (splitFileName)++import Text.Megaparsec hiding (State, runParser)+import qualified Text.Megaparsec.Char as C+import Unbound.Generics.LocallyNameless (Name, name2String,+ string2Name)++import Disco.Effects.Input+import Disco.Effects.LFresh+import Disco.Effects.State+import Polysemy+import Polysemy.Error hiding (try)+import Polysemy.Output+import Polysemy.Reader++import Data.Maybe (maybeToList)+import Disco.AST.Surface+import Disco.AST.Typed+import Disco.Compile+import Disco.Context as Ctx+import Disco.Desugar+import Disco.Error+import Disco.Eval+import Disco.Extensions+import Disco.Interpret.CESK+import Disco.Messages+import Disco.Module+import Disco.Names+import Disco.Parser (Parser, ident, reservedOp,+ runParser, sc, symbol, term,+ wholeModule, withExts)+import Disco.Pretty hiding (empty, (<>))+import qualified Disco.Pretty as Pretty+import Disco.Syntax.Operators+import Disco.Syntax.Prims (Prim (PrimBOp, PrimUOp),+ primDoc, primReference,+ toPrim)+import Disco.Typecheck+import Disco.Typecheck.Erase+import Disco.Types (toPolyType)+import Disco.Value++------------------------------------------------------------+-- REPL expression type+------------------------------------------------------------++-- | Data type to represent things typed at the Disco REPL. Each+-- constructor has a singleton type to facilitate dispatch.+data REPLExpr :: CmdTag -> * where+ TypeCheck :: Term -> REPLExpr 'CTypeCheck -- Typecheck a term+ Eval :: Module -> REPLExpr 'CEval -- Evaluate a block+ TestProp :: Term -> REPLExpr 'CTestProp -- Run a property test+ ShowDefn :: Name Term -> REPLExpr 'CShowDefn -- Show a variable's definition+ Parse :: Term -> REPLExpr 'CParse -- Show the parsed AST+ Pretty :: Term -> REPLExpr 'CPretty -- Pretty-print a term+ Ann :: Term -> REPLExpr 'CAnn -- Show type-annotated term+ Desugar :: Term -> REPLExpr 'CDesugar -- Show a desugared term+ Compile :: Term -> REPLExpr 'CCompile -- Show a compiled term+ Load :: FilePath -> REPLExpr 'CLoad -- Load a file.+ Reload :: REPLExpr 'CReload -- Reloads the most recently+ -- loaded file.+ Doc :: Either (Name Term) Prim -> REPLExpr 'CDoc -- Show documentation.+ Nop :: REPLExpr 'CNop -- No-op, e.g. if the user+ -- just enters a comment+ Help :: REPLExpr 'CHelp -- Show help+ Names :: REPLExpr 'CNames -- Show bound names++deriving instance Show (REPLExpr c)++-- | An existential wrapper around any REPL expression.+data SomeREPLExpr where+ SomeREPL :: Typeable c => REPLExpr c -> SomeREPLExpr++------------------------------------------------------------+-- REPL command types+------------------------------------------------------------++data REPLCommandCategory+ = -- | REPL commands for everyday users+ User+ | -- | REPL commands for developers working on Disco+ Dev+ deriving (Eq, Show)++data REPLCommandType+ = -- | Things that don't start with a colon: eval and nop+ BuiltIn+ | -- | Things that start with a colon, e.g. :help, :names, :load...+ ColonCmd+ deriving (Eq, Show)++-- | Tags used at the type level to denote each REPL command.+data CmdTag+ = CTypeCheck+ | CEval+ | CShowDefn+ | CParse+ | CPretty+ | CAnn+ | CDesugar+ | CCompile+ | CLoad+ | CReload+ | CDoc+ | CNop+ | CHelp+ | CNames+ | CTestProp+ deriving (Show, Eq, Typeable)++------------------------------------------------------------+-- REPL command info record+------------------------------------------------------------++-- | Data type to represent all the information about a single REPL+-- command.+data REPLCommand (c :: CmdTag) = REPLCommand+ { -- | Name of the command+ name :: String,+ -- | Help text showing how to use the command, e.g. ":ann <term>"+ helpcmd :: String,+ -- | Short free-form text explaining the command.+ -- We could also consider adding long help text as well.+ shortHelp :: String,+ -- | Is the command for users or devs?+ category :: REPLCommandCategory,+ -- | Is it a built-in command or colon command?+ cmdtype :: REPLCommandType,+ -- | The action to execute,+ -- given the input to the+ -- command.+ action :: REPLExpr c -> (forall r. Members DiscoEffects r => Sem r ()),+ -- | Parser for the command argument(s).+ parser :: Parser (REPLExpr c)+ }++-- | An existential wrapper around any REPL command info record.+data SomeREPLCommand where+ SomeCmd :: Typeable c => REPLCommand c -> SomeREPLCommand++------------------------------------------------------------+-- REPL command lists+------------------------------------------------------------++type REPLCommands = [SomeREPLCommand]++-- | Keep only commands of a certain type.+byCmdType :: REPLCommandType -> REPLCommands -> REPLCommands+byCmdType ty = P.filter (\(SomeCmd rc) -> cmdtype rc == ty)++-- | Given a list of REPL commands and something typed at the REPL,+-- pick the first command with a matching type-level tag and run its+-- associated action.+dispatch :: Members DiscoEffects r => REPLCommands -> SomeREPLExpr -> Sem r ()+dispatch [] _ = return ()+dispatch (SomeCmd c : cs) r@(SomeREPL e) = case gcast e of+ Just e' -> outputDiscoErrors $ action c e'+ Nothing -> dispatch cs r++-- | The list of all commands that can be used at the REPL.+-- Resolution of REPL commands searches this list /in order/, which+-- means ambiguous command prefixes (e.g. :t for :type) are resolved+-- to the first matching command.+discoCommands :: REPLCommands+discoCommands =+ [ SomeCmd annCmd,+ SomeCmd compileCmd,+ SomeCmd desugarCmd,+ SomeCmd docCmd,+ SomeCmd evalCmd,+ SomeCmd helpCmd,+ SomeCmd loadCmd,+ SomeCmd namesCmd,+ SomeCmd nopCmd,+ SomeCmd parseCmd,+ SomeCmd prettyCmd,+ SomeCmd reloadCmd,+ SomeCmd showDefnCmd,+ SomeCmd typeCheckCmd,+ SomeCmd testPropCmd+ ]++------------------------------------------------------------+-- Parsing+------------------------------------------------------------++builtinCommandParser :: REPLCommands -> Parser SomeREPLExpr+builtinCommandParser =+ foldr ((<|>) . (\(SomeCmd rc) -> SomeREPL <$> try (parser rc))) empty+ . byCmdType BuiltIn++-- | Parse one of the colon commands in the given list of commands.+commandParser :: REPLCommands -> Parser SomeREPLExpr+commandParser allCommands =+ (symbol ":" *> many C.lowerChar) >>= parseCommandArgs allCommands++-- | Given a list of available commands and a string seen after a+-- colon, return a parser for its arguments.+parseCommandArgs :: REPLCommands -> String -> Parser SomeREPLExpr+parseCommandArgs allCommands cmd = maybe badCmd snd $ find ((cmd `isPrefixOf`) . fst) parsers+ where+ badCmd = fail $ "Command \":" ++ cmd ++ "\" is unrecognized."++ parsers =+ map (\(SomeCmd rc) -> (name rc, SomeREPL <$> parser rc)) $+ byCmdType ColonCmd allCommands++-- | Parse a file name.+fileParser :: Parser FilePath+fileParser = many C.spaceChar *> many (satisfy (not . isSpace))++-- | A parser for something entered at the REPL prompt.+lineParser :: REPLCommands -> Parser SomeREPLExpr+lineParser allCommands =+ builtinCommandParser allCommands+ <|> commandParser allCommands++-- | Given a list of available REPL commands and the currently enabled+-- extensions, parse a string entered at the REPL prompt, returning+-- either a parse error message or a parsed REPL expression.+parseLine :: REPLCommands -> ExtSet -> String -> Either String SomeREPLExpr+parseLine allCommands exts s =+ case runParser (withExts exts (lineParser allCommands)) "" s of+ Left e -> Left $ errorBundlePretty e+ Right l -> Right l++--------------------------------------------------------------------------------+-- The commands!+--------------------------------------------------------------------------------++------------------------------------------------------------+-- :ann++annCmd :: REPLCommand 'CAnn+annCmd =+ REPLCommand+ { name = "ann",+ helpcmd = ":ann",+ shortHelp = "Show type-annotated typechecked term",+ category = Dev,+ cmdtype = ColonCmd,+ action = \x -> inputToState @TopInfo . handleAnn $ x,+ parser = Ann <$> term+ }++handleAnn ::+ Members '[Error DiscoError, Input TopInfo, Output Message] r =>+ REPLExpr 'CAnn ->+ Sem r ()+handleAnn (Ann t) = do+ (at, _) <- typecheckTop $ inferTop t+ infoPretty at++------------------------------------------------------------+-- :compile++compileCmd :: REPLCommand 'CCompile+compileCmd =+ REPLCommand+ { name = "compile",+ helpcmd = ":compile",+ shortHelp = "Show a compiled term",+ category = Dev,+ cmdtype = ColonCmd,+ action = \x -> inputToState @TopInfo . handleCompile $ x,+ parser = Compile <$> term+ }++handleCompile ::+ Members '[Error DiscoError, Input TopInfo, Output Message] r =>+ REPLExpr 'CCompile ->+ Sem r ()+handleCompile (Compile t) = do+ (at, _) <- typecheckTop $ inferTop t+ infoPretty . compileTerm $ at++------------------------------------------------------------+-- :desugar++desugarCmd :: REPLCommand 'CDesugar+desugarCmd =+ REPLCommand+ { name = "desugar",+ helpcmd = ":desugar",+ shortHelp = "Show a desugared term",+ category = Dev,+ cmdtype = ColonCmd,+ action = \x -> inputToState @TopInfo . handleDesugar $ x,+ parser = Desugar <$> term+ }++handleDesugar ::+ Members '[Error DiscoError, Input TopInfo, LFresh, Output Message] r =>+ REPLExpr 'CDesugar ->+ Sem r ()+handleDesugar (Desugar t) = do+ (at, _) <- typecheckTop $ inferTop t+ info $ pretty' . eraseDTerm . runDesugar . desugarTerm $ at++------------------------------------------------------------+-- :doc++docCmd :: REPLCommand 'CDoc+docCmd =+ REPLCommand+ { name = "doc",+ helpcmd = ":doc <term>",+ shortHelp = "Show documentation",+ category = User,+ cmdtype = ColonCmd,+ action = \x -> inputToState @TopInfo . handleDoc $ x,+ parser = Doc <$> parseDoc+ }++parseDoc :: Parser (Either (Name Term) Prim)+parseDoc =+ try (Left <$> (sc *> ident))+ <|> (Right <$> (parseNakedOpPrim <?> "operator"))++handleDoc ::+ Members '[Error DiscoError, Input TopInfo, LFresh, Output Message] r =>+ REPLExpr 'CDoc ->+ Sem r ()+handleDoc (Doc (Left x)) = do+ ctx <- inputs @TopInfo (view (replModInfo . miTys))+ tydefs <- inputs @TopInfo (view (replModInfo . miTydefs))+ docs <- inputs @TopInfo (view (replModInfo . miDocs))++ debug $ text . show $ docs++ case (Ctx.lookupAll' x ctx, M.lookup (name2String x) tydefs) of+ ([], Nothing) ->+ -- Maybe the variable name entered by the user is actually a prim.+ case toPrim (name2String x) of+ (prim:_) -> handleDoc (Doc (Right prim))+ _ -> err $ "No documentation found for" <+> pretty' x <> "."+ (binds, def) ->+ mapM_ (showDoc docs) (map Left binds ++ map Right (maybeToList def))++ where+ showDoc docMap (Left (qn, ty)) = info $+ hsep [pretty' x, ":", pretty' ty]+ $+$+ case Ctx.lookup' qn docMap of+ Just (DocString ss : _) -> vcat (text "" : map text ss ++ [text ""])+ _ -> Pretty.empty+ showDoc docMap (Right tdBody) = info $+ pretty' (name2String x, tdBody)+ $+$+ case Ctx.lookupAll' x docMap of+ ((_, DocString ss : _) : _) -> vcat (text "" : map text ss ++ [text ""])+ _ -> Pretty.empty+handleDoc (Doc (Right prim)) = do+ handleTypeCheck (TypeCheck (TPrim prim))+ info $ vcat+ [ case prim of+ PrimUOp u -> describeAlts (f == Post) (f == Pre) syns+ where+ OpInfo (UOpF f _) syns _ = uopMap ! u+ PrimBOp b -> describeAlts True True (opSyns $ bopMap ! b)+ _ -> Pretty.empty+ , case prim of+ PrimUOp u -> describePrec (uPrec u)+ PrimBOp b -> describePrec (bPrec b) <> describeFixity (assoc b)+ _ -> Pretty.empty+ ]+ case (M.lookup prim primDoc, M.lookup prim primReference) of+ (Nothing, Nothing) -> return ()+ (Nothing, Just p) -> info $ mkReference p+ (Just d, mp) ->+ info $ "" $+$ text d $+$ "" $+$ maybe Pretty.empty (\p -> mkReference p $+$ "") mp+ where+ describePrec p = "precedence level" <+> text (show p)+ describeFixity In = Pretty.empty+ describeFixity InL = ", left associative"+ describeFixity InR = ", right associative"+ describeAlts _ _ [] = Pretty.empty+ describeAlts _ _ [_] = Pretty.empty+ describeAlts pre post (_:alts) = "Alternative syntax:" <+> intercalate "," (map showOp alts)+ where+ showOp op = hcat+ [ if pre then "~" else Pretty.empty+ , text op+ , if post then "~" else Pretty.empty]+++ mkReference p =+ "https://disco-lang.readthedocs.io/en/latest/reference/" <> text p <> ".html"++------------------------------------------------------------+-- eval++evalCmd :: REPLCommand 'CEval+evalCmd = REPLCommand+ { name = "eval"+ , helpcmd = "<code>"+ , shortHelp = "Evaluate a block of code"+ , category = User+ , cmdtype = BuiltIn+ , action = \x -> handleEval x+ , parser = Eval <$> wholeModule REPL+ }++handleEval+ :: Members (Error DiscoError ': State TopInfo ': Output Message ': Embed IO ': EvalEffects) r+ => REPLExpr 'CEval -> Sem r ()+handleEval (Eval m) = do+ mi <- inputToState @TopInfo $ loadParsedDiscoModule False FromCwdOrStdlib REPLModule m+ addToREPLModule mi+ forM_ (mi ^. miTerms) (mapError EvalErr . evalTerm . fst)+ -- garbageCollect?++evalTerm :: Members (Error EvalError ': State TopInfo ': Output Message ': EvalEffects) r => ATerm -> Sem r Value+evalTerm at = do+ env <- use @TopInfo topEnv+ v <- runInputConst env $ eval (compileTerm at)++ tydefs <- use @TopInfo (replModInfo . to allTydefs)+ info $ runInputConst tydefs $ prettyValue' ty v++ modify @TopInfo $+ (replModInfo . miTys %~ Ctx.insert (QName (QualifiedName REPLModule) (string2Name "it")) (toPolyType ty)) .+ (topEnv %~ Ctx.insert (QName (QualifiedName REPLModule) (string2Name "it")) v)+ return v+ where+ ty = getType at++------------------------------------------------------------+-- :help++helpCmd :: REPLCommand 'CHelp+helpCmd =+ REPLCommand+ { name = "help",+ helpcmd = ":help",+ shortHelp = "Show help",+ category = User,+ cmdtype = ColonCmd,+ action = \x -> handleHelp x,+ parser = return Help+ }++handleHelp :: Member (Output Message) r => REPLExpr 'CHelp -> Sem r ()+handleHelp Help =+ info $+ vcat+ [ "Commands available from the prompt:"+ , text ""+ , vcat (map (\(SomeCmd c) -> showCmd c) $ sortedList discoCommands)+ , text ""+ ]+ where+ maxlen = longestCmd discoCommands+ sortedList cmds =+ sortBy (\(SomeCmd x) (SomeCmd y) -> compare (name x) (name y)) $ filteredCommands cmds+ -- don't show dev-only commands by default+ filteredCommands cmds = P.filter (\(SomeCmd c) -> category c == User) cmds+ showCmd c = text (padRight (helpcmd c) maxlen ++ " " ++ shortHelp c)+ longestCmd cmds = maximum $ map (\(SomeCmd c) -> length $ helpcmd c) cmds+ padRight s maxsize = take maxsize (s ++ repeat ' ')++------------------------------------------------------------+-- :load++loadCmd :: REPLCommand 'CLoad+loadCmd =+ REPLCommand+ { name = "load",+ helpcmd = ":load <filename>",+ shortHelp = "Load a file",+ category = User,+ cmdtype = ColonCmd,+ action = \x -> handleLoadWrapper x,+ parser = Load <$> fileParser+ }++-- | Parses, typechecks, and loads a module by first recursively loading any imported+-- modules by calling loadDiscoModule. If no errors are thrown, any tests present+-- in the parent module are executed.+-- Disco.Interactive.CmdLine uses a version of this function that returns a Bool.+handleLoadWrapper ::+ Members (Error DiscoError ': State TopInfo ': Output Message ': Embed IO ': EvalEffects) r =>+ REPLExpr 'CLoad ->+ Sem r ()+handleLoadWrapper (Load fp) = void (handleLoad fp)++handleLoad ::+ Members (Error DiscoError ': State TopInfo ': Output Message ': Embed IO ': EvalEffects) r =>+ FilePath ->+ Sem r Bool+handleLoad fp = do+ let (directory, modName) = splitFileName fp++ -- Reset top-level module map and context to empty, so we start+ -- fresh and pick up any changes to imported modules etc.+ modify @TopInfo $ topModMap .~ M.empty+ modify @TopInfo $ topEnv .~ Ctx.emptyCtx++ -- Load the module.+ m <- inputToState @TopInfo $ loadDiscoModule False (FromDir directory) modName+ setREPLModule m++ -- Now run any tests+ t <- inputToState $ runAllTests (m ^. miProps)++ -- Remember which was the most recently loaded file, so we can :reload+ modify @TopInfo (lastFile ?~ fp)+ info "Loaded."+ return t++-- XXX Return a structured summary of the results, not a Bool;+-- separate out results generation and pretty-printing, & move this+-- somewhere else.+runAllTests :: Members (Output Message ': Input TopInfo ': EvalEffects) r => Ctx ATerm [AProperty] -> Sem r Bool -- (Ctx ATerm [TestResult])+runAllTests aprops+ | Ctx.null aprops = return True+ | otherwise = do+ info "Running tests..."+ and <$> mapM (uncurry runTests) (Ctx.assocs aprops)++ where+ numSamples :: Int+ numSamples = 50 -- XXX make this configurable somehow++ runTests :: Members (Output Message ': Input TopInfo ': EvalEffects) r => QName ATerm -> [AProperty] -> Sem r Bool+ runTests (QName _ n) props = do+ results <- inputTopEnv $ traverse (sequenceA . (id &&& runTest numSamples)) props+ let failures = P.filter (not . testIsOk . snd) results+ hdr = pretty' n <> ":"++ case P.null failures of+ True -> info $ nest 2 $ hdr <+> "OK"+ False -> do+ tydefs <- inputs @TopInfo (view (replModInfo . to allTydefs))+ let prettyFailures =+ runInputConst tydefs . runReader initPA . runLFresh $+ bulletList "-" $ map (uncurry prettyTestFailure) failures+ info $ nest 2 $ hdr $+$ prettyFailures+ return (P.null failures)++------------------------------------------------------------+-- :names++namesCmd :: REPLCommand 'CNames+namesCmd =+ REPLCommand+ { name = "names",+ helpcmd = ":names",+ shortHelp = "Show all names in current scope",+ category = User,+ cmdtype = ColonCmd,+ action = \x -> inputToState . handleNames $ x,+ parser = return Names+ }++-- | Show names and types for each item in the top-level context.+handleNames ::+ Members '[Input TopInfo, LFresh, Output Message] r =>+ REPLExpr 'CNames ->+ Sem r ()+handleNames Names = do+ tyDef <- inputs @TopInfo (view (replModInfo . miTydefs))+ ctx <- inputs @TopInfo (view (replModInfo . miTys))+ info $+ vcat (map pretty' (M.assocs tyDef))+ $+$+ vcat (map showFn (Ctx.assocs ctx))+ where+ showFn (QName _ x, ty) = hsep [pretty' x, text ":", pretty' ty]++------------------------------------------------------------+-- nop++nopCmd :: REPLCommand 'CNop+nopCmd =+ REPLCommand+ { name = "nop",+ helpcmd = "",+ shortHelp = "No-op, e.g. if the user just enters a comment",+ category = Dev,+ cmdtype = BuiltIn,+ action = \x -> handleNop x,+ parser = Nop <$ (sc <* eof)+ }++handleNop :: REPLExpr 'CNop -> Sem r ()+handleNop Nop = pure ()++------------------------------------------------------------+-- :parse++parseCmd :: REPLCommand 'CParse+parseCmd =+ REPLCommand+ { name = "parse",+ helpcmd = ":parse <expr>",+ shortHelp = "Show the parsed AST",+ category = Dev,+ cmdtype = ColonCmd,+ action = \x -> handleParse x,+ parser = Parse <$> term+ }++handleParse :: Member (Output Message) r => REPLExpr 'CParse -> Sem r ()+handleParse (Parse t) = info (text (show t))++------------------------------------------------------------+-- :pretty++prettyCmd :: REPLCommand 'CPretty+prettyCmd =+ REPLCommand+ { name = "pretty",+ helpcmd = ":pretty <expr>",+ shortHelp = "Pretty-print a term",+ category = Dev,+ cmdtype = ColonCmd,+ action = \x -> handlePretty x,+ parser = Pretty <$> term+ }++handlePretty :: Members '[LFresh, Output Message] r => REPLExpr 'CPretty -> Sem r ()+handlePretty (Pretty t) = info $ pretty' t++------------------------------------------------------------+-- :reload++reloadCmd :: REPLCommand 'CReload+reloadCmd =+ REPLCommand+ { name = "reload",+ helpcmd = ":reload",+ shortHelp = "Reloads the most recently loaded file",+ category = User,+ cmdtype = ColonCmd,+ action = \x -> handleReload x,+ parser = return Reload+ }++handleReload ::+ Members (Error DiscoError ': State TopInfo ': Output Message ': Embed IO ': EvalEffects) r =>+ REPLExpr 'CReload ->+ Sem r ()+handleReload Reload = do+ file <- use lastFile+ case file of+ Nothing -> info "No file to reload."+ Just f -> void (handleLoad f)++------------------------------------------------------------+-- :defn++showDefnCmd :: REPLCommand 'CShowDefn+showDefnCmd =+ REPLCommand+ { name = "defn",+ helpcmd = ":defn <var>",+ shortHelp = "Show a variable's definition",+ category = User,+ cmdtype = ColonCmd,+ action = \x -> inputToState @TopInfo . handleShowDefn $ x,+ parser = ShowDefn <$> (sc *> ident)+ }++handleShowDefn ::+ Members '[Input TopInfo, LFresh, Output Message] r =>+ REPLExpr 'CShowDefn ->+ Sem r ()+handleShowDefn (ShowDefn x) = do+ let name2s = name2String x+ defns <- inputs @TopInfo (view (replModInfo . miTermdefs))+ tyDefns <- inputs @TopInfo (view (replModInfo . miTydefs))++ let xdefs = Ctx.lookupAll' (coerce x) defns+ mtydef = M.lookup name2s tyDefns++ info $ do+ let ds = map (pretty' . snd) xdefs ++ maybe [] (pure . pretty' . (name2s,)) mtydef+ case ds of+ [] -> text "No definition for" <+> pretty' x+ _ -> vcat ds++------------------------------------------------------------+-- :test++testPropCmd :: REPLCommand 'CTestProp+testPropCmd =+ REPLCommand+ { name = "test",+ helpcmd = ":test <property>",+ shortHelp = "Test a property using random examples",+ category = User,+ cmdtype = ColonCmd,+ action = \x -> handleTest x,+ parser = TestProp <$> term+ }++handleTest ::+ Members (Error DiscoError ': State TopInfo ': Output Message ': EvalEffects) r =>+ REPLExpr 'CTestProp ->+ Sem r ()+handleTest (TestProp t) = do+ at <- inputToState . typecheckTop $ checkProperty t+ tydefs <- use @TopInfo (replModInfo . to allTydefs)+ inputToState . inputTopEnv $ do+ r <- runTest 100 at -- XXX make configurable+ info $ runInputConst tydefs . runReader initPA $ nest 2 $ "-" <+> prettyTestResult at r++------------------------------------------------------------+-- :type++typeCheckCmd :: REPLCommand 'CTypeCheck+typeCheckCmd =+ REPLCommand+ { name = "type",+ helpcmd = ":type <term>",+ shortHelp = "Typecheck a term",+ category = Dev,+ cmdtype = ColonCmd,+ action = \x -> inputToState @TopInfo . handleTypeCheck $ x,+ parser = parseTypeCheck+ }++handleTypeCheck ::+ Members '[Error DiscoError, Input TopInfo, LFresh, Output Message] r =>+ REPLExpr 'CTypeCheck ->+ Sem r ()+handleTypeCheck (TypeCheck t) = do+ (_, sig) <- typecheckTop $ inferTop t+ info $ pretty' t <+> text ":" <+> pretty' sig++parseTypeCheck :: Parser (REPLExpr 'CTypeCheck)+parseTypeCheck =+ TypeCheck+ <$> ( (try term <?> "expression")+ <|> (parseNakedOp <?> "operator")+ )++-- In a :type or :doc command, allow naked operators, as in :type + ,+-- even though + by itself is not a syntactically valid term.+-- However, this seems like it may be a common thing for a student to+-- ask and there is no reason we can't have this as a special case.+parseNakedOp :: Parser Term+parseNakedOp = TPrim <$> parseNakedOpPrim++parseNakedOpPrim :: Parser Prim+parseNakedOpPrim = sc *> choice (map mkOpParser (concat opTable))+ where+ mkOpParser :: OpInfo -> Parser Prim+ mkOpParser (OpInfo (UOpF _ op) syns _) = choice (map ((PrimUOp op <$) . reservedOp) syns)+ mkOpParser (OpInfo (BOpF _ op) syns _) = choice (map ((PrimBOp op <$) . reservedOp) syns)
+ src/Disco/Interpret/CESK.hs view
@@ -0,0 +1,790 @@+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -fmax-pmcheck-models=200 #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Interpret.CESK+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- CESK machine interpreter for Disco.+-----------------------------------------------------------------------------++module Disco.Interpret.CESK+ ( CESK,+ runCESK,+ step,+ eval,+ runTest,+ )+where++import Text.Show.Pretty (ppShow)++import Unbound.Generics.LocallyNameless (Bind, Name)++import Algebra.Graph+import qualified Algebra.Graph.AdjacencyMap as AdjMap+import Control.Arrow ((***), (>>>))+import Control.Monad ((>=>))+import Data.Bifunctor (first, second)+import Data.List (find)+import qualified Data.Map as M+import Data.Maybe (isJust)+import Data.Ratio+import Disco.AST.Core+import Disco.AST.Generic (Ellipsis (..), Side (..),+ selectSide)+import Disco.AST.Typed (AProperty)+import Disco.Compile+import Disco.Context as Ctx+import Disco.Enumerate+import Disco.Error+import Disco.Names+import Disco.Property+import Disco.Types hiding (V)+import Disco.Value+import Math.Combinatorics.Exact.Binomial (choose)+import Math.Combinatorics.Exact.Factorial (factorial)+import Math.NumberTheory.Primes (factorise, unPrime)+import Math.NumberTheory.Primes.Testing (isPrime)+import Math.OEIS (catalogNums,+ extendSequence,+ lookupSequence)++import Disco.Effects.Fresh+import Disco.Effects.Input+import Disco.Effects.Random+import Polysemy+import Polysemy.Error+import Polysemy.State++------------------------------------------------------------+-- Utilities+------------------------------------------------------------++------------------------------------------------------------+-- Frames and continuations+------------------------------------------------------------++-- The CESK machine carries a current continuation explaining what to+-- do with the value of the currently focused expression, once it has+-- been fully evaluated.++-- | A continuation is just a stack of frames.+type Cont = [Frame]++-- | A frame represents a single step of the context, explaining what+-- to do with a value in that context (ultimately transforming it+-- into another value, which may in turn be handed to the next frame+-- in the continuation stack, and so on).+--+-- As an invariant, any 'Frame' containing unevaluated 'Core'+-- expressions must also carry an 'Env' in which to evaluate them.+data Frame+ = -- | Inject the value into a sum type.+ FInj Side+ | -- | Do a case analysis on the value.+ FCase Env (Bind (Name Core) Core) (Bind (Name Core) Core)+ | -- | Evaluate the right-hand value of a pair once we have finished+ -- evaluating the left-hand side.+ FPairR Env Core+ | -- | Put the value into the right-hand side of a pair together with+ -- this previously evaluated left-hand side.+ FPairL Value+ | -- | Project one or the other side of a pair.+ FProj Side+ | -- | Evaluate the argument of an application once we have finished+ -- evaluating the function.+ FArg Env Core+ | -- | Apply an evaluated function to this already-evaluated argument.+ FArgV Value+ | -- | Apply a previously evaluated function to the value.+ FApp Value+ | -- | Force evaluation of the contents of a memory cell.+ FForce+ | -- | Update the contents of a memory cell with its evaluation.+ FUpdate Int+ | -- | Record the results of a test.+ FTest TestVars Env+ deriving (Show)++------------------------------------------------------------+-- The CESK machine+------------------------------------------------------------++-- | The CESK machine has two basic kinds of states.+data CESK+ = -- | The 'In' constructor represents the state when we are recursing+ -- "into" a term. There is a currently focused expression which+ -- is to be evaluated in the given context. Generally, evaluation+ -- proceeds by pattern-matching on the focused expression and+ -- either immediately turning it into a value (if it is simple),+ -- or focusing on a subexpression and pushing a new frame on the+ -- continuation stack indicating how to continue evaluating the+ -- whole expression once finished with the subexpression.+ In Core Env Cont+ | -- | The 'Out' constructor represents the state when we have+ -- completed evaluating an expression and are now on our way back+ -- "out" of the recursion. Generally, evaluation proceeds by+ -- pattern-matching on the top frame of the continuation stack+ -- (and sometimes on the value as well), to see what is to be done+ -- with the value.+ Out Value Cont+ | -- | There is also an 'Up' constructor representing an exception+ -- that is propagating up the continuation stack. Disco does+ -- not have user-level exceptions or try/catch blocks etc., but+ -- exceptions may be caught by test frames and turned into a+ -- test result rather than crashing the entire computation.+ Up EvalError Cont+ deriving (Show)++-- | Is the CESK machine in a final state?+isFinal :: CESK -> Maybe (Either EvalError Value)+isFinal (Up e []) = Just (Left e)+isFinal (Out v []) = Just (Right v)+isFinal _ = Nothing++-- | Run a CESK machine to completion.+runCESK :: Members '[Fresh, Random, State Mem] r => CESK -> Sem r (Either EvalError Value)+runCESK cesk = case isFinal cesk of+ Just res -> return res+ Nothing -> step cesk >>= runCESK++(!!!) :: (Show a, Show b) => Ctx a b -> QName a -> b+ctx !!! x = case Ctx.lookup' x ctx of+ Nothing -> error $ "variable " ++ show x ++ " not found in environment\n"+ ++ ppShow (Ctx.keysSet ctx)+ Just v -> v++-- | Advance the CESK machine by one step.+step :: Members '[Fresh, Random, State Mem] r => CESK -> Sem r CESK+step cesk = case cesk of+ (In (CVar x) e k) -> return $ Out (e !!! x) k+ (In (CNum d r) _ k) -> return $ Out (VNum d r) k+ (In (CConst OMatchErr) _ k) -> return $ Up NonExhaustive k+ (In (CConst OEmptyGraph) _ k) -> return $ Out (VGraph empty) k+ (In (CConst op) _ k) -> return $ Out (VConst op) k+ (In (CInj s c) e k) -> return $ In c e (FInj s : k)+ (In (CCase c b1 b2) e k) -> return $ In c e (FCase e b1 b2 : k)+ (In CUnit _ k) -> return $ Out VUnit k+ (In (CPair c1 c2) e k) -> return $ In c1 e (FPairR e c2 : k)+ (In (CProj s c) e k) -> return $ In c e (FProj s : k)+ (In (CAbs b) e k) -> do+ (xs, body) <- unbind b+ return $ Out (VClo e xs body) k+ (In (CApp c1 c2) e k) -> return $ In c1 e (FArg e c2 : k)+ (In (CType ty) _ k) -> return $ Out (VType ty) k+ (In (CDelay b) e k) -> do+ (xs, cs) <- unbind b+ locs <- allocateRec e (zip (map localName xs) cs)+ return $ Out (foldr (VPair . VRef) VUnit locs) k+ (In (CForce c) e k) -> return $ In c e (FForce : k)+ (In (CTest vars c) e k) -> return $ In c e (FTest (TestVars vars) e : k)++ (Out v (FInj s : k)) -> return $ Out (VInj s v) k+ (Out (VInj L v) (FCase e b1 _ : k)) -> do+ (x, c1) <- unbind b1+ return $ In c1 (Ctx.insert (localName x) v e) k+ (Out (VInj R v) (FCase e _ b2 : k)) -> do+ (x, c2) <- unbind b2+ return $ In c2 (Ctx.insert (localName x) v e) k+ (Out v1 (FPairR e c2 : k)) -> return $ In c2 e (FPairL v1 : k)+ (Out v2 (FPairL v1 : k)) -> return $ Out (VPair v1 v2) k+ (Out (VPair v1 v2) (FProj s : k)) -> return $ Out (selectSide s v1 v2) k+ (Out v (FArg e c2 : k)) -> return $ In c2 e (FApp v : k)+ (Out v2 (FApp (VClo e [x] b) : k)) -> return $ In b (Ctx.insert (localName x) v2 e) k+ (Out v2 (FApp (VClo e (x : xs) b) : k)) -> return $ Out (VClo (Ctx.insert (localName x) v2 e) xs b) k+ (Out v2 (FApp (VConst op) : k)) -> appConst k op v2+ (Out v2 (FApp (VFun f) : k)) -> return $ Out (f v2) k+ -- Annoying to repeat this code, not sure of a better way.+ -- The usual evaluation order (function then argument) doesn't work when+ -- we're applying a test function to randomly generated values.+ (Out (VClo e [x] b) (FArgV v : k)) -> return $ In b (Ctx.insert (localName x) v e) k+ (Out (VClo e (x : xs) b) (FArgV v : k)) -> return $ Out (VClo (Ctx.insert (localName x) v e) xs b) k+ (Out (VConst op) (FArgV v : k)) -> appConst k op v+ (Out (VFun f) (FArgV v : k)) -> return $ Out (f v) k++ (Out (VRef n) (FForce : k)) -> do+ cell <- lkup n+ case cell of+ Nothing -> error $ "impossible: location " ++ show n ++ " not found in memory"+ Just (V v) -> return $ Out v k+ Just (E e t) -> do+ set n Blackhole+ return $ In t e (FUpdate n : k)+ Just Blackhole -> return $ Up InfiniteLoop k+ (Out v (FUpdate n : k)) -> do+ set n (V v)+ return $ Out v k++ (Up err (f@FTest{} : k)) ->+ return $ Out (VProp (VPDone (TestResult False (TestRuntimeError err) emptyTestEnv))) (f : k)+ (Up err (_ : ks)) -> return $ Up err ks++ (Out v (FTest vs e : k)) -> do+ let result = ensureProp v+ res = getTestEnv vs e+ case res of+ Left err -> return $ Up err k+ Right e' -> return $ Out (VProp $ extendPropEnv e' result) k++ _ -> error "Impossible! Bad CESK machine state"++------------------------------------------------------------+-- Interpreting constants+------------------------------------------------------------++arity2 :: (Value -> Value -> a) -> Value -> a+arity2 f (VPair x y) = f x y+arity2 _f _v = error "arity2 on a non-pair!"++arity3 :: (Value -> Value -> Value -> a) -> Value -> a+arity3 f (VPair x (VPair y z)) = f x y z+arity3 _f _v = error "arity3 on a non-triple!"++appConst+ :: Members '[Random, State Mem] r+ => Cont -> Op -> Value -> Sem r CESK+appConst k = \case+ --------------------------------------------------+ -- Basics++ OCrash -> up . Crash . vlist vchar+ OId -> out+ --------------------------------------------------+ -- Arithmetic++ OAdd -> numOp2 (+) >=> out+ ONeg -> numOp1 negate >=> out+ OSqrt -> numOp1 integerSqrt >=> out+ OFloor -> numOp1 ((% 1) . floor) >=> out+ OCeil -> numOp1 ((% 1) . ceiling) >=> out+ OAbs -> numOp1 abs >=> out+ OMul -> numOp2 (*) >=> out+ ODiv -> numOp2' divOp >>> outWithErr+ where+ divOp :: Member (Error EvalError) r => Rational -> Rational -> Sem r Value+ divOp _ 0 = throw DivByZero+ divOp m n = return $ ratv (m / n)+ OExp -> numOp2 (\m n -> m ^^ numerator n) >=> out+ OMod -> numOp2' modOp >>> outWithErr+ where+ modOp :: Member (Error EvalError) r => Rational -> Rational -> Sem r Value+ modOp m n+ | n == 0 = throw DivByZero+ | otherwise = return $ intv (numerator m `mod` numerator n)+ ODivides -> numOp2' (\m n -> return (enumv $ divides m n)) >=> out+ where+ divides 0 0 = True+ divides 0 _ = False+ divides x y = denominator (y / x) == 1++ --------------------------------------------------+ -- Number theory++ OIsPrime -> intOp1 (enumv . isPrime) >=> out+ OFactor -> intOp1' primFactor >>> outWithErr+ where+ -- Semantics of the @$factor@ prim: turn a natural number into its+ -- bag of prime factors. Crash if called on 0, which does not have+ -- a prime factorization.+ primFactor :: Member (Error EvalError) r => Integer -> Sem r Value+ primFactor 0 = throw (Crash "0 has no prime factorization!")+ primFactor n = return . VBag $ map ((intv . unPrime) *** fromIntegral) (factorise n)+ OFrac -> numOp1' (return . primFrac) >=> out+ where+ -- Semantics of the @$frac@ prim: turn a rational number into a pair+ -- of its numerator and denominator.+ primFrac :: Rational -> Value+ primFrac r = VPair (intv (numerator r)) (intv (denominator r))++ --------------------------------------------------+ -- Combinatorics++ OMultinom -> arity2 multinomOp >=> out+ where+ multinomOp :: Value -> Value -> Sem r Value+ multinomOp (vint -> n0) (vlist vint -> ks0) = return . intv $ multinomial n0 ks0+ where+ multinomial :: Integer -> [Integer] -> Integer+ multinomial _ [] = 1+ multinomial n (k' : ks)+ | k' > n = 0+ | otherwise = choose n k' * multinomial (n - k') ks+ OFact -> numOp1' factOp >>> outWithErr+ where+ factOp :: Member (Error EvalError) r => Rational -> Sem r Value+ factOp (numerator -> n)+ | n > fromIntegral (maxBound :: Int) = throw Overflow+ | otherwise = return . intv $ factorial (fromIntegral n)+ OEnum -> out . enumOp+ where+ enumOp :: Value -> Value+ enumOp (VType ty) = listv id (enumerateType ty)+ enumOp v = error $ "Impossible! enumOp on non-type " ++ show v+ OCount -> out . countOp+ where+ countOp :: Value -> Value+ countOp (VType ty) = case countType ty of+ Just num -> VInj R (intv num)+ Nothing -> VNil+ countOp v = error $ "Impossible! countOp on non-type " ++ show v++ --------------------------------------------------+ -- Sequences++ OUntil -> arity2 $ \v1 -> out . ellipsis (Until v1)+ OLookupSeq -> out . oeisLookup+ OExtendSeq -> out . oeisExtend+ --------------------------------------------------+ -- Comparison++ OEq -> arity2 $ \v1 v2 -> out $ enumv (valEq v1 v2)+ OLt -> arity2 $ \v1 v2 -> out $ enumv (valLt v1 v2)+ --------------------------------------------------+ -- Container operations++ OSize -> withBag OSize $ out . intv . sum . map snd+ OPower -> withBag OPower $ out . VBag . sortNCount . map (first VBag) . choices+ where+ choices :: [(Value, Integer)] -> [([(Value, Integer)], Integer)]+ choices [] = [([], 1)]+ choices ((x, n) : xs) = xs' ++ concatMap (\k' -> map (cons n (x, k')) xs') [1 .. n]+ where+ xs' = choices xs+ cons n (x, k') (zs, m) = ((x, k') : zs, choose n k' * m)+ OBagElem -> arity2 $ \x -> withBag OBagElem $+ out . enumv . isJust . find (valEq x) . map fst+ OListElem -> arity2 $ \x -> out . enumv . isJust . find (valEq x) . vlist id++ OEachSet -> arity2 $ \f -> withBag OEachSet $+ outWithErr . fmap (VBag . countValues) . mapM (evalApp f . (:[]) . fst)++ OEachBag -> arity2 $ \f -> withBag OEachBag $+ outWithErr . fmap (VBag . sortNCount) . mapM (\(x,n) -> (,n) <$> evalApp f [x])++ OFilterBag -> arity2 $ \f -> withBag OFilterBag $ \xs ->+ outWithErr $ do+ bs <- mapM (evalApp f . (:[]) . fst) xs+ return . VBag . map snd . Prelude.filter (isTrue . fst) $ zip bs xs+ where+ isTrue (VInj R VUnit) = True+ isTrue _ = False++ OMerge -> arity3 $ \f bxs bys ->+ case (bxs, bys) of+ (VBag xs, VBag ys) -> outWithErr (VBag <$> mergeM f xs ys)+ (VBag _, _) -> error $ "Impossible! OMerge on non-VBag " ++ show bys+ _ -> error $ "Impossible! OMerge on non-VBag " ++ show bxs++ OBagUnions -> withBag OBagUnions $ \cts ->+ out . VBag $ sortNCount [(x, m*n) | (VBag xs, n) <- cts, (x,m) <- xs]++ --------------------------------------------------+ -- Container conversions++ OBagToSet -> withBag OBagToSet $ out . VBag . (map . second) (const 1)+ OSetToList -> withBag OSetToList $ out . listv id . map fst+ OBagToList -> withBag OBagToList $ out . listv id . concatMap (uncurry (flip (replicate . fromIntegral)))+ OListToSet -> out . VBag . (map . fmap) (const 1) . countValues . vlist id+ OListToBag -> out . VBag . countValues . vlist id+ OBagToCounts -> withBag OBagToCounts $ out . VBag . map ((,1) . pairv id intv)+ -- Bag (a, N) -> Bag a+ -- Notionally this takes a set of pairs instead of a bag, but operationally we need to+ -- be prepared for a bag, because of the way literal bags desugar, e.g.+ --+ -- Disco> :desugar let x = 3 in ⟅ 'a' # (2 + x), 'b', 'b' ⟆+ -- (λx. bagFromCounts(bag(('a', 2 + x) :: ('b', 1) :: ('b', 1) :: [])))(3)++ OCountsToBag -> withBag OCountsToBag $+ out . VBag . sortNCount . map (second (uncurry (*)) . assoc . first (vpair id vint))+ where+ assoc ((a, b), c) = (a, (b, c))++ --------------------------------------------------+ -- Maps++ OMapToSet -> withMap OMapToSet $+ out . VBag . map (\(k',v) -> (VPair (fromSimpleValue k') v, 1)) . M.assocs++ OSetToMap -> withBag OSetToMap $+ out . VMap . M.fromList . map (convertAssoc . fst)+ where+ convertAssoc (VPair k' v) = (toSimpleValue k', v)+ convertAssoc v = error $ "Impossible! convertAssoc on non-VPair " ++ show v++ OInsert -> arity3 $ \k' v -> withMap OInsert $+ out . VMap . M.insert (toSimpleValue k') v++ OLookup -> arity2 $ \k' -> withMap OLookup $+ out . toMaybe . M.lookup (toSimpleValue k')+ where+ toMaybe = maybe (VInj L VUnit) (VInj R)++ --------------------------------------------------+ -- Graph operations++ OVertex -> out . VGraph . Vertex . toSimpleValue+ OOverlay -> arity2 $ withGraph2 OOverlay $ \g1 g2 ->+ out $ VGraph (Overlay g1 g2)+ OConnect -> arity2 $ withGraph2 OConnect $ \g1 g2 ->+ out $ VGraph (Connect g1 g2)+ OSummary -> withGraph OSummary $ out . graphSummary++ --------------------------------------------------+ -- Propositions++ OForall tys -> out . (\v -> VProp (VPSearch SMForall tys v emptyTestEnv ))+ OExists tys -> out . (\v -> VProp (VPSearch SMExists tys v emptyTestEnv ))+ OHolds -> testProperty Exhaustive >=> resultToBool >>> outWithErr+ ONotProp -> out . VProp . notProp . ensureProp+ OShouldEq ty -> arity2 $ \v1 v2 ->+ out $ VProp (VPDone (TestResult (valEq v1 v2) (TestEqual ty v1 v2) emptyTestEnv))++ c -> error $ "Unimplemented: appConst " ++ show c+ where+ outWithErr :: Sem (Error EvalError ': r) Value -> Sem r CESK+ outWithErr m = either (`Up` k) (`Out` k) <$> runError m+ out v = return $ Out v k+ up e = return $ Up e k++ withBag :: Op -> ([(Value,Integer)] -> Sem r a) -> Value -> Sem r a+ withBag op f = \case+ VBag xs -> f xs+ v -> error $ "Impossible! " ++ show op ++ " on non-VBag " ++ show v++ withMap :: Op -> (M.Map SimpleValue Value -> Sem r a) -> Value -> Sem r a+ withMap op f = \case+ VMap m -> f m+ v -> error $ "Impossible! " ++ show op ++ " on non-VMap " ++ show v++ withGraph :: Op -> (Graph SimpleValue -> Sem r a) -> Value -> Sem r a+ withGraph op f = \case+ VGraph g -> f g+ v -> error $ "Impossible! " ++ show op ++ " on non-VGraph " ++ show v++ withGraph2 :: Op -> (Graph SimpleValue -> Graph SimpleValue -> Sem r a) -> Value -> Value -> Sem r a+ withGraph2 op f v1 v2 = case (v1, v2) of+ (VGraph g1, VGraph g2) -> f g1 g2+ (_, VGraph _) -> error $ "Impossible! " ++ show op ++ " on non-VGraph " ++ show v1+ _ -> error $ "Impossible! " ++ show op ++ " on non-VGraph " ++ show v2++--------------------------------------------------+-- Arithmetic++intOp1 :: (Integer -> Value) -> Value -> Sem r Value+intOp1 f = intOp1' (return . f)++intOp1' :: (Integer -> Sem r Value) -> Value -> Sem r Value+intOp1' f = f . vint++numOp1 :: (Rational -> Rational) -> Value -> Sem r Value+numOp1 f = numOp1' $ return . ratv . f++numOp1' :: (Rational -> Sem r Value) -> Value -> Sem r Value+numOp1' f (VNum _ m) = f m+numOp1' _ v = error $ "Impossible! numOp1' on non-VNum " ++ show v++numOp2 :: (Rational -> Rational -> Rational) -> Value -> Sem r Value+numOp2 (#) = numOp2' $ \m n -> return (ratv (m # n))++numOp2' :: (Rational -> Rational -> Sem r Value) -> Value -> Sem r Value+numOp2' (#) =+ arity2 $ \v1 v2 -> case (v1, v2) of+ (VNum d1 n1, VNum d2 n2) -> do+ res <- n1 # n2+ case res of+ VNum _ r -> return $ VNum (d1 <> d2) r+ _ -> return res+ (VNum{}, _) -> error $ "Impossible! numOp2' on non-VNum " ++ show v2+ _ -> error $ "Impossible! numOp2' on non-VNum " ++ show v1++-- | Perform a square root operation. If the program typechecks,+-- then the argument and output will really be Natural.+integerSqrt :: Rational -> Rational+integerSqrt n = integerSqrt' (numerator n) % 1++-- | implementation of `integerSqrt'` taken from the Haskell wiki:+-- https://wiki.haskell.org/Generic_number_type#squareRoot+integerSqrt' :: Integer -> Integer+integerSqrt' 0 = 0+integerSqrt' 1 = 1+integerSqrt' n =+ let twopows = iterate (^! 2) 2+ (lowerRoot, lowerN) =+ last $ takeWhile ((n >=) . snd) $ zip (1 : twopows) twopows+ newtonStep x = div (x + div n x) 2+ iters = iterate newtonStep (integerSqrt' (div n lowerN) * lowerRoot)+ isRoot r = r ^! 2 <= n && n < (r + 1) ^! 2+ in head $ dropWhile (not . isRoot) iters++-- this operator is used for `integerSqrt'`+(^!) :: Num a => a -> Int -> a+(^!) x n = x ^ n++------------------------------------------------------------+-- Comparison+------------------------------------------------------------++valEq :: Value -> Value -> Bool+valEq v1 v2 = valCmp v1 v2 == EQ++valLt :: Value -> Value -> Bool+valLt v1 v2 = valCmp v1 v2 == LT++valCmp :: Value -> Value -> Ordering+valCmp (VNum _ r1) (VNum _ r2) = compare r1 r2+valCmp (VInj L _) (VInj R _) = LT+valCmp (VInj R _) (VInj L _) = GT+valCmp (VInj L v1) (VInj L v2) = valCmp v1 v2+valCmp (VInj R v1) (VInj R v2) = valCmp v1 v2+valCmp VUnit VUnit = EQ+valCmp (VPair v11 v12) (VPair v21 v22) = valCmp v11 v21 <> valCmp v12 v22+valCmp (VType ty1) (VType ty2) = compare ty1 ty2+valCmp (VBag cs1) (VBag cs2) = compareBags cs1 cs2+valCmp (VMap m1) (VMap m2) = compareMaps (M.assocs m1) (M.assocs m2)+valCmp (VGraph g1) (VGraph g2) = valCmp (graphSummary g1) (graphSummary g2)+valCmp v1 v2 = error $ "valCmp " ++ show v1 ++ " " ++ show v2++compareBags :: [(Value, Integer)] -> [(Value, Integer)] -> Ordering+compareBags [] [] = EQ+compareBags [] _ = LT+compareBags _ [] = GT+compareBags ((x, xn) : xs) ((y, yn) : ys)+ = valCmp x y <> compare xn yn <> compareBags xs ys++compareMaps :: [(SimpleValue, Value)] -> [(SimpleValue, Value)] -> Ordering+compareMaps [] [] = EQ+compareMaps [] _ = LT+compareMaps _ [] = GT+compareMaps ((k1, v1) : as1) ((k2, v2) : as2)+ = valCmp (fromSimpleValue k1) (fromSimpleValue k2) <> valCmp v1 v2 <> compareMaps as1 as2++------------------------------------------------------------+-- Polynomial sequences [a,b,c,d .. e]+------------------------------------------------------------++ellipsis :: Ellipsis Value -> Value -> Value+ellipsis (fmap vrat -> end) (vlist vrat -> rs) = listv ratv $ enumEllipsis rs end++enumEllipsis :: (Enum a, Num a, Ord a) => [a] -> Ellipsis a -> [a]+enumEllipsis [] _ = error "Impossible! Disco.Interpret.CESK.enumEllipsis []"+enumEllipsis [x] (Until y)+ | x <= y = [x .. y]+ | otherwise = [x, pred x .. y]+enumEllipsis xs (Until y)+ | d > 0 = takeWhile (<= y) nums+ | d < 0 = takeWhile (>= y) nums+ | otherwise = nums+ where+ d = constdiff xs+ nums = babbage xs++-- | Extend a sequence infinitely by interpolating it as a polynomial+-- sequence, via forward differences. Essentially the same+-- algorithm used by Babbage's famous Difference Engine.+babbage :: Num a => [a] -> [a]+babbage [] = []+babbage [x] = repeat x+babbage (x : xs) = scanl (+) x (babbage (diff (x : xs)))++-- | Compute the forward difference of the given sequence, that is,+-- differences of consecutive pairs of elements.+diff :: Num a => [a] -> [a]+diff xs = zipWith (-) (tail xs) xs++-- | Take forward differences until the result is constant, and return+-- the constant. The sign of the constant difference tells us the+-- limiting behavior of the sequence.+constdiff :: (Eq a, Num a) => [a] -> a+constdiff [] = error "Impossible! Disco.Interpret.Core.constdiff []"+constdiff (x : xs)+ | all (== x) xs = x+ | otherwise = constdiff (diff (x : xs))++------------------------------------------------------------+-- OEIS+------------------------------------------------------------++-- | Looks up a sequence of integers in OEIS.+-- Returns 'left()' if the sequence is unknown in OEIS,+-- otherwise 'right "https://oeis.org/<oeis_sequence_id>"'+oeisLookup :: Value -> Value+oeisLookup (vlist vint -> ns) = maybe VNil parseResult (lookupSequence ns)+ where+ parseResult r = VInj R (listv charv ("https://oeis.org/" ++ seqNum r))+ seqNum = getCatalogNum . catalogNums++ getCatalogNum [] = error "No catalog info"+ getCatalogNum (n : _) = n++-- | Extends a Disco integer list with data from a known OEIS+-- sequence. Returns a list of integers upon success, otherwise the+-- original list (unmodified).+oeisExtend :: Value -> Value+oeisExtend = listv intv . extendSequence . vlist vint++------------------------------------------------------------+-- Normalizing bags/sets+------------------------------------------------------------++-- | Given a list of disco values, sort and collate them into a list+-- pairing each unique value with its count. Used to+-- construct/normalize bags and sets. Prerequisite: the values must+-- be comparable.+countValues :: [Value] -> [(Value, Integer)]+countValues = sortNCount . map (,1)++-- | Normalize a list of values where each value is paired with a+-- count, but there could be duplicate values. This function uses+-- merge sort to sort the values, adding the counts of multiple+-- instances of the same value. Prerequisite: the values must be+-- comparable.+sortNCount :: [(Value, Integer)] -> [(Value, Integer)]+sortNCount [] = []+sortNCount [x] = [x]+sortNCount xs = merge (+) (sortNCount firstHalf) (sortNCount secondHalf)+ where+ (firstHalf, secondHalf) = splitAt (length xs `div` 2) xs++-- | Generic function for merging two sorted, count-annotated lists of+-- type @[(a,Integer)]@ a la merge sort, using the given comparison+-- function, and using the provided count combining function to+-- decide what count to assign to each element of the output. For+-- example, @(+)@ corresponds to bag union; @min@ corresponds to+-- intersection; and so on.+merge ::+ (Integer -> Integer -> Integer) ->+ [(Value, Integer)] ->+ [(Value, Integer)] ->+ [(Value, Integer)]+merge g = go+ where+ go [] [] = []+ go [] ((y, n) : ys) = mergeCons y 0 n (go [] ys)+ go ((x, n) : xs) [] = mergeCons x n 0 (go xs [])+ go ((x, n1) : xs) ((y, n2) : ys) = case valCmp x y of+ LT -> mergeCons x n1 0 (go xs ((y, n2) : ys))+ EQ -> mergeCons x n1 n2 (go xs ys)+ GT -> mergeCons y 0 n2 (go ((x, n1) : xs) ys)++ mergeCons a m1 m2 zs = case g m1 m2 of+ 0 -> zs+ n -> (a, n) : zs++mergeM ::+ Members '[Random, Error EvalError, State Mem] r =>+ Value ->+ [(Value, Integer)] ->+ [(Value, Integer)] ->+ Sem r [(Value, Integer)]+mergeM g = go+ where+ go [] [] = return []+ go [] ((y, n) : ys) = mergeCons y 0 n =<< go [] ys+ go ((x, n) : xs) [] = mergeCons x n 0 =<< go xs []+ go ((x, n1) : xs) ((y, n2) : ys) = case valCmp x y of+ LT -> mergeCons x n1 0 =<< go xs ((y, n2) : ys)+ EQ -> mergeCons x n1 n2 =<< go xs ys+ GT -> mergeCons y 0 n2 =<< go ((x, n1) : xs) ys++ mergeCons a m1 m2 zs = do+ nm <- evalApp g [VPair (intv m1) (intv m2)]+ return $ case nm of+ VNum _ 0 -> zs+ VNum _ n -> (a, numerator n) : zs+ v -> error $ "Impossible! merge function in mergeM returned non-VNum " ++ show v++------------------------------------------------------------+-- Graphs+------------------------------------------------------------++graphSummary :: Graph SimpleValue -> Value+graphSummary = toDiscoAdjMap . reifyGraph+ where+ reifyGraph :: Graph SimpleValue -> [(SimpleValue, [SimpleValue])]+ reifyGraph =+ AdjMap.adjacencyList . foldg AdjMap.empty AdjMap.vertex AdjMap.overlay AdjMap.connect++ toDiscoAdjMap :: [(SimpleValue, [SimpleValue])] -> Value+ toDiscoAdjMap =+ VMap . M.fromList . map (second (VBag . countValues . map fromSimpleValue))++------------------------------------------------------------+-- Propositions / tests+------------------------------------------------------------++resultToBool :: Member (Error EvalError) r => TestResult -> Sem r Value+resultToBool (TestResult _ (TestRuntimeError e) _) = throw e+resultToBool (TestResult b _ _) = return $ enumv b++notProp :: ValProp -> ValProp+notProp (VPDone r) = VPDone (invertPropResult r)+notProp (VPSearch sm tys p e) = VPSearch (invertMotive sm) tys p e++-- | Convert a @Value@ to a @ValProp@, embedding booleans if necessary.+ensureProp :: Value -> ValProp+ensureProp (VProp p) = p+ensureProp (VInj L _) = VPDone (TestResult False TestBool emptyTestEnv)+ensureProp (VInj R _) = VPDone (TestResult True TestBool emptyTestEnv)+ensureProp _ = error "ensureProp: non-prop value"++testProperty+ :: Members '[Random, State Mem] r+ => SearchType -> Value -> Sem r TestResult+testProperty initialSt = checkProp . ensureProp+ where+ checkProp+ :: Members '[Random, State Mem] r+ => ValProp -> Sem r TestResult+ checkProp (VPDone r) = return r+ checkProp (VPSearch sm tys f e) =+ extendResultEnv e <$> (generateSamples initialSt vals >>= go)+ where+ vals = enumTypes tys+ (SearchMotive (whenFound, wantsSuccess)) = sm++ go+ :: Members '[Random, State Mem] r+ => ([[Value]], SearchType) -> Sem r TestResult+ go ([], st) = return $ TestResult (not whenFound) (TestNotFound st) emptyTestEnv+ go (x:xs, st) = do+ mprop <- runError (ensureProp <$> evalApp f x)+ case mprop of+ Left err -> return $ TestResult False (TestRuntimeError err) emptyTestEnv+ Right (VPDone r) -> continue st xs r+ Right prop -> checkProp prop >>= continue st xs++ continue+ :: Members '[Random, State Mem] r+ => SearchType -> [[Value]] -> TestResult -> Sem r TestResult+ continue st xs r@(TestResult _ _ e')+ | testIsError r = return r+ | testIsOk r == wantsSuccess =+ return $ TestResult whenFound (TestFound r) e'+ | otherwise = go (xs, st)++evalApp+ :: Members '[Random, Error EvalError, State Mem] r+ => Value -> [Value] -> Sem r Value+evalApp f xs =+ runFresh (runCESK (Out f (map FArgV xs))) >>= either throw return++runTest+ :: Members '[Random, Error EvalError, Input Env, State Mem] r+ => Int -> AProperty -> Sem r TestResult+runTest n p = testProperty (Randomized n' n') =<< eval (compileProperty p)+ where+ n' = fromIntegral (n `div` 2)++------------------------------------------------------------+-- Top-level evaluation+------------------------------------------------------------++eval :: Members '[Random, Error EvalError, Input Env, State Mem] r => Core -> Sem r Value+eval c = do+ e <- input @Env+ runFresh (runCESK (In c e [])) >>= either throw return
+ src/Disco/Messages.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Messages+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Message logging framework (e.g. for errors, warnings, etc.) for+-- disco.+--+-----------------------------------------------------------------------------++module Disco.Messages where++import Control.Lens+import Control.Monad (when)+import Polysemy+import Polysemy.Output++import Disco.Pretty (Doc, Pretty, pretty', renderDoc')++data MessageType+ = Info+ | Warning+ | ErrMsg+ | Debug+ deriving (Show, Read, Eq, Ord, Enum, Bounded)++data Message = Message {_messageType :: MessageType, _message :: Doc}+ deriving (Show)++makeLenses ''Message++handleMsg :: Member (Embed IO) r => (Message -> Bool) -> Message -> Sem r ()+handleMsg p m = when (p m) $ printMsg m++printMsg :: Member (Embed IO) r => Message -> Sem r ()+printMsg (Message _ m) = embed $ putStrLn (renderDoc' m)++msg :: Member (Output Message) r => MessageType -> Sem r Doc -> Sem r ()+msg typ m = m >>= output . Message typ++info :: Member (Output Message) r => Sem r Doc -> Sem r ()+info = msg Info++infoPretty :: (Member (Output Message) r, Pretty t) => t -> Sem r ()+infoPretty = info . pretty'++warn :: Member (Output Message) r => Sem r Doc -> Sem r ()+warn = msg Warning++debug :: Member (Output Message) r => Sem r Doc -> Sem r ()+debug = msg Debug++debugPretty :: (Member (Output Message) r, Pretty t) => t -> Sem r ()+debugPretty = debug . pretty'++err :: Member (Output Message) r => Sem r Doc -> Sem r ()+err = msg ErrMsg
+ src/Disco/Module.hs view
@@ -0,0 +1,196 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Module+-- Copyright : (c) 2019 disco team (see LICENSE)+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- The 'ModuleInfo' record representing a disco module, and functions+-- to resolve the location of a module on disk.+-----------------------------------------------------------------------------++module Disco.Module where++import Data.Data (Data)+import GHC.Generics (Generic)++import Control.Lens (Getting, foldOf,+ makeLenses, view)+import Control.Monad (filterM, foldM)+import Control.Monad.IO.Class (MonadIO (..))+import Data.Bifunctor (first)+import Data.Map (Map)+import qualified Data.Map as M+import Data.Maybe (listToMaybe)+import qualified Data.Set as S+import System.Directory (doesFileExist)+import System.FilePath (replaceExtension,+ (</>))++import Unbound.Generics.LocallyNameless (Alpha, Bind, Name,+ Subst, bind)+import Unbound.Generics.LocallyNameless.Unsafe (unsafeUnbind)++import Polysemy+import Polysemy.Error++import Disco.AST.Surface+import Disco.AST.Typed+import Disco.Context+import Disco.Extensions+import Disco.Names+import Disco.Pretty hiding ((<>))+import Disco.Typecheck.Erase (erase, erasePattern)+import Disco.Typecheck.Util (TCError (..), TyCtx)+import Disco.Types++import Paths_disco++------------------------------------------------------------+-- ModuleInfo and related types+------------------------------------------------------------++-- | When loading a module, we could be loading it from code entered+-- at the REPL, or from a standalone file. The two modes have+-- slightly different behavior.+data LoadingMode = REPL | Standalone++-- | A definition consists of a name being defined, the types of any+-- pattern arguments (each clause must have the same number of+-- patterns), the type of the body of each clause, and a list of+-- clauses. For example,+--+-- @+-- f x (0,z) = 3*x + z > 5+-- f x (y,z) = z == 9+-- @+--+-- might look like @Defn f [Z, Z*Z] B [clause 1 ..., clause 2 ...]@+data Defn = Defn (Name ATerm) [Type] Type [Clause]+ deriving (Show, Generic, Alpha, Data, Subst Type)++instance Pretty Defn where+ pretty (Defn x patTys ty clauses) = vcat $+ prettyTyDecl x (foldr (:->:) ty patTys)+ :+ map (pretty . (x,) . eraseClause) clauses++-- | A clause in a definition consists of a list of patterns (the LHS+-- of the =) and a term (the RHS). For example, given the concrete+-- syntax @f n (x,y) = n*x + y@, the corresponding 'Clause' would be+-- something like @[n, (x,y)] (n*x + y)@.+type Clause = Bind [APattern] ATerm++eraseClause :: Clause -> Bind [Pattern] Term+eraseClause b = bind (map erasePattern ps) (erase t)+ where (ps, t) = unsafeUnbind b++-- | Type checking a module yields a value of type ModuleInfo which contains+-- mapping from terms to their relavent documenation, a mapping from terms to+-- properties, and a mapping from terms to their types.+data ModuleInfo = ModuleInfo+ { _miName :: ModuleName+ , _miImports :: Map ModuleName ModuleInfo+ , _miDocs :: Ctx Term Docs+ , _miProps :: Ctx ATerm [AProperty]+ , _miTys :: TyCtx+ , _miTydefs :: TyDefCtx+ , _miTermdefs :: Ctx ATerm Defn+ , _miTerms :: [(ATerm, PolyType)]+ , _miExts :: ExtSet+ }+ deriving (Show)++makeLenses ''ModuleInfo++-- | Get something from a module and its direct imports.+withImports :: Monoid a => Getting a ModuleInfo a -> ModuleInfo -> a+withImports l = view l <> foldOf (miImports . traverse . l)++-- | Get the types of all names bound in a module and its direct imports.+allTys :: ModuleInfo -> TyCtx+allTys = withImports miTys++-- | Get all type definitions from a module and its direct imports.+allTydefs :: ModuleInfo -> TyDefCtx+allTydefs = withImports miTydefs++-- | The empty module info record.+emptyModuleInfo :: ModuleInfo+emptyModuleInfo = ModuleInfo REPLModule M.empty emptyCtx emptyCtx emptyCtx M.empty emptyCtx [] S.empty++-- | Merges a list of ModuleInfos into one ModuleInfo. Two ModuleInfos+-- are merged by joining their doc, type, type definition, and term+-- contexts. The property context of the new module is the one+-- obtained from the second module. The name of the new module is+-- taken from the first. Definitions from later modules override+-- earlier ones. Note that this function should really only be used+-- for the special top-level REPL module.+combineModuleInfo :: Member (Error TCError) r => [ModuleInfo] -> Sem r ModuleInfo+combineModuleInfo = foldM combineMods emptyModuleInfo+ where+ combineMods :: Member (Error TCError) r => ModuleInfo -> ModuleInfo -> Sem r ModuleInfo+ combineMods+ (ModuleInfo n1 is1 d1 _ ty1 tyd1 tm1 tms1 es1)+ (ModuleInfo _ is2 d2 p2 ty2 tyd2 tm2 tms2 es2) =+ return $+ ModuleInfo+ n1+ (is1 <> is2)+ (d2 <> d1)+ p2+ (ty2 <> ty1)+ (tyd2 <> tyd1)+ (tm2 <> tm1)+ (tms1 <> tms2)+ (es1 <> es2)++------------------------------------------------------------+-- Module resolution+------------------------------------------------------------++-- | A data type indicating where we should look for Disco modules to+-- be loaded.+data Resolver+ = -- | Load only from the stdlib (standard lib modules)+ FromStdlib+ | -- | Load only from a specific directory (:load)+ FromDir FilePath+ | -- | Load from current working dir or stdlib (import at REPL)+ FromCwdOrStdlib+ | -- | Load from specific dir or stdlib (import in file)+ FromDirOrStdlib FilePath++-- | Add the possibility of loading imports from the stdlib. For+-- example, this is what we want to do after a user loads a specific+-- file using `:load` (for which we will NOT look in the stdlib),+-- but then we need to recursively load modules which it imports+-- (which may either be in the stdlib, or the same directory as the+-- `:load`ed module).+withStdlib :: Resolver -> Resolver+withStdlib (FromDir fp) = FromDirOrStdlib fp+withStdlib r = r++-- | Given a module resolution mode and a raw module name, relavent+-- directories are searched for the file containing the provided+-- module name. Returns Nothing if no module with the given name+-- could be found.+resolveModule :: Member (Embed IO) r => Resolver -> String -> Sem r (Maybe (FilePath, ModuleProvenance))+resolveModule resolver modname = do+ datadir <- liftIO getDataDir+ let searchPath =+ case resolver of+ FromStdlib -> [(datadir, Stdlib)]+ FromDir dir -> [(dir, Dir dir)]+ FromCwdOrStdlib -> [(datadir, Stdlib), (".", Dir ".")]+ FromDirOrStdlib dir -> [(datadir, Stdlib), (dir, Dir dir)]+ let fps = map (first (</> replaceExtension modname "disco")) searchPath+ fexists <- liftIO $ filterM (doesFileExist . fst) fps+ return $ listToMaybe fexists
+ src/Disco/Names.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Names+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- Names for modules and identifiers.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Names+ ( -- * Modules and their provenance+ ModuleProvenance(..), ModuleName(..)+ -- * Names and their provenance+ , NameProvenance(..), QName(..), isFree, localName, (.-)+ -- * Name-related utilities+ , fvQ, substQ, substsQ+ ) where++import Control.Lens (Traversal', filtered)+import Data.Data (Data)+import Data.Data.Lens (template)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Prelude hiding ((<>))+import System.FilePath (dropExtension)+import Unbound.Generics.LocallyNameless++import Disco.Pretty+import Disco.Types++------------------------------------------------------------+-- Modules+------------------------------------------------------------++-- | Where did a module come from?+data ModuleProvenance+ = Dir FilePath -- ^ From a particular directory (relative to cwd)+ | Stdlib -- ^ From the standard library+ deriving (Eq, Ord, Show, Generic, Data, Alpha, Subst Type)++-- | The name of a module.+data ModuleName+ = REPLModule -- ^ The special top-level "module" consisting of+ -- what has been entered at the REPL.+ | Named ModuleProvenance String+ -- ^ A named module, with its name and provenance.+ deriving (Eq, Ord, Show, Generic, Data, Alpha, Subst Type)++------------------------------------------------------------+-- Names+------------------------------------------------------------++-- | Where did a name come from?+data NameProvenance+ = LocalName -- ^ The name is locally bound+ | QualifiedName ModuleName -- ^ The name is exported by the given module+ deriving (Eq, Ord, Show, Generic, Data, Alpha, Subst Type)++-- | A @QName@, or qualified name, is a 'Name' paired with its+-- 'NameProvenance'.+data QName a = QName { qnameProvenance :: NameProvenance, qname :: Name a }+ deriving (Eq, Ord, Show, Generic, Data, Alpha, Subst Type)++-- | Does this name correspond to a free variable?+isFree :: QName a -> Bool+isFree (QName (QualifiedName _) _) = True+isFree (QName LocalName n) = isFreeName n++-- | Create a locally bound qualified name.+localName :: Name a -> QName a+localName = QName LocalName++-- | Create a module-bound qualified name.+(.-) :: ModuleName -> Name a -> QName a+m .- x = QName (QualifiedName m) x++------------------------------------------------------------+-- Free variables and substitution+------------------------------------------------------------++-- | The @unbound-generics@ library gives us free variables for free.+-- But when dealing with typed and desugared ASTs, we want all the+-- free 'QName's instead of just 'Name's.+fvQ :: (Data t, Typeable e) => Traversal' t (QName e)+fvQ = template . filtered isFree++substQ :: Subst b a => QName b -> b -> a -> a+substQ = undefined++substsQ :: Subst b a => [(QName b, b)] -> a -> a+substsQ = undefined++------------------------------------------------------------+-- Pretty-printing+------------------------------------------------------------++instance Pretty ModuleName where+ pretty REPLModule = "REPL"+ pretty (Named (Dir _) s) = text (dropExtension s)+ pretty (Named Stdlib s) = text (dropExtension s)++instance Pretty (QName a) where+ pretty (QName LocalName x) = pretty x+ pretty (QName (QualifiedName mn) x) = pretty mn <> "." <> pretty x
+ src/Disco/Parser.hs view
@@ -0,0 +1,1048 @@+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Parser+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Parser to convert concrete Disco syntax into an (untyped, surface+-- language) AST.+--+-----------------------------------------------------------------------------++module Disco.Parser+ ( -- * Parser type and utilities+ DiscoParseError(..), Parser, runParser, withExts, indented, thenIndented++ -- * Lexer++ -- ** Basic lexemes+ , sc, lexeme, symbol, reservedOp+ , natural, reserved, reservedWords, ident++ -- ** Punctuation+ , parens, braces, angles, brackets+ , semi, comma, colon, dot, pipe+ , lambda++ -- * Disco parser++ -- ** Modules+ , wholeModule, parseModule, parseExtName, parseTopLevel, parseDecl+ , parseImport, parseModuleName++ -- ** Terms+ , term, parseTerm, parseTerm', parseExpr, parseAtom+ , parseContainer, parseEllipsis, parseContainerComp, parseQual+ , parseLet, parseTypeOp++ -- ** Case and patterns+ , parseCase, parseBranch, parseGuards, parseGuard+ , parsePattern, parseAtomicPattern++ -- ** Types+ , parseType, parseAtomicType+ , parsePolyTy+ )+ where++import Unbound.Generics.LocallyNameless (Name, bind, embed,+ fvAny, string2Name)+import Unbound.Generics.LocallyNameless.Unsafe (unsafeUnbind)++import Control.Monad.Combinators.Expr+import Text.Megaparsec hiding (runParser)+import qualified Text.Megaparsec as MP+import Text.Megaparsec.Char+import qualified Text.Megaparsec.Char.Lexer as L++import Control.Lens (makeLenses, toListOf,+ use, (%=), (%~), (&),+ (.=))+import Control.Monad.State+import Data.Char (isDigit)+import Data.Foldable (asum)+import Data.List (find, intercalate)+import qualified Data.Map as M+import Data.Maybe (fromMaybe)+import Data.Ratio+import Data.Set (Set)+import qualified Data.Set as S++import Disco.AST.Surface+import Disco.Extensions+import Disco.Module+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import Disco.Types++------------------------------------------------------------+-- Lexer++-- Some of the basic setup code for the parser taken from+-- https://markkarpov.com/megaparsec/parsing-simple-imperative-language.html++-- | Currently required indent level.+data IndentMode where+ NoIndent :: IndentMode -- ^ Don't require indent.+ ThenIndent :: IndentMode -- ^ Parse one token without+ -- indent, then switch to @Indent@.+ Indent :: IndentMode -- ^ Require everything to be indented at+ -- least one space.++-- | Extra custom state for the parser.+data ParserState = ParserState+ { _indentMode :: IndentMode -- ^ Currently required level of indentation.+ , _enabledExts :: Set Ext -- ^ Set of enabled language extensions+ -- (some of which may affect parsing).+ }++makeLenses ''ParserState++initParserState :: ParserState+initParserState = ParserState NoIndent S.empty++data DiscoParseError+ = ReservedVarName String+ deriving (Show, Eq, Ord)++instance ShowErrorComponent DiscoParseError where+ showErrorComponent (ReservedVarName x) = "keyword \"" ++ x ++ "\" cannot be used as a variable name"+ errorComponentLen (ReservedVarName x) = length x++-- | A parser is a megaparsec parser of strings, with an extra layer+-- of state to keep track of the current indentation level and+-- language extensions, and some custom error messages.+type Parser = StateT ParserState (MP.Parsec DiscoParseError String)++-- | Run a parser from the initial state.+runParser :: Parser a -> FilePath -> String -> Either (ParseErrorBundle String DiscoParseError) a+runParser = MP.runParser . flip evalStateT initParserState++-- | Run a parser under a specified 'IndentMode'.+withIndentMode :: IndentMode -> Parser a -> Parser a+withIndentMode m p = do+ indentMode .= m+ res <- p+ indentMode .= NoIndent+ return res++-- | @indented p@ is just like @p@, except that every token must not+-- start in the first column.+indented :: Parser a -> Parser a+indented = withIndentMode Indent++-- | @indented p@ is just like @p@, except that every token after the+-- first must not start in the first column.+thenIndented :: Parser a -> Parser a+thenIndented = withIndentMode ThenIndent++-- | @requireIndent p@ possibly requires @p@ to be indented, depending+-- on the current '_indentMode'. Used in the definition of+-- 'lexeme' and 'symbol'.+requireIndent :: Parser a -> Parser a+requireIndent p = do+ l <- use indentMode+ case l of+ ThenIndent -> do+ a <- p+ indentMode .= Indent+ return a+ Indent -> L.indentGuard sc GT pos1 >> p+ NoIndent -> p++-- | Locally set the enabled extensions within a subparser.+withExts :: Set Ext -> Parser a -> Parser a+withExts exts p = do+ oldExts <- use enabledExts+ enabledExts .= exts+ a <- p+ enabledExts .= oldExts+ return a++-- | Locally enable some additional extensions within a subparser.+withAdditionalExts :: Set Ext -> Parser a -> Parser a+withAdditionalExts exts p = do+ oldExts <- use enabledExts+ enabledExts %= S.union exts+ a <- p+ enabledExts .= oldExts+ return a++-- | Ensure that a specific extension is enabled, fail if not.+ensureEnabled :: Ext -> Parser ()+ensureEnabled e = do+ exts <- use enabledExts+ guard $ e `S.member` exts++-- | Generically consume whitespace, including comments.+sc :: Parser ()+sc = L.space space1 lineComment empty {- no block comments in disco -}+ where+ lineComment = L.skipLineComment "--"++-- | Parse a lexeme, that is, a parser followed by consuming+-- whitespace.+lexeme :: Parser a -> Parser a+lexeme p = requireIndent $ L.lexeme sc p++-- | Parse a given string as a lexeme.+symbol :: String -> Parser String+symbol s = requireIndent $ L.symbol sc s++-- | Parse a reserved operator.+reservedOp :: String -> Parser ()+reservedOp s = (lexeme . try) (string s *> notFollowedBy (oneOf opChar))++-- | Characters that can occur in an operator symbol.+opChar :: [Char]+opChar = "~!@#$%^&*-+=|<>?/\\."++parens, braces, angles, brackets, bagdelims, fbrack, cbrack :: Parser a -> Parser a+parens = between (symbol "(") (symbol ")")+braces = between (symbol "{") (symbol "}")+angles = between (symbol "<") (symbol ">")+brackets = between (symbol "[") (symbol "]")+bagdelims = between (symbol "⟅") (symbol "⟆")+fbrack = between (symbol "⌊") (symbol "⌋")+cbrack = between (symbol "⌈") (symbol "⌉")++semi, comma, colon, dot, pipe, hash :: Parser String+semi = symbol ";"+comma = symbol ","+colon = symbol ":"+dot = symbol "."+pipe = symbol "|"+hash = symbol "#"++-- | A literal ellipsis of two or more dots, @..@+ellipsis :: Parser String+ellipsis = label "ellipsis (..)" $ concat <$> ((:) <$> dot <*> some dot)++-- | The symbol that starts an anonymous function (either a backslash+-- or a Greek λ).+lambda :: Parser String+lambda = symbol "\\" <|> symbol "λ"++forall :: Parser ()+forall = () <$ symbol "∀" <|> reserved "forall"++exists :: Parser ()+exists = () <$ symbol "∃" <|> reserved "exists"++-- | Parse a natural number.+natural :: Parser Integer+natural = lexeme L.decimal <?> "natural number"++-- | Parse a nonnegative decimal of the form @xxx.yyyy[zzz]@, where+-- the @y@s and bracketed @z@s are both optional as long as the+-- other is present. (In other words, there must be something after+-- the period.) For example, this parser accepts all of the+-- following:+--+-- > 2.0+-- > 2.333+-- > 2.33[45]+-- > 2.[45]+--+-- The idea is that brackets surround an infinitely repeating+-- sequence of digits.+--+-- We used to accept @2.@ with no trailing digits, but no longer do.+-- See https://github.com/disco-lang/disco/issues/245 and Note+-- [Trailing period].+decimal :: Parser Rational+decimal = lexeme (readDecimal <$> some digit <* char '.'+ <*> fractionalPart+ )+ where+ digit = satisfy isDigit+ fractionalPart =+ -- either some digits optionally followed by bracketed digits...+ (,) <$> some digit <*> optional (brackets (some digit))+ -- ...or just bracketed digits.+ <|> ([],) <$> (Just <$> brackets (some digit))++ readDecimal a (b, mrep)+ = read a % 1 -- integer part++ -- next part is just b/10^n+ + (if null b then 0 else read b) % (10^length b)++ -- repeating part+ + readRep (length b) mrep++ readRep _ Nothing = 0+ readRep offset (Just rep) = read rep % (10^offset * (10^length rep - 1))+ -- If s = 0.[rep] then 10^(length rep) * s = rep.[rep], so+ -- 10^(length rep) * s - s = rep, so+ --+ -- s = rep/(10^(length rep) - 1).+ --+ -- We also have to divide by 10^(length b) to shift it over+ -- past any non-repeating prefix.++-- ~~~~ Note [Trailing period]+--+-- We used to accept numbers with nothing after the trailing period,+-- such as @2.@. However, this caused some problems with parsing:+--+-- - First, https://github.com/disco-lang/disco/issues/99 which we+-- solved by making sure there was not another period after the+-- trailing period.+-- - Next, https://github.com/disco-lang/disco/issues/245.+--+-- I first tried solving #245 by disallowing *any* operator character+-- after the trailing period, but then some tests in the test suite+-- started failing, where we had written things like @1./(10^5)@. The+-- problem is that when a period is followed by another operator+-- symbol, sometimes we might want them to be parsed as an operator+-- (as in @2.-4@, #245), and sometimes we might not (as in+-- @1./(10^5)@). So in the end it seems simpler and cleaner to+-- require at least a 0 digit after the period --- just like pretty+-- much every other programming language and just like standard+-- mathematical practice.++-- | Parse a reserved word.+reserved :: String -> Parser ()+reserved w = (lexeme . try) $ string w *> notFollowedBy alphaNumChar++-- | The list of all reserved words.+reservedWords :: [String]+reservedWords =+ [ "unit", "true", "false", "True", "False", "let", "in", "is"+ , "if", "when"+ , "otherwise", "and", "or", "mod", "choose", "implies"+ , "min", "max"+ , "union", "∪", "intersect", "∩", "subset", "⊆", "elem", "∈"+ , "enumerate", "count", "divides"+ , "Void", "Unit", "Bool", "Boolean", "Proposition", "Prop", "Char"+ , "Nat", "Natural", "Int", "Integer", "Frac", "Fractional", "Rational", "Fin"+ , "List", "Bag", "Set", "Graph", "Map"+ , "N", "Z", "F", "Q", "ℕ", "ℤ", "𝔽", "ℚ"+ , "∀", "forall", "∃", "exists", "type"+ , "import", "using"+ ]++-- | Parse an identifier, i.e. any non-reserved string beginning with+-- a given type of character and continuing with alphanumerics,+-- underscores, and apostrophes.+identifier :: Parser Char -> Parser String+identifier begin = (lexeme . try) (p >>= check) <?> "variable name"+ where+ p = (:) <$> begin <*> many identChar+ identChar = alphaNumChar <|> oneOf "_'"+ check x+ | x `elem` reservedWords = do+ -- back up to beginning of bad token to report correct position+ updateParserState (\s -> s { stateOffset = stateOffset s - length x })+ customFailure $ ReservedVarName x+ | otherwise = return x++-- | Parse an 'identifier' and turn it into a 'Name'.+ident :: Parser (Name Term)+ident = string2Name <$> identifier letterChar++------------------------------------------------------------+-- Parser++-- | Results from parsing a block of top-level things.+data TLResults = TLResults+ { _tlDecls :: [Decl]+ , _tlDocs :: [(Name Term, [DocThing])]+ , _tlTerms :: [Term]+ }++emptyTLResults :: TLResults+emptyTLResults = TLResults [] [] []++makeLenses ''TLResults++-- | Parse the entire input as a module (with leading whitespace and+-- no leftovers).+wholeModule :: LoadingMode -> Parser Module+wholeModule = between sc eof . parseModule++-- | Parse an entire module (a list of declarations ended by+-- semicolons). The 'LoadingMode' parameter tells us whether to+-- include or replace any language extensions enabled at the top+-- level. We include them when parsing a module entered at the+-- REPL, and replace them when parsing a standalone module.+parseModule :: LoadingMode -> Parser Module+parseModule mode = do+ exts <- S.fromList <$> many parseExtension+ let extFun = case mode of+ Standalone -> withExts+ REPL -> withAdditionalExts++ extFun exts $ do+ imports <- many parseImport+ topLevel <- many parseTopLevel+ let theMod = mkModule exts imports topLevel+ return theMod+ where+ groupTLs :: [DocThing] -> [TopLevel] -> TLResults+ groupTLs _ [] = emptyTLResults+ groupTLs revDocs (TLDoc doc : rest)+ = groupTLs (doc : revDocs) rest+ groupTLs revDocs (TLDecl decl@(DType (TypeDecl x _)) : rest)+ = groupTLs [] rest+ & tlDecls %~ (decl :)+ & tlDocs %~ ((x, reverse revDocs) :)+ groupTLs revDocs (TLDecl decl@(DTyDef (TypeDefn x _ _)) : rest)+ = groupTLs [] rest+ & tlDecls %~ (decl :)+ & tlDocs %~ ((string2Name x, reverse revDocs) :)+ groupTLs _ (TLDecl defn : rest)+ = groupTLs [] rest+ & tlDecls %~ (defn :)+ groupTLs _ (TLExpr t : rest)+ = groupTLs [] rest & tlTerms %~ (t:)++ defnGroups :: [Decl] -> [Decl]+ defnGroups [] = []+ defnGroups (d@DType{} : ds) = d : defnGroups ds+ defnGroups (d@DTyDef{} : ds) = d : defnGroups ds+ defnGroups (DDefn (TermDefn x bs) : ds) = DDefn (TermDefn x (bs ++ concatMap (\(TermDefn _ cs) -> cs) grp)) : defnGroups rest+ where+ (grp, rest) = matchDefn ds+ matchDefn :: [Decl] -> ([TermDefn], [Decl])+ matchDefn (DDefn t@(TermDefn x' _) : ds2) | x == x' = (t:ts, ds2')+ where+ (ts, ds2') = matchDefn ds2+ matchDefn ds2 = ([], ds2)++ mkModule exts imps tls = Module exts imps (defnGroups decls) docs terms+ where+ TLResults decls docs terms = groupTLs [] tls++-- | Parse an extension.+parseExtension :: Parser Ext+parseExtension = L.nonIndented sc $+ reserved "using" *> parseExtName++-- | Parse the name of a language extension (case-insensitive).+parseExtName :: Parser Ext+parseExtName = choice (map parseOneExt allExtsList) <?> "language extension name"+ where+ parseOneExt ext = ext <$ lexeme (string' (show ext) :: Parser String)++-- | Parse an import, of the form @import <modulename>@.+parseImport :: Parser String+parseImport = L.nonIndented sc $+ reserved "import" *> parseModuleName++-- | Parse the name of a module.+parseModuleName :: Parser String+parseModuleName = lexeme $+ intercalate "/" <$> (some (alphaNumChar <|> oneOf "_-") `sepBy` char '/') <* optional (string ".disco")++-- | Parse a top level item (either documentation or a declaration),+-- which must start at the left margin.+parseTopLevel :: Parser TopLevel+parseTopLevel = L.nonIndented sc $+ TLDoc <$> parseDocThing+ <|> TLDecl <$> try parseDecl+ <|> TLExpr <$> thenIndented parseTerm++-- | Parse a documentation item: either a group of lines beginning+-- with @|||@ (text documentation), or a group beginning with @!!!@+-- (checked examples/properties).+parseDocThing :: Parser DocThing+parseDocThing+ = DocString <$> some parseDocString+ <|> DocProperty <$> parseProperty++-- | Parse one line of documentation beginning with @|||@.+parseDocString :: Parser String+parseDocString = label "documentation" $ L.nonIndented sc $+ string "|||"+ *> takeWhileP Nothing (`elem` " \t")+ *> takeWhileP Nothing (`notElem` "\r\n") <* sc++ -- Note we use string "|||" rather than symbol "|||" because we+ -- don't want it to consume whitespace afterwards (in particular a+ -- line with ||| by itself would cause symbol "|||" to consume the+ -- newline).++-- | Parse a top-level property/unit test, of the form+--+-- @!!! forall x1 : ty1, ..., xn : tyn. term@.+--+-- The forall is optional.+parseProperty :: Parser Term+parseProperty = label "property" $ L.nonIndented sc $ do+ _ <- symbol "!!!"+ indented parseTerm++-- | Parse a single top-level declaration (either a type declaration+-- or single definition clause).+parseDecl :: Parser Decl+parseDecl = try (DType <$> parseTyDecl) <|> DDefn <$> parseDefn <|> DTyDef <$> parseTyDefn++-- | Parse a top-level type declaration of the form @x : ty@.+parseTyDecl :: Parser TypeDecl+parseTyDecl = label "type declaration" $+ TypeDecl <$> ident <*> indented (colon *> parsePolyTy)++-- | Parse a definition of the form @x pat1 .. patn = t@.+parseDefn :: Parser TermDefn+parseDefn = label "definition" $+ TermDefn+ <$> ident+ <*> indented ((:[]) <$> (bind <$> many parseAtomicPattern <*> (symbol "=" *> parseTerm)))++-- | Parse the definition of a user-defined algebraic data type.+parseTyDefn :: Parser TypeDefn+parseTyDefn = label "type defintion" $ do+ reserved "type"+ indented $ do+ name <- parseTyDef+ args <- fromMaybe [] <$> optional (parens $ parseTyVarName `sepBy1` comma)+ _ <- symbol "="+ TypeDefn name args <$> parseType++-- | Parse the entire input as a term (with leading whitespace and+-- no leftovers).+term :: Parser Term+term = between sc eof parseTerm++-- | Parse a term, consisting of a @parseTerm'@ optionally+-- followed by an ascription.+parseTerm :: Parser Term+parseTerm = -- trace "parseTerm" $+ ascribe <$> parseTerm' <*> optional (label "type annotation" $ colon *> parsePolyTy)+ where+ ascribe t Nothing = t+ ascribe t (Just ty) = TAscr t ty++-- | Parse a non-atomic, non-ascribed term.+parseTerm' :: Parser Term+parseTerm' = label "expression" $+ parseQuantified+ <|> parseLet+ <|> parseExpr+ <|> parseAtom++-- | Parse an atomic term.+parseAtom :: Parser Term+parseAtom = label "expression" $+ parseUnit+ <|> TBool True <$ (reserved "true" <|> reserved "True")+ <|> TBool False <$ (reserved "false" <|> reserved "False")+ <|> TChar <$> lexeme (between (char '\'') (char '\'') L.charLiteral)+ <|> TString <$> lexeme (char '"' >> manyTill L.charLiteral (char '"'))+ <|> TWild <$ try parseWild+ <|> TPrim <$> try parseStandaloneOp++ -- Note primitives are NOT reserved words, so they are just parsed+ -- as identifiers. This means that it is possible to shadow a+ -- primitive in a local context, as it should be. Vars are turned+ -- into prims at scope-checking time: if a var is not in scope but+ -- there is a prim of that name then it becomes a TPrim. See the+ -- 'typecheck Infer (TVar x)' case in Disco.Typecheck.+ <|> TVar <$> ident+ <|> TPrim <$> (ensureEnabled Primitives *> parsePrim)+ <|> TRat <$> try decimal+ <|> TNat <$> natural+ <|> parseTypeOp+ <|> TApp (TPrim PrimFloor) . TParens <$> fbrack parseTerm+ <|> TApp (TPrim PrimCeil) . TParens <$> cbrack parseTerm+ <|> parseCase+ <|> try parseAbs+ <|> bagdelims (parseContainer BagContainer)+ <|> braces (parseContainer SetContainer)+ <|> brackets (parseContainer ListContainer)+ <|> tuple <$> parens (parseTerm `sepBy1` comma)++parseAbs :: Parser Term+parseAbs = TApp (TPrim PrimAbs) <$> (pipe *> parseTerm <* pipe)++parseUnit :: Parser Term+parseUnit = TUnit <$ (reserved "unit" <|> void (symbol "■"))++-- | Parse a wildcard, which is an underscore that isn't the start of+-- an identifier.+parseWild :: Parser ()+parseWild = (lexeme . try . void) $+ string "_" <* notFollowedBy (alphaNumChar <|> oneOf "_'")++-- | Parse a standalone operator name with tildes indicating argument+-- slots, e.g. ~+~ for the addition operator.+parseStandaloneOp :: Parser Prim+parseStandaloneOp = asum $ concatMap mkStandaloneOpParsers (concat opTable)+ where+ mkStandaloneOpParsers :: OpInfo -> [Parser Prim]+ mkStandaloneOpParsers (OpInfo (UOpF Pre uop) syns _)+ = map (\syn -> PrimUOp uop <$ try (lexeme (string syn >> char '~'))) syns+ mkStandaloneOpParsers (OpInfo (UOpF Post uop) syns _)+ = map (\syn -> PrimUOp uop <$ try (lexeme (char '~' >> string syn))) syns+ mkStandaloneOpParsers (OpInfo (BOpF _ bop) syns _)+ = map (\syn -> PrimBOp bop <$ try (lexeme (char '~' >> string syn >> char '~'))) syns++ -- XXX TODO: improve the above so it first tries to parse a ~,+ -- then parses any postfix or infix thing; or else it looks for+ -- a prefix thing followed by a ~. This will get rid of the+ -- need for 'try' and also potentially improve error messages.+ -- The below may come in useful.++ -- flatOpTable = concat opTable++ -- prefixOps = [ (uop, syns) | (OpInfo (UOpF Pre uop) syns _) <- flatOpTable ]+ -- postfixOps = [ (uop, syns) | (OpInfo (UOpF Post uop) syns _) <- flatOpTable ]+ -- infixOps = [ (bop, syns) | (OpInfo (BOpF _ bop) syns _) <- flatOpTable ]++-- | Parse a primitive name starting with a $.+parsePrim :: Parser Prim+parsePrim = do+ void (char '$')+ x <- identifier letterChar+ case find ((==x) . primSyntax) primTable of+ Just (PrimInfo p _ _) -> return p+ Nothing -> fail ("Unrecognized primitive $" ++ x)++-- | Parse a container, like a literal list, set, bag, or a+-- comprehension (not including the square or curly brackets).+--+-- @+-- <container>+-- ::= '[' <container-contents> ']'+-- | '{' <container-contents> '}'+--+-- <container-contents>+-- ::= empty | <nonempty-container>+--+-- <nonempty-container>+-- ::= <term> [ <ellipsis> ]+-- | <term> <container-end>+--+-- <container-end>+-- ::= '|' <comprehension>+-- | ',' [ <term> (',' <item>)* ] [ <ellipsis> ]+--+-- <comprehension> ::= <qual> [ ',' <qual> ]*+--+-- <qual>+-- ::= <ident> 'in' <term>+-- | <term>+--+-- <ellipsis> ::= '..' [ <term> ]+-- @++parseContainer :: Container -> Parser Term+parseContainer c = nonEmptyContainer <|> return (TContainer c [] Nothing)+ -- Careful to do this without backtracking, since backtracking can+ -- lead to bad performance in certain pathological cases (for+ -- example, a very deeply nested list).++ where+ -- Any non-empty container starts with a term, followed by some+ -- remainder (which could either be the rest of a literal+ -- container, or a container comprehension). If there is no+ -- remainder just return a singleton container, optionally with an+ -- ellipsis.+ nonEmptyContainer = do+ t <- parseRepTerm++ containerRemainder t <|> singletonContainer t++ parseRepTerm = do+ t <- parseTerm+ n <- optional $ do+ guard (c == BagContainer)+ void hash+ parseTerm+ return (t, n)++ singletonContainer t = TContainer c [t] <$> optional parseEllipsis++ -- The remainder of a container after the first term starts with+ -- either a pipe (for a comprehension) or a comma (for a literal+ -- container).+ containerRemainder :: (Term, Maybe Term) -> Parser Term+ containerRemainder (t,n) = do+ s <- pipe <|> comma+ case (s, n) of+ ("|", Nothing) -> parseContainerComp c t+ ("|", Just _) -> fail "no comprehension with bag repetition syntax"+ (",", _) -> do+ -- Parse the rest of the terms in a literal container after+ -- the first, then an optional ellipsis, and return+ -- everything together.+ ts <- parseRepTerm `sepBy` comma+ e <- optional parseEllipsis++ return $ TContainer c ((t,n):ts) e+ _ -> error "Impossible, got a symbol other than '|' or ',' in containerRemainder"++-- | Parse an ellipsis at the end of a literal list, of the form+-- @.. t@. Any number > 1 of dots may be used, just for fun.+parseEllipsis :: Parser (Ellipsis Term)+parseEllipsis = do+ _ <- ellipsis+ Until <$> parseTerm++-- | Parse the part of a list comprehension after the | (without+-- square brackets), i.e. a list of qualifiers.+--+-- @q [,q]*@+parseContainerComp :: Container -> Term -> Parser Term+parseContainerComp c t = do+ qs <- toTelescope <$> (parseQual `sepBy` comma)+ return (TContainerComp c $ bind qs t)++-- | Parse a qualifier in a comprehension: either a binder @x in t@ or+-- a guard @t@.+parseQual :: Parser Qual+parseQual = try parseSelection <|> parseQualGuard+ where+ parseSelection = label "membership expression (x in ...)" $+ QBind <$> ident <*> (selector *> (embed <$> parseTerm))+ selector = reservedOp "<-" <|> reserved "in"++ parseQualGuard = label "boolean expression" $+ QGuard . embed <$> parseTerm++-- | Turn a parenthesized list of zero or more terms into the+-- appropriate syntax node: one term @(t)@ is just the term itself+-- (but we record the fact that it was parenthesized, in order to+-- correctly turn juxtaposition into multiplication); two or more+-- terms @(t1,t2,...)@ are a tuple.+tuple :: [Term] -> Term+tuple [x] = TParens x+tuple t = TTup t++-- | Parse a quantified abstraction (λ, ∀, ∃).+parseQuantified :: Parser Term+parseQuantified =+ TAbs <$> parseQuantifier+ <*> (bind <$> parsePattern `sepBy` comma <*> (dot *> parseTerm))++-- | Parse a quantifier symbol (lambda, forall, or exists).+parseQuantifier :: Parser Quantifier+parseQuantifier =+ Lam <$ lambda+ <|> All <$ forall+ <|> Ex <$ exists++-- | Parse a let expression (@let x1 = t1, x2 = t2, ... in t@).+parseLet :: Parser Term+parseLet =+ TLet <$>+ (reserved "let" *>+ (bind+ <$> (toTelescope <$> (parseBinding `sepBy` comma))+ <*> (reserved "in" *> parseTerm)))++-- | Parse a single binding (@x [ : ty ] = t@).+parseBinding :: Parser Binding+parseBinding = do+ x <- ident+ mty <- optional (colon *> parsePolyTy)+ t <- symbol "=" *> (embed <$> parseTerm)+ return $ Binding (embed <$> mty) x t++-- | Parse a case expression.+parseCase :: Parser Term+parseCase = between (symbol "{?") (symbol "?}") $+ TCase <$> parseBranch `sepBy` comma++-- | Parse one branch of a case expression.+parseBranch :: Parser Branch+parseBranch = flip bind <$> parseTerm <*> parseGuards++-- | Parse the list of guards in a branch. @otherwise@ can be used+-- interchangeably with an empty list of guards.+parseGuards :: Parser (Telescope Guard)+parseGuards = (TelEmpty <$ reserved "otherwise") <|> (toTelescope <$> many parseGuard)++-- | Parse a single guard (either @if@ or @when@)+parseGuard :: Parser Guard+parseGuard = parseGBool <|> parseGPat <|> parseGLet+ where+ parseGBool = GBool <$> (embed <$> (reserved "if" *> parseTerm))+ parseGPat = GPat <$> (embed <$> (reserved "when" *> parseTerm))+ <*> (reserved "is" *> parsePattern)+ parseGLet = GLet <$> (reserved "let" *> parseBinding)++-- | Parse an atomic pattern, by parsing a term and then attempting to+-- convert it to a pattern.+parseAtomicPattern :: Parser Pattern+parseAtomicPattern = label "pattern" $ do+ t <- parseAtom+ case termToPattern t of+ Nothing -> fail $ "Invalid pattern: " ++ show t+ Just p -> return p++-- | Parse a pattern, by parsing a term and then attempting to convert+-- it to a pattern.+parsePattern :: Parser Pattern+parsePattern = label "pattern" $ do+ t <- parseTerm+ case termToPattern t of+ Nothing -> fail $ "Invalid pattern: " ++ show t+ Just p -> return p++-- | Attempt converting a term to a pattern.+termToPattern :: Term -> Maybe Pattern+termToPattern TWild = Just PWild+termToPattern (TVar x) = Just $ PVar x+termToPattern (TParens t) = termToPattern t+termToPattern TUnit = Just PUnit+termToPattern (TBool b) = Just $ PBool b+termToPattern (TNat n) = Just $ PNat n+termToPattern (TChar c) = Just $ PChar c+termToPattern (TString s) = Just $ PString s+termToPattern (TTup ts) = PTup <$> mapM termToPattern ts+termToPattern (TApp (TVar i) t)+ | i == string2Name "left" = PInj L <$> termToPattern t+ | i == string2Name "right" = PInj R <$> termToPattern t+-- termToPattern (TInj s t) = PInj s <$> termToPattern t++termToPattern (TAscr t s) = case s of+ Forall (unsafeUnbind -> ([], s')) -> PAscr <$> termToPattern t <*> pure s'+ _ -> Nothing++termToPattern (TBin Cons t1 t2)+ = PCons <$> termToPattern t1 <*> termToPattern t2++termToPattern (TBin Add t1 t2)+ = case (termToPattern t1, termToPattern t2) of+ (Just p, _)+ | length (toListOf fvAny p) == 1+ && null (toListOf fvAny t2)+ -> Just $ PAdd L p t2+ (_, Just p)+ | length (toListOf fvAny p) == 1+ && null (toListOf fvAny t1)+ -> Just $ PAdd R p t1+ _ -> Nothing+ -- If t1 is a pattern binding one variable, and t2 has no fvs,+ -- this can be a PAdd L. Also vice versa for PAdd R.++termToPattern (TBin Mul t1 t2)+ = case (termToPattern t1, termToPattern t2) of+ (Just p, _)+ | length (toListOf fvAny p) == 1+ && null (toListOf fvAny t2)+ -> Just $ PMul L p t2+ (_, Just p)+ | length (toListOf fvAny p) == 1+ && null (toListOf fvAny t1)+ -> Just $ PMul R p t1+ _ -> Nothing+ -- If t1 is a pattern binding one variable, and t2 has no fvs,+ -- this can be a PMul L. Also vice versa for PMul R.++termToPattern (TBin Sub t1 t2)+ = case termToPattern t1 of+ Just p+ | length (toListOf fvAny p) == 1+ && null (toListOf fvAny t2)+ -> Just $ PSub p t2+ _ -> Nothing+ -- If t1 is a pattern binding one variable, and t2 has no fvs,+ -- this can be a PSub.++ -- For now we don't handle the case of t - p, since it seems+ -- less useful (and desugaring it would require extra code since+ -- subtraction is not commutative).++termToPattern (TBin Div t1 t2)+ = PFrac <$> termToPattern t1 <*> termToPattern t2++termToPattern (TUn Neg t) = PNeg <$> termToPattern t++termToPattern (TContainer ListContainer ts Nothing)+ = PList <$> mapM (termToPattern . fst) ts++termToPattern _ = Nothing++-- | Parse an expression built out of unary and binary operators.+parseExpr :: Parser Term+parseExpr = fixJuxtMul . fixChains <$> (makeExprParser parseAtom table <?> "expression")+ where+ table+ -- Special case for function application, with highest+ -- precedence. Note that we parse all juxtaposition as+ -- function application first; we later go through and turn+ -- some into multiplication (fixing up the precedence+ -- appropriately) based on a syntactic analysis.+ = [ InfixL (TApp <$ string "") ]++ -- get all other operators from the opTable+ : (map . concatMap) mkOpParser opTable++ mkOpParser :: OpInfo -> [Operator Parser Term]+ mkOpParser (OpInfo op syns _) = map (withOpFixity op) syns++ withOpFixity (UOpF fx op) syn+ = ufxParser fx ((reservedOp syn <?> "operator") >> return (TUn op))++ withOpFixity (BOpF fx op) syn+ = bfxParser fx ((reservedOp syn <?> "operator") >> return (TBin op))++ ufxParser Pre = Prefix+ ufxParser Post = Postfix++ bfxParser InL = InfixL+ bfxParser InR = InfixR+ bfxParser In = InfixN++ isChainable op = op `elem` [Eq, Neq, Lt, Gt, Leq, Geq, Divides]++ -- Comparison chains like 3 < x < 5 first get parsed as 3 < (x <+ -- 5), which does not make sense. This function looks for such+ -- nested comparison operators and turns them into a TChain.+ fixChains (TUn op t) = TUn op (fixChains t)+ fixChains (TBin op t1 (TBin op' t21 t22))+ | isChainable op && isChainable op' = TChain t1 (TLink op t21 : getLinks op' t22)+ fixChains (TBin op t1 t2) = TBin op (fixChains t1) (fixChains t2)+ fixChains (TApp t1 t2) = TApp (fixChains t1) (fixChains t2)++ -- Only recurse as long as we see TUn, TBin, or TApp which could+ -- have been generated by the expression parser. If we see+ -- anything else we can stop.+ fixChains e = e++ getLinks op (TBin op' t1 t2)+ | isChainable op' = TLink op t1 : getLinks op' t2+ getLinks op e = [TLink op (fixChains e)]++ -- Find juxtapositions (parsed as function application) which+ -- syntactically have either a literal Nat or a parenthesized+ -- expression containing an operator as the LHS, and turn them+ -- into multiplications. Then fix up the parse tree by rotating+ -- newly created multiplications up until their precedence is+ -- higher than the thing above them.++ fixJuxtMul :: Term -> Term++ -- Just recurse through TUn or TBin and fix precedence on the way back up.+ fixJuxtMul (TUn op t) = fixPrec $ TUn op (fixJuxtMul t)+ fixJuxtMul (TBin op t1 t2) = fixPrec $ TBin op (fixJuxtMul t1) (fixJuxtMul t2)++ -- Possibly turn a TApp into a multiplication, if the LHS looks+ -- like a multiplicative term. However, we must be sure to+ -- *first* recursively fix the subterms (particularly the+ -- left-hand one) *before* doing this analysis. See+ -- <https://github.com/disco-lang/disco/issues/71> .+ fixJuxtMul (TApp t1 t2)+ | isMultiplicativeTerm t1' = fixPrec $ TBin Mul t1' t2'+ | otherwise = fixPrec $ TApp t1' t2'+ where+ t1' = fixJuxtMul t1+ t2' = fixJuxtMul t2++ -- Otherwise we can stop recursing, since anything other than TUn,+ -- TBin, or TApp could not have been produced by the expression+ -- parser.+ fixJuxtMul t = t++ -- A multiplicative term is one that looks like either a natural+ -- number literal, or a unary or binary operation (optionally+ -- parenthesized). For example, 3, (-2), and (x + 5) are all+ -- multiplicative terms, so 3x, (-2)x, and (x + 5)x all get parsed+ -- as multiplication. On the other hand, (x y) is always parsed+ -- as function application, even if x and y both turn out to have+ -- numeric types; a variable like x does not count as a+ -- multiplicative term. Likewise, (x y) z is parsed as function+ -- application, since (x y) is not a multiplicative term: it is+ -- parenthezised, but contains a TApp rather than a TBin or TUn.+ isMultiplicativeTerm :: Term -> Bool+ isMultiplicativeTerm (TNat _) = True+ isMultiplicativeTerm TUn{} = True+ isMultiplicativeTerm TBin{} = True+ isMultiplicativeTerm (TParens t) = isMultiplicativeTerm t+ isMultiplicativeTerm _ = False++ -- Fix precedence by bubbling up any new TBin terms whose+ -- precedence is less than that of the operator above them. We+ -- don't worry at all about fixing associativity, just precedence.++ fixPrec :: Term -> Term++ -- e.g. 2y! --> (2@y)! --> fixup --> 2 * (y!)+ fixPrec (TUn uop (TBin bop t1 t2))+ | bPrec bop < uPrec uop = case uopMap M.! uop of+ OpInfo (UOpF Pre _) _ _ -> TBin bop (TUn uop t1) t2+ OpInfo (UOpF Post _) _ _ -> TBin bop t1 (TUn uop t2)+ _ -> error "Impossible! In fixPrec, uopMap contained OpInfo (BOpF ...)"++ fixPrec (TBin bop1 (TBin bop2 t1 t2) t3)+ | bPrec bop2 < bPrec bop1 = TBin bop2 t1 (fixPrec $ TBin bop1 t2 t3)++ -- e.g. x^2y --> x^(2@y) --> x^(2*y) --> (x^2) * y+ fixPrec (TBin bop1 t1 (TBin bop2 t2 t3))+ | bPrec bop2 < bPrec bop1 = TBin bop2 (fixPrec $ TBin bop1 t1 t2) t3++ fixPrec t = t++-- | Parse an atomic type.+parseAtomicType :: Parser Type+parseAtomicType = label "type" $+ TyVoid <$ reserved "Void"+ <|> TyUnit <$ reserved "Unit"+ <|> TyBool <$ (reserved "Boolean" <|> reserved "Bool")+ <|> TyProp <$ (reserved "Proposition" <|> reserved "Prop")+ <|> TyC <$ reserved "Char"+ -- <|> try parseTyFin+ <|> TyN <$ (reserved "Natural" <|> reserved "Nat" <|> reserved "N" <|> reserved "ℕ")+ <|> TyZ <$ (reserved "Integer" <|> reserved "Int" <|> reserved "Z" <|> reserved "ℤ")+ <|> TyF <$ (reserved "Fractional" <|> reserved "Frac" <|> reserved "F" <|> reserved "𝔽")+ <|> TyQ <$ (reserved "Rational" <|> reserved "Q" <|> reserved "ℚ")+ <|> TyCon <$> parseCon <*> (fromMaybe [] <$> optional (parens (parseType `sepBy1` comma)))+ <|> TyVar <$> parseTyVar+ <|> parens parseType++-- parseTyFin :: Parser Type+-- parseTyFin = TyFin <$> (reserved "Fin" *> natural)+-- <|> TyFin <$> (lexeme (string "Z" <|> string "ℤ") *> natural)++parseCon :: Parser Con+parseCon =+ CList <$ reserved "List"+ <|> CBag <$ reserved "Bag"+ <|> CSet <$ reserved "Set"+ <|> CGraph <$ reserved "Graph"+ <|> CMap <$ reserved "Map"+ <|> CUser <$> parseTyDef++parseTyDef :: Parser String+parseTyDef = identifier upperChar++parseTyVarName :: Parser String+parseTyVarName = identifier lowerChar++parseTyVar :: Parser (Name Type)+parseTyVar = string2Name <$> parseTyVarName++parsePolyTy :: Parser PolyType+parsePolyTy = closeType <$> parseType++-- | Parse a type expression built out of binary operators.+parseType :: Parser Type+parseType = makeExprParser parseAtomicType table+ where+ table = [ [ infixR "*" (:*:)+ , infixR "×" (:*:) ]+ , [ infixR "+" (:+:)+ , infixR "⊎" (:+:)+ ]+ , [ infixR "->" (:->:)+ , infixR "→" (:->:)+ ]+ ]++ infixR name fun = InfixR (reservedOp name >> return fun)++parseTyOp :: Parser TyOp+parseTyOp =+ Enumerate <$ reserved "enumerate"+ <|> Count <$ reserved "count"++parseTypeOp :: Parser Term+parseTypeOp = TTyOp <$> parseTyOp <*> parseAtomicType
+ src/Disco/Pretty.hs view
@@ -0,0 +1,191 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Pretty+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Various pretty-printing facilities for disco.+--+-----------------------------------------------------------------------------++-- TODO: the calls to 'error' should be replaced with logging/error capabilities.++module Disco.Pretty+ ( module Disco.Pretty.DSL+ , module Disco.Pretty+ , module Disco.Pretty.Prec+ , Doc+ )+ where++import Prelude hiding ((<>))++import Data.Bifunctor+import Data.Char (isAlpha)+import Data.Map (Map)+import qualified Data.Map as M+import Data.Ratio+import Data.Set (Set)+import qualified Data.Set as S++import Disco.Effects.LFresh+import Polysemy++import Polysemy.Reader++import Text.PrettyPrint (Doc)+import Unbound.Generics.LocallyNameless (Name)++import Disco.Pretty.DSL+import Disco.Pretty.Prec+import Disco.Syntax.Operators++------------------------------------------------------------+-- Utilities for handling precedence and associativity++-- | Convenience function combining 'setPA' and 'mparens', since we+-- often want to simultaneously indicate what the precedence and+-- associativity of a term is, and optionally surround it with+-- parentheses depending on the precedence and associativity of its+-- parent.+withPA :: Member (Reader PA) r => PA -> Sem r Doc -> Sem r Doc+withPA pa = mparens pa . setPA pa++-- | Locally set the precedence and associativity within a+-- subcomputation.+setPA :: Member (Reader PA) r => PA -> Sem r a -> Sem r a+setPA = local . const++-- | Mark a subcomputation as pretty-printing a term on the left of an+-- operator (so parentheses can be inserted appropriately, depending+-- on the associativity).+lt :: Member (Reader PA) r => Sem r Doc -> Sem r Doc+lt = local (\(PA p _) -> PA p InL)++-- | Mark a subcomputation as pretty-printing a term on the right of+-- an operator (so parentheses can be inserted appropriately,+-- depending on the associativity).+rt :: Member (Reader PA) r => Sem r Doc -> Sem r Doc+rt = local (\(PA p _) -> PA p InR)++-- | Optionally surround a pretty-printed term with parentheses,+-- depending on its precedence and associativity (given as the 'PA'+-- argument) and that of its context (given by the ambient 'Reader+-- PA' effect).+mparens :: Member (Reader PA) r => PA -> Sem r Doc -> Sem r Doc+mparens pa doc = do+ parentPA <- ask+ (if pa < parentPA then parens else id) doc++------------------------------------------------------------+-- Pretty type class++class Pretty t where+ pretty :: Members '[Reader PA, LFresh] r => t -> Sem r Doc++prettyStr :: Pretty t => t -> Sem r String+prettyStr = renderDoc . runLFresh . pretty++pretty' :: Pretty t => t -> Sem r Doc+pretty' = runReader initPA . runLFresh . pretty++------------------------------------------------------------+-- Some standard instances++instance Pretty a => Pretty [a] where+ pretty = brackets . intercalate "," . map pretty++instance (Pretty k, Pretty v) => Pretty (Map k v) where+ pretty m = do+ let es = map (\(k,v) -> pretty k <+> "->" <+> pretty v) (M.assocs m)+ ds <- setPA initPA $ punctuate "," es+ braces (hsep ds)++instance Pretty a => Pretty (Set a) where+ pretty = braces . intercalate "," . map pretty . S.toList++------------------------------------------------------------+-- Some Disco instances++instance Pretty (Name a) where+ pretty = text . show++instance Pretty TyOp where+ pretty = \case+ Enumerate -> text "enumerate"+ Count -> text "count"++-- | Pretty-print a unary operator, by looking up its concrete syntax+-- in the 'uopMap'.+instance Pretty UOp where+ pretty op = case M.lookup op uopMap of+ Just (OpInfo _ (syn:_) _) ->+ text $ syn ++ (if all isAlpha syn then " " else "")+ _ -> error $ "UOp " ++ show op ++ " not in uopMap!"++-- | Pretty-print a binary operator, by looking up its concrete syntax+-- in the 'bopMap'.+instance Pretty BOp where+ pretty op = case M.lookup op bopMap of+ Just (OpInfo _ (syn:_) _) -> text syn+ _ -> error $ "BOp " ++ show op ++ " not in bopMap!"++--------------------------------------------------+-- Pretty-printing decimals++-- | Pretty-print a rational number using its decimal expansion, in+-- the format @nnn.prefix[rep]...@, with any repeating digits enclosed+-- in square brackets.+prettyDecimal :: Rational -> String+prettyDecimal r = printedDecimal+ where+ (n,d) = properFraction r :: (Integer, Rational)+ (expan, len) = digitalExpansion 10 (numerator d) (denominator d)+ printedDecimal+ | length first102 > 101 || length first102 == 101 && last first102 /= 0+ = show n ++ "." ++ concatMap show (take 100 expan) ++ "..."+ | rep == [0]+ = show n ++ "." ++ (if null pre then "0" else concatMap show pre)+ | otherwise+ = show n ++ "." ++ concatMap show pre ++ "[" ++ concatMap show rep ++ "]"+ where+ (pre, rep) = splitAt len expan+ first102 = take 102 expan++-- Given a list, find the indices of the list giving the first and+-- second occurrence of the first element to repeat, or Nothing if+-- there are no repeats.+findRep :: Ord a => [a] -> ([a], Int)+findRep = findRep' M.empty 0++findRep' :: Ord a => M.Map a Int -> Int -> [a] -> ([a], Int)+findRep' _ _ [] = error "Impossible. Empty list in findRep'"+findRep' prevs ix (x:xs)+ | x `M.member` prevs = ([], prevs M.! x)+ | otherwise = first (x:) $ findRep' (M.insert x ix prevs) (ix+1) xs++-- | @digitalExpansion b n d@ takes the numerator and denominator of a+-- fraction n/d between 0 and 1, and returns a pair of (1) a list of+-- digits @ds@, and (2) a nonnegative integer k such that @splitAt k+-- ds = (prefix, rep)@, where the infinite base-b expansion of+-- n/d is 0.@(prefix ++ cycle rep)@. For example,+--+-- > digitalExpansion 10 1 4 = ([2,5,0], 2)+-- > digitalExpansion 10 1 7 = ([1,4,2,8,5,7], 0)+-- > digitalExpansion 10 3 28 = ([1,0,7,1,4,2,8,5], 2)+-- > digitalExpansion 2 1 5 = ([0,0,1,1], 0)+--+-- It works by performing the standard long division algorithm, and+-- looking for the first time that the remainder repeats.+digitalExpansion :: Integer -> Integer -> Integer -> ([Integer], Int)+digitalExpansion b n d = digits+ where+ longDivStep (_, r) = (b*r) `divMod` d+ res = tail $ iterate longDivStep (0,n)+ digits = first (map fst) (findRep res)
+ src/Disco/Pretty/DSL.hs view
@@ -0,0 +1,107 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Pretty.DSL+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Adapter DSL on top of Text.PrettyPrint for Applicative pretty-printing.+--+-----------------------------------------------------------------------------++{-# OPTIONS_GHC -fno-warn-orphans #-}++module Disco.Pretty.DSL where++import Control.Applicative hiding (empty)+import Data.String (IsString (..))+import Prelude hiding ((<>))++import Polysemy+import Polysemy.Reader++import Text.PrettyPrint (Doc)+import qualified Text.PrettyPrint as PP++import Disco.Pretty.Prec++instance IsString (Sem r Doc) where+ fromString = text++------------------------------------------------------------+-- Adapter DSL+--+-- Each combinator here mirrors one from Text.PrettyPrint, but+-- operates over a generic functor/monad.++vcat :: Applicative f => [f Doc] -> f Doc+vcat ds = PP.vcat <$> sequenceA ds++hcat :: Applicative f => [f Doc] -> f Doc+hcat ds = PP.hcat <$> sequenceA ds++hsep :: Applicative f => [f Doc] -> f Doc+hsep ds = PP.hsep <$> sequenceA ds++parens :: Functor f => f Doc -> f Doc+parens = fmap PP.parens++brackets :: Functor f => f Doc -> f Doc+brackets = fmap PP.brackets++braces :: Functor f => f Doc -> f Doc+braces = fmap PP.braces++bag :: Applicative f => f Doc -> f Doc+bag p = text "⟅" <> p <> text "⟆"++quotes :: Functor f => f Doc -> f Doc+quotes = fmap PP.quotes++doubleQuotes :: Functor f => f Doc -> f Doc+doubleQuotes = fmap PP.doubleQuotes++text :: Applicative m => String -> m Doc+text = pure . PP.text++integer :: Applicative m => Integer -> m Doc+integer = pure . PP.integer++nest :: Functor f => Int -> f Doc -> f Doc+nest n d = PP.nest n <$> d++hang :: Applicative f => f Doc -> Int -> f Doc -> f Doc+hang d1 n d2 = PP.hang <$> d1 <*> pure n <*> d2++empty :: Applicative m => m Doc+empty = pure PP.empty++(<+>) :: Applicative f => f Doc -> f Doc -> f Doc+(<+>) = liftA2 (PP.<+>)++(<>) :: Applicative f => f Doc -> f Doc -> f Doc+(<>) = liftA2 (PP.<>)++($+$) :: Applicative f => f Doc -> f Doc -> f Doc+($+$) = liftA2 (PP.$+$)++punctuate :: Applicative f => f Doc -> [f Doc] -> f [f Doc]+punctuate p ds = map pure <$> (PP.punctuate <$> p <*> sequenceA ds)++intercalate :: Monad f => f Doc -> [f Doc] -> f Doc+intercalate p ds = do+ ds' <- punctuate p ds+ hsep ds'++bulletList :: Applicative f => f Doc -> [f Doc] -> f Doc+bulletList bullet = vcat . map (hang bullet 2)++------------------------------------------------------------+-- Running a pretty-printer++renderDoc :: Sem (Reader PA ': r) Doc -> Sem r String+renderDoc = fmap PP.render . runReader initPA++renderDoc' :: Doc -> String+renderDoc' = PP.render
+ src/Disco/Pretty/Prec.hs view
@@ -0,0 +1,53 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Pretty.Prec+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Precedence and associativity for pretty-printing.+--+-----------------------------------------------------------------------------++module Disco.Pretty.Prec where++import Disco.Syntax.Operators++-- Types for storing precedence + associativity together++type Prec = Int++data PA = PA Prec BFixity+ deriving (Show, Eq)++instance Ord PA where+ compare (PA p1 a1) (PA p2 a2) = compare p1 p2 `mappend` (if a1 == a2 then EQ else LT)++-- Standard precedence levels++initPA :: PA+initPA = PA 0 InL++ascrPA :: PA+ascrPA = PA 1 InL++funPA :: PA+funPA = PA funPrec InL++rPA :: Int -> PA+rPA n = PA n InR++tarrPA, taddPA, tmulPA, tfunPA :: PA+tarrPA = rPA 1+taddPA = rPA 6+tmulPA = rPA 7+tfunPA = PA 9 InL++-- Converting UOp and BOp++ugetPA :: UOp -> PA+ugetPA op = PA (uPrec op) In++getPA :: BOp -> PA+getPA op = PA (bPrec op) (assoc op)
+ src/Disco/Property.hs view
@@ -0,0 +1,53 @@++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Property+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Properties of disco functions.+--+-----------------------------------------------------------------------------++module Disco.Property+ where++import qualified Data.Enumeration.Invertible as E+import qualified Test.QuickCheck as QC++import Disco.Effects.Random+import Polysemy++import Disco.Value++-- | Toggles which outcome (finding or not finding the thing being+-- searched for) qualifies as success, without changing the thing+-- being searched for.+invertMotive :: SearchMotive -> SearchMotive+invertMotive (SearchMotive (a, b)) = SearchMotive (not a, b)++-- | Flips the success or failure status of a @PropResult@, leaving+-- the explanation unchanged.+invertPropResult :: TestResult -> TestResult+invertPropResult res@(TestResult b r env)+ | TestRuntimeError _ <- r = res+ | otherwise = TestResult (not b) r env++-- | Select samples from an enumeration according to a search type. Also returns+-- a 'SearchType' describing the results, which may be 'Exhaustive' if the+-- enumeration is no larger than the number of samples requested.+generateSamples :: Member Random r => SearchType -> E.IEnumeration a -> Sem r ([a], SearchType)+generateSamples Exhaustive e = return (E.enumerate e, Exhaustive)+generateSamples (Randomized n m) e+ | E.Finite k <- E.card e, k <= n + m = return (E.enumerate e, Exhaustive)+ | otherwise = do+ let small = [0 .. n]+ rs <- runGen . mapM sizedNat $ [n .. n + m]+ let samples = map (E.select e) $ small ++ rs+ return (samples, Randomized n m)+ where+ sizedNat k = QC.resize (fromIntegral k) QC.arbitrarySizedNatural++-- XXX do shrinking for randomly generated test cases?
+ src/Disco/Report.hs view
@@ -0,0 +1,55 @@++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Report+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- XXX+--+-----------------------------------------------------------------------------++-- The benefit of having our own deeply-embedded type for pretty+-- printing things would be so we can render it in different backend+-- formats later (text, LaTeX, HTML, ...) so at some point it may be+-- worth doing it. The idea would be to mostly replicate the+-- interface of the pretty-printing library currently being used, so+-- that a lot of code could just be kept unchanged.++module Disco.Report where++import Data.List (intersperse)++data Report+ = RTxt String+ | RSeq [Report]+ | RVSeq [Report]+ | RList [Report]+ | RNest Report+ deriving (Show)++text :: String -> Report+text = RTxt++hcat :: [Report] -> Report+hcat = RSeq++hsep :: [Report] -> Report+hsep = hcat . intersperse (text " ")++vcat :: [Report] -> Report+vcat = RVSeq++vsep :: [Report] -> Report+vsep = vcat . intersperse (text "")++list :: [Report] -> Report+list = RList++nest :: Report -> Report+nest = RNest++------------------------------------------------------------+
+ src/Disco/Subst.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Subst+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- The "Disco.Subst" module defines a generic type of substitutions+-- that map variable names to values.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Subst+ ( -- * Substitutions++ Substitution(..), dom++ -- ** Constructing/destructing substitutions++ , idS, (|->), fromList, toList++ -- ** Substitution operations++ , (@@), compose, applySubst, lookup++ )+ where++import Prelude hiding (lookup)++import Unbound.Generics.LocallyNameless (Name, Subst, substs)++import Data.Coerce++import Data.Map (Map)+import qualified Data.Map as M+import Data.Set (Set)++import Disco.Effects.LFresh+import Disco.Pretty+import Polysemy+import Polysemy.Reader++-- | A value of type @Substitution a@ is a substitution which maps some set of+-- names (the /domain/, see 'dom') to values of type @a@.+-- Substitutions can be /applied/ to certain terms (see+-- 'applySubst'), replacing any free occurrences of names in the+-- domain with their corresponding values. Thus, substitutions can+-- be thought of as functions of type @Term -> Term@ (for suitable+-- @Term@s that contain names and values of the right type).+--+-- Concretely, substitutions are stored using a @Map@.+--+-- See also "Disco.Types", which defines 'S' as an alias for+-- substitutions on types (the most common kind in the disco+-- codebase).+newtype Substitution a = Substitution { getSubst :: Map (Name a) a }+ deriving (Eq, Ord, Show)++instance Functor Substitution where+ fmap f (Substitution m) = Substitution (M.mapKeys coerce . M.map f $ m)++instance Pretty a => Pretty (Substitution a) where+ pretty (Substitution s) = do+ let es = map (uncurry prettyMapping) (M.assocs s)+ ds <- punctuate "," es+ braces (hsep ds)++prettyMapping :: (Pretty a, Members '[Reader PA, LFresh] r) => Name a -> a -> Sem r Doc+prettyMapping x a = pretty x <+> "->" <+> pretty a++-- | The domain of a substitution is the set of names for which the+-- substitution is defined.+dom :: Substitution a -> Set (Name a)+dom = M.keysSet . getSubst++-- | The identity substitution, /i.e./ the unique substitution with an+-- empty domain, which acts as the identity function on terms.+idS :: Substitution a+idS = Substitution M.empty++-- | Construct a singleton substitution, which maps the given name to+-- the given value.+(|->) :: Name a -> a -> Substitution a+x |-> t = Substitution (M.singleton x t)++-- | Compose two substitutions. Applying @s1 \@\@ s2@ is the same as+-- applying first @s2@, then @s1@; that is, semantically,+-- composition of substitutions corresponds exactly to function+-- composition when they are considered as functions on terms.+--+-- As one would expect, composition is associative and has 'idS' as+-- its identity.+(@@) :: Subst a a => Substitution a -> Substitution a -> Substitution a+(Substitution s1) @@ (Substitution s2) = Substitution ((M.map (applySubst (Substitution s1))) s2 `M.union` s1)++-- | Compose a whole container of substitutions. For example,+-- @compose [s1, s2, s3] = s1 \@\@ s2 \@\@ s3@.+compose :: (Subst a a, Foldable t) => t (Substitution a) -> Substitution a+compose = foldr (@@) idS++-- | Apply a substitution to a term, resulting in a new term in which+-- any free variables in the domain of the substitution have been+-- replaced by their corresponding values. Note this requires a+-- @Subst b a@ constraint, which intuitively means that values of+-- type @a@ contain variables of type @b@ we can substitute for.+applySubst :: Subst b a => Substitution b -> a -> a+applySubst (Substitution s) = substs (M.assocs s)++-- | Create a substitution from an association list of names and+-- values.+fromList :: [(Name a, a)] -> Substitution a+fromList = Substitution . M.fromList++-- | Convert a substitution into an association list.+toList :: Substitution a -> [(Name a, a)]+toList (Substitution m) = M.assocs m++-- | Look up the value a particular name maps to under the given+-- substitution; or return @Nothing@ if the name being looked up is+-- not in the domain.+lookup :: Name a -> Substitution a -> Maybe a+lookup x (Substitution m) = M.lookup x m
+ src/Disco/Syntax/Operators.hs view
@@ -0,0 +1,213 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Syntax.Operators+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- Unary and binary operators along with information like precedence,+-- fixity, and concrete syntax.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Syntax.Operators+ ( -- * Operators+ UOp(..), BOp(..), TyOp(..)++ -- * Operator info+ , UFixity(..), BFixity(..), OpFixity(..), OpInfo(..)++ -- * Operator tables and lookup+ , opTable, uopMap, bopMap+ , uPrec, bPrec, assoc, funPrec++ ) where++import Data.Data (Data)+import GHC.Generics (Generic)+import Unbound.Generics.LocallyNameless++import Data.Map (Map, (!))+import qualified Data.Map as M++------------------------------------------------------------+-- Operators+------------------------------------------------------------++-- | Unary operators.+data UOp = Neg -- ^ Arithmetic negation (@-@)+ | Not -- ^ Logical negation (@not@)+ | Fact -- ^ Factorial (@!@)+ deriving (Show, Read, Eq, Ord, Generic, Data, Alpha, Subst t)++-- | Binary operators.+data BOp = Add -- ^ Addition (@+@)+ | Sub -- ^ Subtraction (@-@)+ | SSub -- ^ Saturating Subtraction (@.-@ / @∸@)+ | Mul -- ^ Multiplication (@*@)+ | Div -- ^ Division (@/@)+ | Exp -- ^ Exponentiation (@^@)+ | IDiv -- ^ Integer division (@//@)+ | Eq -- ^ Equality test (@==@)+ | Neq -- ^ Not-equal (@/=@)+ | Lt -- ^ Less than (@<@)+ | Gt -- ^ Greater than (@>@)+ | Leq -- ^ Less than or equal (@<=@)+ | Geq -- ^ Greater than or equal (@>=@)+ | Min -- ^ Minimum (@min@)+ | Max -- ^ Maximum (@max@)+ | And -- ^ Logical and (@&&@ / @and@)+ | Or -- ^ Logical or (@||@ / @or@)+ | Impl -- ^ Logical implies (@==>@ / @implies@)+ | Mod -- ^ Modulo (@mod@)+ | Divides -- ^ Divisibility test (@|@)+ | Choose -- ^ Binomial and multinomial coefficients (@choose@)+ | Cons -- ^ List cons (@::@)+ | Union -- ^ Union of two sets (@union@ / @∪@)+ | Inter -- ^ Intersection of two sets (@intersect@ / @∩@)+ | Diff -- ^ Difference between two sets (@\@)+ | Elem -- ^ Element test (@∈@)+ | Subset -- ^ Subset test (@⊆@)+ | ShouldEq -- ^ Equality assertion (@=!=@)+ deriving (Show, Read, Eq, Ord, Generic, Data, Alpha, Subst t)++-- | Type operators.+data TyOp = Enumerate -- ^ List all values of a type+ | Count -- ^ Count how many values there are of a type+ deriving (Show, Eq, Ord, Generic, Data, Alpha, Subst t)++------------------------------------------------------------+-- Operator info+------------------------------------------------------------++-- | Fixities of unary operators (either pre- or postfix).+data UFixity+ = Pre -- ^ Unary prefix.+ | Post -- ^ Unary postfix.+ deriving (Eq, Ord, Enum, Bounded, Show, Generic)++-- | Fixity/associativity of infix binary operators (either left,+-- right, or non-associative).+data BFixity+ = InL -- ^ Left-associative infix.+ | InR -- ^ Right-associative infix.+ | In -- ^ Infix.+ deriving (Eq, Ord, Enum, Bounded, Show, Generic)++-- | Operators together with their fixity.+data OpFixity =+ UOpF UFixity UOp+ | BOpF BFixity BOp+ deriving (Eq, Show, Generic)++-- | An @OpInfo@ record contains information about an operator, such+-- as the operator itself, its fixity, a list of concrete syntax+-- representations, and a numeric precedence level.+data OpInfo =+ OpInfo+ { opFixity :: OpFixity+ , opSyns :: [String]+ , opPrec :: Int+ }+ deriving Show++------------------------------------------------------------+-- Operator table+------------------------------------------------------------++-- | The @opTable@ lists all the operators in the language, in order+-- of precedence (highest precedence first). Operators in the same+-- list have the same precedence. This table is used by both the+-- parser and the pretty-printer.+opTable :: [[OpInfo]]+opTable =+ assignPrecLevels+ [ [ uopInfo Pre Not ["not", "¬"]+ ]+ , [ uopInfo Post Fact ["!"]+ ]+ , [ bopInfo InR Exp ["^"]+ ]+ , [ uopInfo Pre Neg ["-"]+ ]+ , [ bopInfo In Choose ["choose"]+ ]+ , [ bopInfo InL Union ["union", "∪"]+ , bopInfo InL Inter ["intersect", "∩"]+ , bopInfo InL Diff ["\\"]+ ]+ , [ bopInfo InL Min ["min"]+ , bopInfo InL Max ["max"]+ ]+ , [ bopInfo InL Mul ["*"]+ , bopInfo InL Div ["/"]+ , bopInfo InL Mod ["%"]+ , bopInfo InL Mod ["mod"]+ , bopInfo InL IDiv ["//"]+ ]+ , [ bopInfo InL Add ["+"]+ , bopInfo InL Sub ["-"]+ , bopInfo InL SSub [".-", "∸"]+ ]+ , [ bopInfo InR Cons ["::"]+ ]+ , [ bopInfo InR Eq ["=="]+ , bopInfo InR ShouldEq ["=!="]+ , bopInfo InR Neq ["/=", "≠"]+ , bopInfo InR Lt ["<"]+ , bopInfo InR Gt [">"]+ , bopInfo InR Leq ["<=", "≤"]+ , bopInfo InR Geq [">=", "≥"]+ , bopInfo InR Divides ["divides"]+ , bopInfo InL Subset ["subset", "⊆"]+ , bopInfo InL Elem ["elem", "∈"]+ ]+ , [ bopInfo InR And ["and", "∧", "&&"]+ ]+ , [ bopInfo InR Or ["or", "∨", "||"]+ ]+ , [ bopInfo InR Impl ["==>", "implies"]+ ]+ ]+ where+ uopInfo fx op syns = OpInfo (UOpF fx op) syns (-1)+ bopInfo fx op syns = OpInfo (BOpF fx op) syns (-1)++ -- Start at precedence level 2 so we can give level 1 to ascription, and level 0+ -- to the ambient context + parentheses etc.+ assignPrecLevels table = zipWith assignPrecs (reverse [2 .. length table+1]) table+ assignPrecs p ops = map (assignPrec p) ops+ assignPrec p op = op { opPrec = p }++-- | A map from all unary operators to their associated 'OpInfo' records.+uopMap :: Map UOp OpInfo+uopMap = M.fromList $+ [ (op, info) | opLevel <- opTable, info@(OpInfo (UOpF _ op) _ _) <- opLevel ]++-- | A map from all binary operators to their associatied 'OpInfo' records.+bopMap :: Map BOp OpInfo+bopMap = M.fromList $+ [ (op, info) | opLevel <- opTable, info@(OpInfo (BOpF _ op) _ _) <- opLevel ]++-- | A convenient function for looking up the precedence of a unary operator.+uPrec :: UOp -> Int+uPrec = opPrec . (uopMap !)++-- | A convenient function for looking up the precedence of a binary operator.+bPrec :: BOp -> Int+bPrec = opPrec . (bopMap !)++-- | Look up the \"fixity\" (/i.e./ associativity) of a binary operator.+assoc :: BOp -> BFixity+assoc op =+ case M.lookup op bopMap of+ Just (OpInfo (BOpF fx _) _ _) -> fx+ _ -> error $ "BOp " ++ show op ++ " not in bopMap!"++-- | The precedence level of function application (higher than any+-- other precedence level).+funPrec :: Int+funPrec = length opTable+1
+ src/Disco/Syntax/Prims.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Syntax.Prims+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- Concrete syntax for the prims (i.e. built-in constants) supported+-- by the language.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Syntax.Prims+ ( Prim(..)+ , PrimInfo(..), primTable, toPrim, primMap, primDoc, primReference+ ) where++import GHC.Generics (Generic)+import Unbound.Generics.LocallyNameless++import Data.Map (Map)+import qualified Data.Map as M++import Data.Data (Data)+import Disco.Syntax.Operators+import Disco.Util ((==>))++------------------------------------------------------------+-- Prims+------------------------------------------------------------++-- | Primitives, /i.e./ built-in constants.+data Prim where+ PrimUOp :: UOp -> Prim -- ^ Unary operator+ PrimBOp :: BOp -> Prim -- ^ Binary operator++ PrimLeft :: Prim -- ^ Left injection into a sum type.+ PrimRight :: Prim -- ^ Right injection into a sum type.++ PrimSqrt :: Prim -- ^ Integer square root (@sqrt@)+ PrimFloor :: Prim -- ^ Floor of fractional type (@floor@)+ PrimCeil :: Prim -- ^ Ceiling of fractional type (@ceiling@)+ PrimAbs :: Prim -- ^ Absolute value (@abs@)++ PrimSize :: Prim -- ^ Size of a set (XXX should be in library)+ PrimPower :: Prim -- ^ Power set (XXX or bag?)++ PrimList :: Prim -- ^ Container -> list conversion+ PrimBag :: Prim -- ^ Container -> bag conversion+ PrimSet :: Prim -- ^ Container -> set conversion++ PrimB2C :: Prim -- ^ bag -> set of counts conversion+ PrimC2B :: Prim -- ^ set of counts -> bag conversion+ PrimMapToSet :: Prim -- ^ Map k v -> Set (k × v)+ PrimSetToMap :: Prim -- ^ Set (k × v) -> Map k v++ PrimSummary :: Prim -- ^ Get Adjacency list of Graph+ PrimVertex :: Prim -- ^ Construct a graph Vertex+ PrimEmptyGraph :: Prim -- ^ Empty graph+ PrimOverlay :: Prim -- ^ Overlay two Graphs+ PrimConnect :: Prim -- ^ Connect Graph to another with directed edges++ PrimInsert :: Prim -- ^ Insert into map+ PrimLookup :: Prim -- ^ Get value associated with key in map++ PrimEach :: Prim -- ^ Each operation for containers+ PrimReduce :: Prim -- ^ Reduce operation for containers+ PrimFilter :: Prim -- ^ Filter operation for containers+ PrimJoin :: Prim -- ^ Monadic join for containers+ PrimMerge :: Prim -- ^ Generic merge operation for bags/sets++ PrimIsPrime :: Prim -- ^ Efficient primality test+ PrimFactor :: Prim -- ^ Factorization+ PrimFrac :: Prim -- ^ Turn a rational into a pair (num, denom)++ PrimCrash :: Prim -- ^ Crash++ PrimUntil :: Prim -- ^ @[x, y, z .. e]@++ PrimHolds :: Prim -- ^ Test whether a proposition holds++ PrimLookupSeq :: Prim -- ^ Lookup OEIS sequence+ PrimExtendSeq :: Prim -- ^ Extend OEIS sequence+ deriving (Show, Read, Eq, Ord, Generic, Alpha, Subst t, Data)++------------------------------------------------------------+-- Concrete syntax for prims+------------------------------------------------------------++-- | An info record for a single primitive name, containing the+-- primitive itself, its concrete syntax, and whether it is+-- "exposed", /i.e./ available to be used in the surface syntax of+-- the basic language. Unexposed prims can only be referenced by+-- enabling the Primitives language extension and prefixing their+-- name by @$@.+data PrimInfo =+ PrimInfo+ { thePrim :: Prim+ , primSyntax :: String+ , primExposed :: Bool+ -- Is the prim available in the normal syntax of the language?+ --+ -- primExposed = True means that the bare primSyntax can be used+ -- in the surface syntax, and the prim will be pretty-printed as+ -- the primSyntax.+ --+ -- primExposed = False means that the only way to enter it is to+ -- enable the Primitives language extension and write a $+ -- followed by the primSyntax. The prim will be pretty-printed with a $+ -- prefix.+ --+ -- In no case is a prim a reserved word.+ }++-- | A table containing a 'PrimInfo' record for every non-operator+-- 'Prim' recognized by the language.+primTable :: [PrimInfo]+primTable =+ [ PrimInfo PrimLeft "left" True+ , PrimInfo PrimRight "right" True++ , PrimInfo (PrimUOp Not) "not" True+ , PrimInfo PrimSqrt "sqrt" True+ , PrimInfo PrimFloor "floor" True+ , PrimInfo PrimCeil "ceiling" True+ , PrimInfo PrimAbs "abs" True++ , PrimInfo PrimSize "size" True+ , PrimInfo PrimPower "power" True++ , PrimInfo PrimList "list" True+ , PrimInfo PrimBag "bag" True+ , PrimInfo PrimSet "set" True++ , PrimInfo PrimB2C "bagCounts" True+ , PrimInfo PrimC2B "bagFromCounts" True+ , PrimInfo PrimMapToSet "mapToSet" True+ , PrimInfo PrimSetToMap "map" True++ , PrimInfo PrimSummary "summary" True+ , PrimInfo PrimVertex "vertex" True+ , PrimInfo PrimEmptyGraph "emptyGraph" True+ , PrimInfo PrimOverlay "overlay" True+ , PrimInfo PrimConnect "connect" True++ , PrimInfo PrimInsert "insert" True+ , PrimInfo PrimLookup "lookup" True++ , PrimInfo PrimEach "each" True+ , PrimInfo PrimReduce "reduce" True+ , PrimInfo PrimFilter "filter" True+ , PrimInfo PrimJoin "join" False+ , PrimInfo PrimMerge "merge" False++ , PrimInfo PrimIsPrime "isPrime" False+ , PrimInfo PrimFactor "factor" False+ , PrimInfo PrimFrac "frac" False++ , PrimInfo PrimCrash "crash" False++ , PrimInfo PrimUntil "until" False++ , PrimInfo PrimHolds "holds" True++ , PrimInfo PrimLookupSeq "lookupSequence" False+ , PrimInfo PrimExtendSeq "extendSequence" False+ ]++-- | Find any exposed prims with the given name.+toPrim :: String -> [Prim]+toPrim x = [ p | PrimInfo p syn True <- primTable, syn == x ]++-- | A convenient map from each 'Prim' to its info record.+primMap :: Map Prim PrimInfo+primMap = M.fromList $+ [ (p, pinfo) | pinfo@(PrimInfo p _ _) <- primTable ]++-- | A map from some primitives to a short descriptive string,+-- to be shown by the :doc command.+primDoc :: Map Prim String+primDoc = M.fromList+ [ PrimUOp Neg ==> "Arithmetic negation."+ , PrimBOp Add ==> "The sum of two numbers, types, or graphs."+ , PrimBOp Sub ==> "The difference of two numbers."+ , PrimBOp SSub ==> "The difference of two numbers, with a lower bound of 0."+ , PrimBOp Mul ==> "The product of two numbers, types, or graphs."+ , PrimBOp Div ==> "Divide two numbers."+ , PrimBOp IDiv ==> "The integer quotient of two numbers, rounded down."+ , PrimBOp Mod ==> "a mod b is the remainder when a is divided by b."+ , PrimBOp Exp ==> "Exponentiation. a ^ b is a raised to the b power."+ , PrimUOp Fact ==> "n! computes the factorial of n, that is, 1 * 2 * ... * n."+ , PrimFloor ==> "floor(x) is the largest integer which is <= x."+ , PrimCeil ==> "ceiling(x) is the smallest integer which is >= x."+ , PrimAbs ==> "abs(x) is the absolute value of x. Also written |x|."+ , PrimUOp Not ==> "Logical negation: ¬true = false and ¬false = true. Also written 'not'."+ , PrimBOp And ==> "Logical conjunction (and): true and true = true; otherwise x and y = false."+ , PrimBOp Or ==> "Logical disjunction (or): false or false = false; otherwise x or y = true."+ , PrimBOp Impl ==> "Logical implication (implies): true ==> false = false; otherwise x ==> y = true."+ , PrimBOp Eq ==> "Equality test. x == y is true if x and y are equal."+ , PrimBOp Neq ==> "Inequality test. x /= y is true if x and y are unequal."+ , PrimBOp Lt ==> "Less-than test. x < y is true if x is less than (but not equal to) y."+ , PrimBOp Gt ==> "Greater-than test. x > y is true if x is greater than (but not equal to) y."+ , PrimBOp Leq ==> "Less-than-or-equal test. x <= y is true if x is less than or equal to y."+ , PrimBOp Geq ==> "Greater-than-or-equal test. x >= y is true if x is greater than or equal to y."++ ]++-- | A map from some primitives to their corresponding page in the+-- Disco language reference+-- (https://disco-lang.readthedocs.io/en/latest/reference/index.html).+primReference :: Map Prim String+primReference = M.fromList+ [ PrimBOp Add ==> "addition"+ , PrimBOp Sub ==> "subtraction"+ , PrimBOp SSub ==> "subtraction"+ , PrimBOp Mul ==> "multiplication"+ , PrimBOp Div ==> "division"+ , PrimBOp IDiv ==> "integerdiv"+ , PrimBOp Mod ==> "mod"+ , PrimBOp Exp ==> "exponentiation"+ , PrimUOp Fact ==> "factorial"+ , PrimFloor ==> "round"+ , PrimCeil ==> "round"+ , PrimAbs ==> "abs"+ , PrimUOp Not ==> "not"+ ]
+ src/Disco/Typecheck.hs view
@@ -0,0 +1,1604 @@+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE NondecreasingIndentation #-}+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Typecheck+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Typecheck the Disco surface language and transform it into a+-- type-annotated AST.+--+-----------------------------------------------------------------------------++module Disco.Typecheck where++import Control.Arrow ((&&&))+import Control.Lens ((^..))+import Control.Monad.Except+import Control.Monad.Trans.Maybe+import Data.Bifunctor (first)+import Data.Coerce+import qualified Data.Foldable as F+import Data.List (group, sort)+import Data.Map (Map)+import qualified Data.Map as M+import Data.Maybe (isJust)+import Data.Set (Set)+import qualified Data.Set as S+import Prelude as P hiding (lookup)++import Unbound.Generics.LocallyNameless (Alpha, Bind, Name,+ bind, embed,+ name2String,+ string2Name, substs,+ unembed)+import Unbound.Generics.LocallyNameless.Unsafe (unsafeUnbind)++import Disco.Effects.Fresh+import Polysemy hiding (embed)+import Polysemy.Error+import Polysemy.Output+import Polysemy.Reader+import Polysemy.Writer++import Disco.AST.Surface+import Disco.AST.Typed+import Disco.Context hiding (filter)+import qualified Disco.Context as Ctx+import Disco.Messages+import Disco.Module+import Disco.Names+import Disco.Subst (applySubst)+import qualified Disco.Subst as Subst+import Disco.Syntax.Operators+import Disco.Syntax.Prims+import Disco.Typecheck.Constraints+import Disco.Typecheck.Util+import Disco.Types+import Disco.Types.Rules++------------------------------------------------------------+-- Container utilities+------------------------------------------------------------++containerTy :: Container -> Type -> Type+containerTy c ty = TyCon (containerToCon c) [ty]++containerToCon :: Container -> Con+containerToCon ListContainer = CList+containerToCon BagContainer = CBag+containerToCon SetContainer = CSet++------------------------------------------------------------+-- Telescopes+------------------------------------------------------------++-- | Infer the type of a telescope, given a way to infer the type of+-- each item along with a context of variables it binds; each such+-- context is then added to the overall context when inferring+-- subsequent items in the telescope.+inferTelescope+ :: (Alpha b, Alpha tyb, Member (Reader TyCtx) r)+ => (b -> Sem r (tyb, TyCtx)) -> Telescope b -> Sem r (Telescope tyb, TyCtx)+inferTelescope inferOne tel = do+ (tel1, ctx) <- go (fromTelescope tel)+ return (toTelescope tel1, ctx)+ where+ go [] = return ([], emptyCtx)+ go (b:bs) = do+ (tyb, ctx) <- inferOne b+ extends ctx $ do+ (tybs, ctx') <- go bs+ return (tyb:tybs, ctx <> ctx')++------------------------------------------------------------+-- Modules+------------------------------------------------------------++-- | Check all the types and extract all relevant info (docs,+-- properties, types) from a module, returning a 'ModuleInfo' record+-- on success. This function does not handle imports at all; any+-- imports should already be checked and passed in as the second+-- argument.+checkModule+ :: Members '[Output Message, Reader TyCtx, Reader TyDefCtx, Error TCError, Fresh] r+ => ModuleName -> Map ModuleName ModuleInfo -> Module -> Sem r ModuleInfo+checkModule name imports (Module es _ m docs terms) = do+ let (typeDecls, defns, tydefs) = partitionDecls m+ importTyCtx = mconcat (imports ^.. traverse . miTys)+ -- XXX this isn't right, if multiple modules define the same type synonyms.+ -- Need to use a normal Ctx for tydefs too.+ importTyDefnCtx = M.unions (imports ^.. traverse . miTydefs)+ tyDefnCtx <- makeTyDefnCtx tydefs+ withTyDefns (tyDefnCtx `M.union` importTyDefnCtx) $ do+ tyCtx <- makeTyCtx name typeDecls+ extends importTyCtx $ extends tyCtx $ do+ mapM_ checkTyDefn tydefs+ adefns <- mapM (checkDefn name) defns+ let defnCtx = ctxForModule name (map (getDefnName &&& id) adefns)+ docCtx = ctxForModule name docs+ dups = filterDups . map getDefnName $ adefns+ case dups of+ (x:_) -> throw $ DuplicateDefns (coerce x)+ [] -> do+ aprops <- checkProperties docCtx+ aterms <- mapM inferTop terms+ return $ ModuleInfo name imports docCtx aprops tyCtx tyDefnCtx defnCtx aterms es+ where getDefnName :: Defn -> Name ATerm+ getDefnName (Defn n _ _ _) = n++--------------------------------------------------+-- Type definitions++-- | Turn a list of type definitions into a 'TyDefCtx', checking+-- for duplicate names among the definitions and also any type+-- definitions already in the context.+makeTyDefnCtx :: Members '[Reader TyDefCtx, Error TCError] r => [TypeDefn] -> Sem r TyDefCtx+makeTyDefnCtx tydefs = do+ oldTyDefs <- ask @TyDefCtx+ let oldNames = M.keys oldTyDefs+ newNames = map (\(TypeDefn x _ _) -> x) tydefs+ dups = filterDups $ newNames ++ oldNames++ let convert (TypeDefn x args body)+ = (x, TyDefBody args (flip substs body . zip (map string2Name args)))++ case dups of+ (x:_) -> throw (DuplicateTyDefns x)+ [] -> return . M.fromList $ map convert tydefs++-- | Check the validity of a type definition.+checkTyDefn :: Members '[Reader TyDefCtx, Error TCError] r => TypeDefn -> Sem r ()+checkTyDefn defn@(TypeDefn x args body) = do++ -- First, make sure the body is a valid type, i.e. everything inside+ -- it is well-kinded.+ checkTypeValid body++ -- Now make sure it is not directly cyclic (i.e. ensure it is a+ -- "productive" definition).+ _ <- checkCyclicTy (TyUser x (map (TyVar . string2Name) args)) S.empty++ -- Make sure it does not use any unbound type variables or undefined+ -- types.+ checkUnboundVars defn++ -- Make sure it does not use any polymorphic recursion (polymorphic+ -- recursion isn't allowed at the moment since it can make the+ -- subtyping checker diverge).+ checkPolyRec defn++-- | Check if a given type is cyclic. A type 'ty' is cyclic if:+--+-- 1. 'ty' is the name of a user-defined type.+-- 2. Repeated expansions of the type yield nothing but other user-defined types.+-- 3. An expansion of one of those types yields another type that has+-- been previously encountered.+--+-- In other words, repeatedly expanding the definition can get us+-- back to exactly where we started.+--+-- The function returns the set of TyDefs encountered during+-- expansion if the TyDef is not cyclic.+checkCyclicTy :: Members '[Reader TyDefCtx, Error TCError] r => Type -> Set String -> Sem r (Set String)+checkCyclicTy (TyUser name args) set = do+ case S.member name set of+ True -> throw $ CyclicTyDef name+ False -> do+ ty <- lookupTyDefn name args+ checkCyclicTy ty (S.insert name set)++checkCyclicTy _ set = return set++-- | Ensure that a type definition does not use any unbound type+-- variables or undefined types.+checkUnboundVars :: Members '[Reader TyDefCtx, Error TCError] r => TypeDefn -> Sem r ()+checkUnboundVars (TypeDefn _ args body) = go body+ where+ go (TyAtom (AVar (U x)))+ | name2String x `elem` args = return ()+ | otherwise = throw $ UnboundTyVar x+ go (TyAtom _) = return ()+ go (TyUser name tys) = lookupTyDefn name tys >> mapM_ go tys+ go (TyCon _ tys) = mapM_ go tys++-- | Check for polymorphic recursion: starting from a user-defined+-- type, keep expanding its definition recursively, ensuring that+-- any recursive references to the defined type have only type variables+-- as arguments.+checkPolyRec :: Member (Error TCError) r => TypeDefn -> Sem r ()+checkPolyRec (TypeDefn name args body) = go body+ where+ go (TyCon (CUser x) tys)+ | x == name && not (all isTyVar tys) =+ throw $ NoPolyRec name args tys+ | otherwise = return ()+ go (TyCon _ tys) = mapM_ go tys+ go _ = return ()++-- | Keep only the duplicate elements from a list.+--+-- >>> filterDups [1,3,2,1,1,4,2]+-- [1,2]+filterDups :: Ord a => [a] -> [a]+filterDups = map head . filter ((>1) . length) . group . sort++--------------------------------------------------+-- Type declarations++-- | Given a list of type declarations from a module, first check that+-- there are no duplicate type declarations, and that the types are+-- well-formed; then create a type context containing the given+-- declarations.+makeTyCtx :: Members '[Reader TyDefCtx, Error TCError] r => ModuleName -> [TypeDecl] -> Sem r TyCtx+makeTyCtx name decls = do+ let dups = filterDups . map (\(TypeDecl x _) -> x) $ decls+ case dups of+ (x:_) -> throw (DuplicateDecls x)+ [] -> do+ checkCtx declCtx+ return declCtx+ where+ declCtx = ctxForModule name $ map (\(TypeDecl x ty) -> (x,ty)) decls++-- | Check that all the types in a context are valid.+checkCtx :: Members '[Reader TyDefCtx, Error TCError] r => TyCtx -> Sem r ()+checkCtx = mapM_ checkPolyTyValid . Ctx.elems++--------------------------------------------------+-- Top-level definitions++-- | Type check a top-level definition in the given module.+checkDefn+ :: Members '[Reader TyCtx, Reader TyDefCtx, Error TCError, Fresh, Output Message] r+ => ModuleName -> TermDefn -> Sem r Defn+checkDefn name (TermDefn x clauses) = do++ -- Check that all clauses have the same number of patterns+ checkNumPats clauses++ -- Get the declared type signature of x+ Forall sig <- lookup (name .- x) >>= maybe (throw $ NoType x) return+ -- If x isn't in the context, it's because no type was declared for it, so+ -- throw an error.+ (nms, ty) <- unbind sig++ -- Try to decompose the type into a chain of arrows like pty1 ->+ -- pty2 -> pty3 -> ... -> bodyTy, according to the number of+ -- patterns, and lazily unrolling type definitions along the way.+ (patTys, bodyTy) <- decomposeDefnTy (numPats (head clauses)) ty++ ((acs, _), theta) <- solve $ do+ aclauses <- forAll nms $ mapM (checkClause patTys bodyTy) clauses+ return (aclauses, ty)++ return $ applySubst theta (Defn (coerce x) patTys bodyTy acs)+ where+ numPats = length . fst . unsafeUnbind++ checkNumPats [] = return () -- This can't happen, but meh+ checkNumPats [_] = return ()+ checkNumPats (c:cs)+ | all ((==0) . numPats) (c:cs) = throw (DuplicateDefns x)+ | not (all ((== numPats c) . numPats) cs) = throw NumPatterns+ -- XXX more info, this error actually means # of+ -- patterns don't match across different clauses+ | otherwise = return ()++ -- | Check a clause of a definition against a list of pattern types and a body type.+ checkClause+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => [Type] -> Type -> Bind [Pattern] Term -> Sem r Clause+ checkClause patTys bodyTy clause = do+ (pats, body) <- unbind clause++ -- At this point we know that every clause has the same number of patterns,+ -- which is the same as the length of the list patTys. So we can just use+ -- zipWithM to check all the patterns.+ (ctxs, aps) <- unzip <$> zipWithM checkPattern pats patTys+ at <- extends (mconcat ctxs) $ check body bodyTy+ return $ bind aps at++ -- Decompose a type that must be of the form t1 -> t2 -> ... -> tn -> t{n+1}.+ decomposeDefnTy :: Members '[Reader TyDefCtx, Error TCError] r => Int -> Type -> Sem r ([Type], Type)+ decomposeDefnTy 0 ty = return ([], ty)+ decomposeDefnTy n (TyUser tyName args) = lookupTyDefn tyName args >>= decomposeDefnTy n+ decomposeDefnTy n (ty1 :->: ty2) = first (ty1:) <$> decomposeDefnTy (n-1) ty2+ decomposeDefnTy _n _ty = throw NumPatterns+ -- XXX include more info. More argument patterns than arrows in the type.++--------------------------------------------------+-- Properties++-- | Given a context mapping names to documentation, extract the+-- properties attached to each name and typecheck them.+checkProperties+ :: Members '[Reader TyCtx, Reader TyDefCtx, Error TCError, Fresh, Output Message] r+ => Ctx Term Docs -> Sem r (Ctx ATerm [AProperty])+checkProperties docs =+ Ctx.coerceKeys . Ctx.filter (not . P.null)+ <$> (traverse . traverse) checkProperty properties+ where+ properties :: Ctx Term [Property]+ properties = fmap (\ds -> [p | DocProperty p <- ds]) docs++-- | Check the types of the terms embedded in a property.+checkProperty+ :: Members '[Reader TyCtx, Reader TyDefCtx, Error TCError, Fresh, Output Message] r+ => Property -> Sem r AProperty+checkProperty prop = do+ (at, theta) <- solve $ check prop TyProp+ -- XXX do we need to default container variables here?+ return $ applySubst theta at++------------------------------------------------------------+-- Type checking/inference+------------------------------------------------------------++--------------------------------------------------+-- Checking types/kinds+--------------------------------------------------++-- | Check that a sigma type is a valid type. See 'checkTypeValid'.+checkPolyTyValid :: Members '[Reader TyDefCtx, Error TCError] r => PolyType -> Sem r ()+checkPolyTyValid (Forall b) = do+ let (_, ty) = unsafeUnbind b+ checkTypeValid ty++-- | Disco doesn't need kinds per se, since all types must be fully+-- applied. But we do need to check that every type is applied to+-- the correct number of arguments.+checkTypeValid :: Members '[Reader TyDefCtx, Error TCError] r => Type -> Sem r ()+checkTypeValid (TyAtom _) = return ()+checkTypeValid (TyCon c tys) = do+ k <- conArity c+ if | n < k -> throw (NotEnoughArgs c)+ | n > k -> throw (TooManyArgs c)+ | otherwise -> mapM_ checkTypeValid tys+ where+ n = length tys++conArity :: Members '[Reader TyDefCtx, Error TCError] r => Con -> Sem r Int+conArity (CContainer _) = return 1+conArity CGraph = return 1+conArity (CUser name) = do+ d <- ask @TyDefCtx+ case M.lookup name d of+ Nothing -> throw (NotTyDef name)+ Just (TyDefBody as _) -> return (length as)+conArity _ = return 2 -- (->, *, +, map)++--------------------------------------------------+-- Checking modes+--------------------------------------------------++-- | Typechecking can be in one of two modes: inference mode means we+-- are trying to synthesize a valid type for a term; checking mode+-- means we are trying to show that a term has a given type.+data Mode = Infer | Check Type+ deriving Show++-- | Check that a term has the given type. Either throws an error, or+-- returns the term annotated with types for all subterms.+--+-- This function is provided for convenience; it simply calls+-- 'typecheck' with an appropriate 'Mode'.+check+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Term -> Type -> Sem r ATerm+check t ty = typecheck (Check ty) t++-- | Check that a term has the given polymorphic type.+checkPolyTy+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Term -> PolyType -> Sem r ATerm+checkPolyTy t (Forall sig) = do+ (as, tau) <- unbind sig+ (at, cst) <- withConstraint $ check t tau+ case as of+ [] -> constraint cst+ _ -> constraint $ CAll (bind as cst)+ return at++-- | Infer the type of a term. If it succeeds, it returns the term+-- with all subterms annotated.+--+-- This function is provided for convenience; it simply calls+-- 'typecheck' with an appropriate 'Mode'.+infer+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Term -> Sem r ATerm+infer = typecheck Infer++-- | Top-level type inference algorithm: infer a (polymorphic) type+-- for a term by running type inference, solving the resulting+-- constraints, and quantifying over any remaining type variables.+inferTop+ :: Members '[Output Message, Reader TyCtx, Reader TyDefCtx, Error TCError, Fresh] r+ => Term -> Sem r (ATerm, PolyType)+inferTop t = do++ -- Run inference on the term and try to solve the resulting+ -- constraints.+ (at, theta) <- solve $ infer t++ debug "Final annotated term (before substitution and container monomorphizing):"+ debugPretty at++ -- Apply the resulting substitution.+ let at' = applySubst theta at++ -- Find any remaining container variables.+ cvs = containerVars (getType at')++ -- Replace them all with List.+ at'' = applySubst (Subst.fromList $ zip (S.toList cvs) (repeat (TyAtom (ABase CtrList)))) at'++ -- Finally, quantify over any remaining type variables and return+ -- the term along with the resulting polymorphic type.+ return (at'', closeType (getType at''))++--------------------------------------------------+-- The typecheck function+--------------------------------------------------++-- | The main workhorse of the typechecker. Instead of having two+-- functions, one for inference and one for checking, 'typecheck'+-- takes a 'Mode'. This cuts down on code duplication in many+-- cases, and allows all the checking and inference code related to+-- a given AST node to be placed together.+typecheck+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Mode -> Term -> Sem r ATerm++-- ~~~~ Note [Pattern coverage]+-- In several places we have clauses like+--+-- inferPrim (PrimBOp op) | op `elem` [And, Or, Impl]+--+-- since the typing rules for all the given operators are the same.+-- The only problem is that the pattern coverage checker (sensibly)+-- doesn't look at guards in general, so it thinks that there are TBin+-- cases still uncovered.+--+-- However, we *don't* just want to add a catch-all case at the end,+-- because the coverage checker is super helpful in alerting us when+-- there's a missing typechecking case after modifying the language in+-- some way. The (not ideal) solution for now is to add some+-- additional explicit cases that simply call 'error', which will+-- never be reached but which assure the coverage checker that we have+-- handled those cases.+--+-- The ideal solution would be to use or-patterns, if Haskell had them+-- (see https://github.com/ghc-proposals/ghc-proposals/pull/43).++--------------------------------------------------+-- Defined types++-- To check at a user-defined type, expand its definition and recurse.+-- This case has to be first, so in all other cases we know the type+-- will not be a TyUser.+typecheck (Check (TyUser name args)) t = lookupTyDefn name args >>= check t++--------------------------------------------------+-- Parens++-- Recurse through parens; they are not represented explicitly in the+-- resulting ATerm.+typecheck mode (TParens t) = typecheck mode t++--------------------------------------------------+-- Variables++-- Resolve variable names and infer their types. We don't need a+-- checking case; checking the type of a variable will fall through to+-- this case.+typecheck Infer (TVar x) = do++ -- Pick the first method that succeeds; if none do, throw an unbound+ -- variable error.+ mt <- runMaybeT . F.asum . map MaybeT $ [tryLocal, tryModule, tryPrim]+ maybe (throw (Unbound x)) return mt+ where+ -- 1. See if the variable name is bound locally.+ tryLocal = do+ mty <- Ctx.lookup (localName x)+ case mty of+ Just (Forall sig) -> do+ (_, ty) <- unbind sig+ return . Just $ ATVar ty (localName (coerce x))+ Nothing -> return Nothing++ -- 2. See if the variable name is bound in some in-scope module,+ -- throwing an ambiguity error if it is bound in multiple modules.+ tryModule = do+ bs <- Ctx.lookupNonLocal x+ case bs of+ [(m,Forall sig)] -> do+ (_, ty) <- unbind sig+ return . Just $ ATVar ty (m .- coerce x)+ [] -> return Nothing+ _ -> throw $ Ambiguous x (map fst bs)++ -- 3. See if we should convert it to a primitive.+ tryPrim =+ case toPrim (name2String x) of+ (prim:_) -> Just <$> typecheck Infer (TPrim prim)+ _ -> return Nothing++--------------------------------------------------+-- Primitives++typecheck Infer (TPrim prim) = do+ ty <- inferPrim prim+ return $ ATPrim ty prim++ where+ inferPrim :: Members '[Writer Constraint, Fresh] r => Prim -> Sem r Type++ ----------------------------------------+ -- Left/right++ inferPrim PrimLeft = do+ a <- freshTy+ b <- freshTy+ return $ a :->: (a :+: b)++ inferPrim PrimRight = do+ a <- freshTy+ b <- freshTy+ return $ b :->: (a :+: b)++ ----------------------------------------+ -- Logic++ --- XXX restore typing rules for logical operations on Props+ --- once the evaluator can handle them.++ inferPrim (PrimBOp op) | op `elem` [And, Or, Impl] = do+ return $ TyBool :*: TyBool :->: TyBool+ -- a <- freshTy+ -- constraint $ CQual (bopQual op) a+ -- return $ a :*: a :->: a++ -- See Note [Pattern coverage] -----------------------------+ inferPrim (PrimBOp And) = error "inferPrim And should be unreachable"+ inferPrim (PrimBOp Or) = error "inferPrim Or should be unreachable"+ inferPrim (PrimBOp Impl) = error "inferPrim Impl should be unreachable"+ ------------------------------------------------------------++ inferPrim (PrimUOp Not) = do+ return $ TyBool :->: TyBool+ -- a <- freshTy+ -- constraint $ CQual QBool a+ -- return $ a :->: a++ ----------------------------------------+ -- Container conversion++ inferPrim conv | conv `elem` [PrimList, PrimBag, PrimSet] = do+ c <- freshAtom -- make a unification variable for the container type+ a <- freshTy -- make a unification variable for the element type++ -- converting to a set or bag requires being able to sort the elements+ when (conv /= PrimList) $ constraint $ CQual QCmp a++ return $ TyContainer c a :->: primCtrCon conv a++ where+ primCtrCon PrimList = TyList+ primCtrCon PrimBag = TyBag+ primCtrCon _ = TySet++ -- See Note [Pattern coverage] -----------------------------+ inferPrim PrimList = error "inferPrim PrimList should be unreachable"+ inferPrim PrimBag = error "inferPrim PrimBag should be unreachable"+ inferPrim PrimSet = error "inferPrim PrimSet should be unreachable"+ ------------------------------------------------------------++ inferPrim PrimB2C = do+ a <- freshTy+ return $ TyBag a :->: TySet (a :*: TyN)++ inferPrim PrimC2B = do+ a <- freshTy+ c <- freshAtom+ constraint $ CQual QCmp a+ return $ TyContainer c (a :*: TyN) :->: TyBag a++ inferPrim PrimMapToSet = do+ k <- freshTy+ v <- freshTy+ constraint $ CQual QSimple k+ return $ TyMap k v :->: TySet (k :*: v)++ inferPrim PrimSetToMap = do+ k <- freshTy+ v <- freshTy+ constraint $ CQual QSimple k+ return $ TySet (k :*: v) :->: TyMap k v++ inferPrim PrimSummary = do+ a <- freshTy+ constraint $ CQual QSimple a+ return $ TyGraph a :->: TyMap a (TySet a)++ inferPrim PrimVertex = do+ a <- freshTy+ constraint $ CQual QSimple a+ return $ a :->: TyGraph a++ inferPrim PrimEmptyGraph = do+ a <- freshTy+ constraint $ CQual QSimple a+ return $ TyGraph a++ inferPrim PrimOverlay = do+ a <- freshTy+ constraint $ CQual QSimple a+ return $ TyGraph a :*: TyGraph a :->: TyGraph a++ inferPrim PrimConnect = do+ a <- freshTy+ constraint $ CQual QSimple a+ return $ TyGraph a :*: TyGraph a :->: TyGraph a++ inferPrim PrimInsert = do+ a <- freshTy+ b <- freshTy+ constraint $ CQual QSimple a+ return $ a :*: b :*: TyMap a b :->: TyMap a b++ inferPrim PrimLookup = do+ a <- freshTy+ b <- freshTy+ constraint $ CQual QSimple a+ return $ a :*: TyMap a b :->: (TyUnit :+: b)+ ----------------------------------------+ -- Container primitives++ inferPrim (PrimBOp Cons) = do+ a <- freshTy+ return $ a :*: TyList a :->: TyList a++ -- XXX see https://github.com/disco-lang/disco/issues/160+ -- each : (a -> b) × c a -> c b+ inferPrim PrimEach = do+ c <- freshAtom+ a <- freshTy+ b <- freshTy+ return $ (a :->: b) :*: TyContainer c a :->: TyContainer c b++ -- XXX should eventually be (a * a -> a) * c a -> a,+ -- with a check that the function has the right properties.+ -- reduce : (a * a -> a) * a * c a -> a+ inferPrim PrimReduce = do+ c <- freshAtom+ a <- freshTy+ return $ (a :*: a :->: a) :*: a :*: TyContainer c a :->: a++ -- filter : (a -> Bool) × c a -> c a+ inferPrim PrimFilter = do+ c <- freshAtom+ a <- freshTy+ return $ (a :->: TyBool) :*: TyContainer c a :->: TyContainer c a++ -- join : c (c a) -> c a+ inferPrim PrimJoin = do+ c <- freshAtom+ a <- freshTy+ return $ TyContainer c (TyContainer c a) :->: TyContainer c a++ -- merge : (N × N -> N) × c a × c a -> c a (c = bag or set)+ inferPrim PrimMerge = do+ c <- freshAtom+ a <- freshTy+ constraint $ COr+ [ CEq (TyAtom (ABase CtrBag)) (TyAtom c)+ , CEq (TyAtom (ABase CtrSet)) (TyAtom c)+ ]+ let ca = TyContainer c a+ return $ (TyN :*: TyN :->: TyN) :*: ca :*: ca :->: ca++ inferPrim (PrimBOp setOp) | setOp `elem` [Union, Inter, Diff, Subset] = do+ a <- freshTy+ c <- freshAtom+ constraint $ COr+ [ CEq (TyAtom (ABase CtrBag)) (TyAtom c)+ , CEq (TyAtom (ABase CtrSet)) (TyAtom c)+ ]+ let ca = TyContainer c a+ let resTy = case setOp of {Subset -> TyBool; _ -> ca}+ return $ ca :*: ca :->: resTy++ -- See Note [Pattern coverage] -----------------------------+ inferPrim (PrimBOp Union) = error "inferPrim Union should be unreachable"+ inferPrim (PrimBOp Inter) = error "inferPrim Inter should be unreachable"+ inferPrim (PrimBOp Diff) = error "inferPrim Diff should be unreachable"+ inferPrim (PrimBOp Subset) = error "inferPrim Subset should be unreachable"+ ------------------------------------------------------------++ inferPrim (PrimBOp Elem) = do+ a <- freshTy+ c <- freshAtom++ constraint $ CQual QCmp a++ return $ a :*: TyContainer c a :->: TyBool++ ----------------------------------------+ -- Arithmetic++ inferPrim (PrimBOp IDiv) = do+ a <- freshTy+ resTy <- cInt a+ return $ a :*: a :->: resTy++ inferPrim (PrimBOp Mod) = do+ a <- freshTy+ constraint $ CSub a TyZ+ return $ a :*: a :->: a++ inferPrim (PrimBOp op) | op `elem` [Add, Mul, Sub, Div, SSub] = do+ a <- freshTy+ constraint $ CQual (bopQual op) a+ return $ a :*: a :->: a++ -- See Note [Pattern coverage] -----------------------------+ inferPrim (PrimBOp Add ) = error "inferPrim Add should be unreachable"+ inferPrim (PrimBOp Mul ) = error "inferPrim Mul should be unreachable"+ inferPrim (PrimBOp Sub ) = error "inferPrim Sub should be unreachable"+ inferPrim (PrimBOp Div ) = error "inferPrim Div should be unreachable"+ inferPrim (PrimBOp SSub) = error "inferPrim SSub should be unreachable"+ ------------------------------------------------------------++ inferPrim (PrimUOp Neg) = do+ a <- freshTy+ constraint $ CQual QSub a+ return $ a :->: a++ inferPrim (PrimBOp Exp) = do+ a <- freshTy+ b <- freshTy+ resTy <- cExp a b+ return $ a :*: b :->: resTy++ ----------------------------------------+ -- Number theory++ inferPrim PrimIsPrime = return $ TyN :->: TyBool+ inferPrim PrimFactor = return $ TyN :->: TyBag TyN++ inferPrim PrimFrac = return $ TyQ :->: (TyZ :*: TyN)++ inferPrim (PrimBOp Divides) = do+ a <- freshTy+ constraint $ CQual QNum a+ return $ a :*: a :->: TyBool++ ----------------------------------------+ -- Choose++ -- For now, a simple typing rule for multinomial coefficients that+ -- requires everything to be Nat. However, they can be extended to+ -- handle negative or fractional arguments.+ inferPrim (PrimBOp Choose) = do+ b <- freshTy++ -- b can be either Nat (a binomial coefficient)+ -- or a list of Nat (a multinomial coefficient).+ constraint $ COr [CEq b TyN, CEq b (TyList TyN)]+ return $ TyN :*: b :->: TyN++ ----------------------------------------+ -- Ellipses++ -- Actually 'until' supports more types than this, e.g. Q instead+ -- of N, but this is good enough. This case is here just for+ -- completeness---in case someone enables primitives and uses it+ -- directly---but typically 'until' is generated only during+ -- desugaring of a container with ellipsis, after typechecking, in+ -- which case it can be assigned a more appropriate type directly.++ inferPrim PrimUntil = return $ TyN :*: TyList TyN :->: TyList TyN++ ----------------------------------------+ -- Crash++ inferPrim PrimCrash = do+ a <- freshTy+ return $ TyString :->: a++ ----------------------------------------+ -- Propositions++ -- 'holds' converts a Prop into a Bool (but might not terminate).+ inferPrim PrimHolds = return $ TyProp :->: TyBool++ -- An equality assertion =!= is just like a comparison ==, except+ -- the result is a Prop.+ inferPrim (PrimBOp ShouldEq) = do+ ty <- freshTy+ constraint $ CQual QCmp ty+ return $ ty :*: ty :->: TyProp++ ----------------------------------------+ -- Comparisons++ -- Infer the type of a comparison. A comparison always has type+ -- Bool, but we have to make sure the subterms have compatible+ -- types. We also generate a QCmp qualifier, for two reasons:+ -- one, we need to know whether e.g. a comparison was done at a+ -- certain type, so we can decide whether the type is allowed to+ -- be completely polymorphic or not. Also, comparison of Props is+ -- not allowed.+ inferPrim (PrimBOp op) | op `elem` [Eq, Neq, Lt, Gt, Leq, Geq] = do+ ty <- freshTy+ constraint $ CQual QCmp ty+ return $ ty :*: ty :->: TyBool++ -- See Note [Pattern coverage] -----------------------------+ inferPrim (PrimBOp Eq) = error "inferPrim Eq should be unreachable"+ inferPrim (PrimBOp Neq) = error "inferPrim Neq should be unreachable"+ inferPrim (PrimBOp Lt) = error "inferPrim Lt should be unreachable"+ inferPrim (PrimBOp Gt) = error "inferPrim Gt should be unreachable"+ inferPrim (PrimBOp Leq) = error "inferPrim Leq should be unreachable"+ inferPrim (PrimBOp Geq) = error "inferPrim Geq should be unreachable"+ ------------------------------------------------------------++ inferPrim (PrimBOp op) | op `elem` [Min, Max] = do+ ty <- freshTy+ constraint $ CQual QCmp ty+ return $ ty :*: ty :->: ty++ -- See Note [Pattern coverage] -----------------------------+ inferPrim (PrimBOp Min) = error "inferPrim Min should be unreachable"+ inferPrim (PrimBOp Max) = error "inferPrim Max should be unreachable"+ ------------------------------------------------------------++ ----------------------------------------+ -- Special arithmetic functions: fact, sqrt, floor, ceil, abs++ inferPrim (PrimUOp Fact) = return $ TyN :->: TyN+ inferPrim PrimSqrt = return $ TyN :->: TyN++ inferPrim p | p `elem` [PrimFloor, PrimCeil] = do+ argTy <- freshTy+ resTy <- cInt argTy+ return $ argTy :->: resTy++ -- See Note [Pattern coverage] -----------------------------+ inferPrim PrimFloor = error "inferPrim Floor should be unreachable"+ inferPrim PrimCeil = error "inferPrim Ceil should be unreachable"+ ------------------------------------------------------------++ inferPrim PrimAbs = do+ argTy <- freshTy+ resTy <- cPos argTy+ return $ argTy :->: resTy++ ----------------------------------------+ -- set size, power set/bag++ -- XXX set size should move into standard library+ inferPrim PrimSize = do+ a <- freshTy+ return $ TySet a :->: TyN++ inferPrim PrimPower = do+ a <- freshTy+ c <- freshAtom++ constraint $ CQual QCmp a+ constraint $ COr+ [ CEq (TyAtom (ABase CtrSet)) (TyAtom c)+ , CEq (TyAtom (ABase CtrBag)) (TyAtom c)+ ]++ return $ TyContainer c a :->: TyContainer c (TyContainer c a)++ inferPrim PrimLookupSeq = return $ TyList TyN :->: (TyUnit :+: TyString)+ inferPrim PrimExtendSeq = return $ TyList TyN :->: TyList TyN++--------------------------------------------------+-- Base types++-- A few trivial cases for base types.+typecheck Infer TUnit = return ATUnit+typecheck Infer (TBool b) = return $ ATBool TyBool b+typecheck Infer (TChar c) = return $ ATChar c+typecheck Infer (TString cs) = return $ ATString cs+-- typecheck (Check (TyFin n)) (TNat x) = return $ ATNat (TyFin n) x+typecheck Infer (TNat n) = return $ ATNat TyN n+typecheck Infer (TRat r) = return $ ATRat r++typecheck _ TWild = throw NoTWild++--------------------------------------------------+-- Abstractions (lambdas and quantifiers)++-- Lambdas and quantifiers are similar enough that we can share a+-- bunch of the code, but their typing rules are a bit different. In+-- particular a lambda+--+-- \(x1:ty1), (x2:ty2) ... . body+--+-- is going to have a type like ty1 -> ty2 -> ... -> resTy, whereas a+-- quantifier like+--+-- ∃(x1:ty1), (x2:ty2) ... . body+--+-- is just going to have the type Prop. The similarity is that in+-- both cases we have to generate unification variables for any+-- binders with omitted type annotations, and typecheck the body under+-- an extended context.++-- It's only helpful to do lambdas in checking mode, since the+-- provided function type can provide information about the types of+-- the arguments. For other quantifiers we can just fall back to+-- inference mode.+typecheck (Check checkTy) tm@(TAbs Lam body) = do+ (args, t) <- unbind body++ -- First check that the given type is of the form ty1 -> ty2 ->+ -- ... -> resTy, where the types ty1, ty2 ... match up with any+ -- types declared for the arguments to the lambda (e.g. (x:tyA)+ -- (y:tyB) -> ...).+ (ctx, typedArgs, resTy) <- checkArgs args checkTy tm++ -- Then check the type of the body under a context extended with+ -- types for all the arguments.+ extends ctx $+ ATAbs Lam checkTy <$> (bind (coerce typedArgs) <$> check t resTy)++ where++ -- Given the patterns and their optional type annotations in the+ -- head of a lambda (e.g. @x (y:Z) (f : N -> N) -> ...@), and the+ -- type at which we are checking the lambda, ensure that:+ --+ -- - The type is of the form @ty1 -> ty2 -> ... -> resTy@ and+ -- there are enough @ty1@, @ty2@, ... to match all the arguments.+ -- - Each pattern successfully checks at its corresponding type.+ --+ -- If it succeeds, return a context binding variables to their+ -- types (as determined by the patterns and the input types) which+ -- we can use to extend when checking the body, a list of the typed+ -- patterns, and the result type of the function.+ checkArgs+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => [Pattern] -> Type -> Term -> Sem r (TyCtx, [APattern], Type)++ -- If we're all out of arguments, the remaining checking type is the+ -- result, and there are no variables to bind in the context.+ checkArgs [] ty _ = return (emptyCtx, [], ty)++ -- Take the next pattern and its annotation; the checking type must+ -- be a function type ty1 -> ty2.+ checkArgs (p : args) ty term = do++ -- Ensure that ty is a function type+ (ty1, ty2) <- ensureConstr2 CArr ty (Left term)++ -- Check the argument pattern against the function domain.+ (pCtx, pTyped) <- checkPattern p ty1++ -- Check the rest of the arguments under the type ty2, returning a+ -- context with the rest of the arguments and the final result type.+ (ctx, typedArgs, resTy) <- checkArgs args ty2 term++ -- Pass the result type through, and put the pattern-bound variables+ -- in the returned context.+ return (pCtx <> ctx, pTyped : typedArgs, resTy)++-- In inference mode, we handle lambdas as well as quantifiers (∀, ∃).+typecheck Infer (TAbs q lam) = do++ -- Open it and get the argument patterns with any type annotations.+ (args, t) <- unbind lam++ -- Replace any missing type annotations with fresh type variables,+ -- and check each pattern at that variable to refine them, collecting+ -- the types of each pattern's bound variables in a context.+ tys <- mapM getAscrOrFresh args+ (pCtxs, typedPats) <- unzip <$> zipWithM checkPattern args tys++ -- In the case of ∀, ∃, have to ensure that the argument types are+ -- searchable.+ when (q `elem` [All, Ex]) $+ -- What's the difference between this and `tys`? Nothing, after+ -- the solver runs, but right now the patterns might have a+ -- concrete type from annotations inside tuples.+ forM_ (map getType typedPats) $ \ty ->+ unless (isSearchable ty) $+ throw $ NoSearch ty++ -- Extend the context with the given arguments, and then do+ -- something appropriate depending on the quantifier.+ extends (mconcat pCtxs) $ do+ case q of+ -- For lambdas, infer the type of the body, and return an appropriate+ -- function type.+ Lam -> do+ at <- infer t+ return $ ATAbs Lam (mkFunTy tys (getType at)) (bind typedPats at)++ -- For other quantifiers, check that the body has type Prop,+ -- and return Prop.+ _ -> do -- ∀, ∃+ at <- check t TyProp+ return $ ATAbs q TyProp (bind typedPats at)+ where+ getAscrOrFresh+ :: Members '[Reader TyDefCtx, Error TCError, Fresh] r+ => Pattern -> Sem r Type+ getAscrOrFresh (PAscr _ ty) = checkTypeValid ty >> pure ty+ getAscrOrFresh _ = freshTy++ -- mkFunTy [ty1, ..., tyn] out = ty1 -> (ty2 -> ... (tyn -> out))+ mkFunTy :: [Type] -> Type -> Type+ mkFunTy tys out = foldr (:->:) out tys++--------------------------------------------------+-- Application++-- Infer the type of a function application by inferring the function+-- type and then checking the argument type. We don't need a checking+-- case because checking mode doesn't help.+typecheck Infer (TApp t t') = do+ at <- infer t+ let ty = getType at+ (ty1, ty2) <- ensureConstr2 CArr ty (Left t)+ ATApp ty2 at <$> check t' ty1++--------------------------------------------------+-- Tuples++-- Check/infer the type of a tuple.+typecheck mode1 (TTup tup) = uncurry ATTup <$> typecheckTuple mode1 tup+ where+ typecheckTuple+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Mode -> [Term] -> Sem r (Type, [ATerm])+ typecheckTuple _ [] = error "Impossible! typecheckTuple []"+ typecheckTuple mode [t] = (getType &&& (:[])) <$> typecheck mode t+ typecheckTuple mode (t:ts) = do+ (m,ms) <- ensureConstrMode2 CProd mode (Left $ TTup (t:ts))+ at <- typecheck m t+ (ty, ats) <- typecheckTuple ms ts+ return (getType at :*: ty, at : ats)++----------------------------------------+-- Comparison chain++typecheck Infer (TChain t ls) =+ ATChain TyBool <$> infer t <*> inferChain t ls++ where+ inferChain+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Term -> [Link] -> Sem r [ALink]+ inferChain _ [] = return []+ inferChain t1 (TLink op t2 : links) = do+ at2 <- infer t2+ _ <- check (TBin op t1 t2) TyBool+ atl <- inferChain t2 links+ return $ ATLink op at2 : atl++----------------------------------------+-- Type operations++typecheck Infer (TTyOp Enumerate t) = do+ checkTypeValid t+ return $ ATTyOp (TyList t) Enumerate t++typecheck Infer (TTyOp Count t) = do+ checkTypeValid t+ return $ ATTyOp (TyUnit :+: TyN) Count t++--------------------------------------------------+-- Containers++-- Literal containers, including ellipses+typecheck mode t@(TContainer c xs ell) = do+ eltMode <- ensureConstrMode1 (containerToCon c) mode (Left t)+ axns <- mapM (\(x,n) -> (,) <$> typecheck eltMode x <*> traverse (`check` TyN) n) xs+ aell <- typecheckEllipsis eltMode ell+ resTy <- case mode of+ Infer -> do+ let tys = [ getType at | Just (Until at) <- [aell] ] ++ map (getType . fst) axns+ tyv <- freshTy+ constraints $ map (`CSub` tyv) tys+ return $ containerTy c tyv+ Check ty -> return ty+ when (isJust ell) $ do+ eltTy <- getEltTy c resTy+ constraint $ CQual QEnum eltTy+ return $ ATContainer resTy c axns aell++ where+ typecheckEllipsis+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Mode -> Maybe (Ellipsis Term) -> Sem r (Maybe (Ellipsis ATerm))+ typecheckEllipsis _ Nothing = return Nothing+ typecheckEllipsis m (Just (Until tm)) = Just . Until <$> typecheck m tm++-- Container comprehensions+typecheck mode tcc@(TContainerComp c bqt) = do+ eltMode <- ensureConstrMode1 (containerToCon c) mode (Left tcc)+ (qs, t) <- unbind bqt+ (aqs, cx) <- inferTelescope inferQual qs+ extends cx $ do+ at <- typecheck eltMode t+ let resTy = case mode of+ Infer -> containerTy c (getType at)+ Check ty -> ty+ return $ ATContainerComp resTy c (bind aqs at)++ where+ inferQual+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Qual -> Sem r (AQual, TyCtx)+ inferQual (QBind x (unembed -> t)) = do+ at <- infer t+ ty <- ensureConstr1 (containerToCon c) (getType at) (Left t)+ return (AQBind (coerce x) (embed at), singleCtx (localName x) (toPolyType ty))++ inferQual (QGuard (unembed -> t)) = do+ at <- check t TyBool+ return (AQGuard (embed at), emptyCtx)++--------------------------------------------------+-- Let++-- To check/infer a let expression. Note let is non-recursive.+typecheck mode (TLet l) = do+ (bs, t2) <- unbind l++ -- Infer the types of all the variables bound by the let...+ (as, ctx) <- inferTelescope inferBinding bs++ -- ...then check/infer the body under an extended context.+ extends ctx $ do+ at2 <- typecheck mode t2+ return $ ATLet (getType at2) (bind as at2)++ where++ -- Infer the type of a binding (@x [: ty] = t@), returning a+ -- type-annotated binding along with a (singleton) context for the+ -- bound variable. The optional type annotation on the variable+ -- determines whether we use inference or checking mode for the+ -- body.+ inferBinding+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Binding -> Sem r (ABinding, TyCtx)+ inferBinding (Binding mty x (unembed -> t)) = do+ at <- case mty of+ Just (unembed -> ty) -> checkPolyTy t ty+ Nothing -> infer t+ return (ABinding mty (coerce x) (embed at), singleCtx (localName x) (toPolyType $ getType at))++--------------------------------------------------+-- Case++-- Check/infer a case expression.+typecheck _ (TCase []) = throw EmptyCase+typecheck mode (TCase bs) = do+ bs' <- mapM typecheckBranch bs+ resTy <- case mode of+ Check ty -> return ty+ Infer -> do+ x <- freshTy+ constraints $ map ((`CSub` x) . getType) bs'+ return x+ return $ ATCase resTy bs'++ where+ typecheckBranch+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Branch -> Sem r ABranch+ typecheckBranch b = do+ (gs, t) <- unbind b+ (ags, ctx) <- inferTelescope inferGuard gs+ extends ctx $+ bind ags <$> typecheck mode t++ -- Infer the type of a guard, returning the type-annotated guard+ -- along with a context of types for any variables bound by the+ -- guard.+ inferGuard+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Guard -> Sem r (AGuard, TyCtx)+ inferGuard (GBool (unembed -> t)) = do+ at <- check t TyBool+ return (AGBool (embed at), emptyCtx)+ inferGuard (GPat (unembed -> t) p) = do+ at <- infer t+ (ctx, apt) <- checkPattern p (getType at)+ return (AGPat (embed at) apt, ctx)+ inferGuard (GLet (Binding mty x (unembed -> t))) = do+ at <- case mty of+ Just (unembed -> ty) -> checkPolyTy t ty+ Nothing -> infer t+ return+ ( AGLet (ABinding mty (coerce x) (embed at))+ , singleCtx (localName x) (toPolyType (getType at))+ )++--------------------------------------------------+-- Type ascription++-- Ascriptions are what let us flip from inference mode into+-- checking mode.+typecheck Infer (TAscr t ty) = checkPolyTyValid ty >> checkPolyTy t ty++--------------------------------------------------+-- Inference fallback++-- Finally, to check anything else, we can fall back to inferring its+-- type and then check that the inferred type is a *subtype* of the+-- given type. We have to be careful to call 'setType' to change the+-- type at the root of the term to the requested type.+typecheck (Check ty) t = do+ at <- infer t+ constraint $ CSub (getType at) ty+ return $ setType ty at++------------------------------------------------------------+-- Patterns+------------------------------------------------------------++-- | Check that a pattern has the given type, and return a context of+-- pattern variables bound in the pattern along with their types.+checkPattern+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Pattern -> Type -> Sem r (TyCtx, APattern)++checkPattern p (TyUser name args) = lookupTyDefn name args >>= checkPattern p++checkPattern (PVar x) ty = return (singleCtx (localName x) (toPolyType ty), APVar ty (coerce x))++checkPattern PWild ty = return (emptyCtx, APWild ty)++checkPattern (PAscr p ty1) ty2 = do+ -- We have a pattern that promises to match ty1 and someone is asking+ -- us if it can also match ty2. So we just have to ensure what we're+ -- being asked for is a subtype of what we can promise to cover...+ constraint $ CSub ty2 ty1+ -- ... and then make sure the pattern can actually match what it promised to.+ checkPattern p ty1++checkPattern PUnit ty = do+ ensureEq ty TyUnit+ return (emptyCtx, APUnit)++checkPattern (PBool b) ty = do+ ensureEq ty TyBool+ return (emptyCtx, APBool b)++checkPattern (PChar c) ty = do+ ensureEq ty TyC+ return (emptyCtx, APChar c)++checkPattern (PString s) ty = do+ ensureEq ty TyString+ return (emptyCtx, APString s)++checkPattern (PTup tup) tupTy = do+ listCtxtAps <- checkTuplePat tup tupTy+ let (ctxs, aps) = unzip listCtxtAps+ return (mconcat ctxs, APTup (foldr1 (:*:) (map getType aps)) aps)++ where+ checkTuplePat+ :: Members '[Reader TyCtx, Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => [Pattern] -> Type -> Sem r [(TyCtx, APattern)]+ checkTuplePat [] _ = error "Impossible! checkTuplePat []"+ checkTuplePat [p] ty = do -- (:[]) <$> check t ty+ (ctx, apt) <- checkPattern p ty+ return [(ctx, apt)]+ checkTuplePat (p:ps) ty = do+ (ty1, ty2) <- ensureConstr2 CProd ty (Right $ PTup (p:ps))+ (ctx, apt) <- checkPattern p ty1+ rest <- checkTuplePat ps ty2+ return ((ctx, apt) : rest)++checkPattern p@(PInj L pat) ty = do+ (ty1, ty2) <- ensureConstr2 CSum ty (Right p)+ (ctx, apt) <- checkPattern pat ty1+ return (ctx, APInj (ty1 :+: ty2) L apt)+checkPattern p@(PInj R pat) ty = do+ (ty1, ty2) <- ensureConstr2 CSum ty (Right p)+ (ctx, apt) <- checkPattern pat ty2+ return (ctx, APInj (ty1 :+: ty2) R apt)++-- we can match any supertype of TyN against a Nat pattern, OR+-- any TyFin.++-- XXX this isn't quite right, what if we're checking at a type+-- variable but we need to solve it to be a TyFin? Can this ever+-- happen? We would need a COr, except we can't express the+-- constraint "exists m. ty = TyFin m"+--+-- Yes, this can happen, and here's an example:+--+-- > (\x. {? true when x is 3, false otherwise ?}) (2 : Z5)+-- Unsolvable NoUnify+-- > (\(x : Z5). {? true when x is 3, false otherwise ?}) (2 : Z5)+-- false++-- checkPattern (PNat n) (TyFin m) = return (emptyCtx, APNat (TyFin m) n)+checkPattern (PNat n) ty = do+ constraint $ CSub TyN ty+ return (emptyCtx, APNat ty n)++checkPattern p@(PCons p1 p2) ty = do+ tyl <- ensureConstr1 CList ty (Right p)+ (ctx1, ap1) <- checkPattern p1 tyl+ (ctx2, ap2) <- checkPattern p2 (TyList tyl)+ return (ctx1 <> ctx2, APCons (TyList tyl) ap1 ap2)++checkPattern p@(PList ps) ty = do+ tyl <- ensureConstr1 CList ty (Right p)+ listCtxtAps <- mapM (`checkPattern` tyl) ps+ let (ctxs, aps) = unzip listCtxtAps+ return (mconcat ctxs, APList (TyList tyl) aps)++checkPattern (PAdd s p t) ty = do+ constraint $ CQual QNum ty+ (ctx, apt) <- checkPattern p ty+ at <- check t ty+ return (ctx, APAdd ty s apt at)++checkPattern (PMul s p t) ty = do+ constraint $ CQual QNum ty+ (ctx, apt) <- checkPattern p ty+ at <- check t ty+ return (ctx, APMul ty s apt at)++checkPattern (PSub p t) ty = do+ constraint $ CQual QNum ty+ (ctx, apt) <- checkPattern p ty+ at <- check t ty+ return (ctx, APSub ty apt at)++checkPattern (PNeg p) ty = do+ constraint $ CQual QSub ty+ tyInner <- cPos ty+ (ctx, apt) <- checkPattern p tyInner+ return (ctx, APNeg ty apt)++checkPattern (PFrac p q) ty = do+ constraint $ CQual QDiv ty+ tyP <- cInt ty+ tyQ <- cPos tyP+ (ctx1, ap1) <- checkPattern p tyP+ (ctx2, ap2) <- checkPattern q tyQ+ return (ctx1 <> ctx2, APFrac ty ap1 ap2)++------------------------------------------------------------+-- Constraints for abs, floor/ceiling/idiv, and exp+------------------------------------------------------------++-- | Given an input type @ty@, return a type which represents the+-- output type of the absolute value function, and generate+-- appropriate constraints.+cPos :: Members '[Writer Constraint, Fresh] r => Type -> Sem r Type+cPos ty = do+ constraint $ CQual QNum ty -- The input type has to be numeric.+ case ty of+ -- If the input type is a concrete base type, we can just+ -- compute the correct output type.+ TyAtom (ABase b) -> return $ TyAtom (ABase (pos b))++ -- Otherwise, generate a fresh type variable for the output type+ -- along with some constraints.+ _ -> do+ res <- freshTy++ -- Valid types for absolute value are Z -> N, Q -> F, or T -> T+ -- (e.g. Z5 -> Z5).+ constraint $ COr+ [ cAnd [CSub ty TyZ, CSub TyN res]+ , cAnd [CSub ty TyQ, CSub TyF res]+ , CEq ty res+ ]+ return res+ where+ pos Z = N+ pos Q = F+ pos t = t++-- | Given an input type @ty@, return a type which represents the+-- output type of the floor or ceiling functions, and generate+-- appropriate constraints.+cInt :: Members '[Writer Constraint, Fresh] r => Type -> Sem r Type+cInt ty = do+ constraint $ CQual QNum ty+ case ty of+ -- If the input type is a concrete base type, we can just+ -- compute the correct output type.+ TyAtom (ABase b) -> return $ TyAtom (ABase (int b))++ -- Otherwise, generate a fresh type variable for the output type+ -- along with some constraints.+ _ -> do+ res <- freshTy++ -- Valid types for absolute value are F -> N, Q -> Z, or T -> T+ -- (e.g. Z5 -> Z5).+ constraint $ COr+ [ cAnd [CSub ty TyF, CSub TyN res]+ , cAnd [CSub ty TyQ, CSub TyZ res]+ , CEq ty res+ ]+ return res++ where+ int F = N+ int Q = Z+ int t = t++-- | Given input types to the exponentiation operator, return a type+-- which represents the output type, and generate appropriate+-- constraints.+cExp :: Members '[Writer Constraint, Fresh] r => Type -> Type -> Sem r Type+cExp ty1 TyN = do+ constraint $ CQual QNum ty1+ return ty1++-- We could include a special case for TyZ, but for that we would need+-- a function to find a supertype of a given type that satisfies QDiv.++cExp ty1 ty2 = do++ -- Create a fresh type variable to represent the result type. The+ -- base type has to be a subtype.+ resTy <- freshTy+ constraint $ CSub ty1 resTy++ -- Either the exponent type is N, in which case the result type has+ -- to support multiplication, or else the exponent is Z, in which+ -- case the result type also has to support division.+ constraint $ COr+ [ cAnd [CQual QNum resTy, CEq ty2 TyN]+ , cAnd [CQual QDiv resTy, CEq ty2 TyZ]+ ]+ return resTy++------------------------------------------------------------+-- Decomposing type constructors+------------------------------------------------------------++-- | Get the argument (element) type of a (known) container type. Returns a+-- fresh variable with a suitable constraint if the given type is+-- not literally a container type.+getEltTy :: Members '[Writer Constraint, Fresh] r => Container -> Type -> Sem r Type+getEltTy _ (TyContainer _ e) = return e+getEltTy c ty = do+ eltTy <- freshTy+ constraint $ CEq (containerTy c eltTy) ty+ return eltTy++-- | Ensure that a type's outermost constructor matches the provided+-- constructor, returning the types within the matched constructor+-- or throwing a type error. If the type provided is a type+-- variable, appropriate constraints are generated to guarantee the+-- type variable's outermost constructor matches the provided+-- constructor, and a list of fresh type variables is returned whose+-- count matches the arity of the provided constructor.+ensureConstr+ :: forall r. Members '[Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Con -> Type -> Either Term Pattern -> Sem r [Type]+ensureConstr c ty targ = matchConTy c ty+ where+ matchConTy :: Con -> Type -> Sem r [Type]++ -- expand type definitions lazily+ matchConTy c1 (TyUser name args) = lookupTyDefn name args >>= matchConTy c1++ matchConTy c1 (TyCon c2 tys) = do+ matchCon c1 c2+ return tys++ matchConTy c1 tyv@(TyAtom (AVar (U _))) = do+ tyvs <- mapM (const freshTy) (arity c1)+ constraint $ CEq tyv (TyCon c1 tyvs)+ return tyvs++ matchConTy _ _ = matchError++ -- | Check whether two constructors match, which could include+ -- unifying container variables if we are matching two container+ -- types; otherwise, simply ensure that the constructors are+ -- equal. Throw a 'matchError' if they do not match.+ matchCon :: Con -> Con -> Sem r ()+ matchCon c1 c2 | c1 == c2 = return ()+ matchCon (CContainer v@(AVar (U _))) (CContainer ctr2) =+ constraint $ CEq (TyAtom v) (TyAtom ctr2)+ matchCon (CContainer ctr1) (CContainer v@(AVar (U _))) =+ constraint $ CEq (TyAtom ctr1) (TyAtom v)+ matchCon _ _ = matchError++ matchError :: Sem r a+ matchError = case targ of+ Left term -> throw (NotCon c term ty)+ Right pat -> throw (PatternType c pat ty)++-- | A variant of ensureConstr that expects to get exactly one+-- argument type out, and throws an error if we get any other+-- number.+ensureConstr1+ :: Members '[Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Con -> Type -> Either Term Pattern -> Sem r Type+ensureConstr1 c ty targ = do+ tys <- ensureConstr c ty targ+ case tys of+ [ty1] -> return ty1+ _ -> error $+ "Impossible! Wrong number of arg types in ensureConstr1 " ++ show c ++ " "+ ++ show ty ++ ": " ++ show tys++-- | A variant of ensureConstr that expects to get exactly two+-- argument types out, and throws an error if we get any other+-- number.+ensureConstr2+ :: Members '[Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Con -> Type -> Either Term Pattern -> Sem r (Type, Type)+ensureConstr2 c ty targ = do+ tys <- ensureConstr c ty targ+ case tys of+ [ty1, ty2] -> return (ty1, ty2)+ _ -> error $+ "Impossible! Wrong number of arg types in ensureConstr2 " ++ show c ++ " "+ ++ show ty ++ ": " ++ show tys++-- | A variant of 'ensureConstr' that works on 'Mode's instead of+-- 'Type's. Behaves similarly to 'ensureConstr' if the 'Mode' is+-- 'Check'; otherwise it generates an appropriate number of copies+-- of 'Infer'.+ensureConstrMode+ :: Members '[Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Con -> Mode -> Either Term Pattern -> Sem r [Mode]+ensureConstrMode c Infer _ = return $ map (const Infer) (arity c)+ensureConstrMode c (Check ty) tp = map Check <$> ensureConstr c ty tp++-- | A variant of 'ensureConstrMode' that expects to get a single+-- 'Mode' and throws an error if it encounters any other number.+ensureConstrMode1+ :: Members '[Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Con -> Mode -> Either Term Pattern -> Sem r Mode+ensureConstrMode1 c m targ = do+ ms <- ensureConstrMode c m targ+ case ms of+ [m1] -> return m1+ _ -> error $+ "Impossible! Wrong number of arg types in ensureConstrMode1 " ++ show c ++ " "+ ++ show m ++ ": " ++ show ms++-- | A variant of 'ensureConstrMode' that expects to get two 'Mode's+-- and throws an error if it encounters any other number.+ensureConstrMode2+ :: Members '[Reader TyDefCtx, Writer Constraint, Error TCError, Fresh] r+ => Con -> Mode -> Either Term Pattern -> Sem r (Mode, Mode)+ensureConstrMode2 c m targ = do+ ms <- ensureConstrMode c m targ+ case ms of+ [m1, m2] -> return (m1, m2)+ _ -> error $+ "Impossible! Wrong number of arg types in ensureConstrMode2 " ++ show c ++ " "+ ++ show m ++ ": " ++ show ms++-- | Ensure that two types are equal:+-- 1. Do nothing if they are literally equal+-- 2. Generate an equality constraint otherwise+ensureEq :: Member (Writer Constraint) r => Type -> Type -> Sem r ()+ensureEq ty1 ty2+ | ty1 == ty2 = return ()+ | otherwise = constraint $ CEq ty1 ty2
+ src/Disco/Typecheck/Constraints.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Typecheck.Constraints+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Constraints generated by type inference & checking.+--+-----------------------------------------------------------------------------++module Disco.Typecheck.Constraints+ ( Constraint(..)+ , cAnd+ )+ where++import qualified Data.List.NonEmpty as NE+import Data.Semigroup+import GHC.Generics (Generic)+import Unbound.Generics.LocallyNameless hiding (lunbind)++import Disco.Effects.LFresh++import Disco.Pretty hiding ((<>))+import Disco.Syntax.Operators (BFixity (In, InL, InR))+import Disco.Types+import Disco.Types.Rules++-- | Constraints are generated as a result of type inference and checking.+-- These constraints are accumulated during the inference and checking phase+-- and are subsequently solved by the constraint solver.+data Constraint where+ CSub :: Type -> Type -> Constraint+ CEq :: Type -> Type -> Constraint+ CQual :: Qualifier -> Type -> Constraint+ CAnd :: [Constraint] -> Constraint+ CTrue :: Constraint+ COr :: [Constraint] -> Constraint+ CAll :: Bind [Name Type] Constraint -> Constraint++ deriving (Show, Generic, Alpha, Subst Type)++instance Pretty Constraint where+ pretty = \case+ CSub ty1 ty2 -> withPA (PA 4 In) $ lt (pretty ty1) <+> "<:" <+> rt (pretty ty2)+ CEq ty1 ty2 -> withPA (PA 4 In) $ lt (pretty ty1) <+> "=" <+> rt (pretty ty2)+ CQual q ty -> withPA (PA 10 InL) $ lt (pretty q) <+> rt (pretty ty)+ CAnd [c] -> pretty c+ -- Use rt for both, since we don't need to print parens for /\ at all+ CAnd (c:cs) -> withPA (PA 3 InR) $ rt (pretty c) <+> "/\\" <+> rt (pretty (CAnd cs))+ CAnd [] -> "True"+ CTrue -> "True"+ COr [c] -> pretty c+ COr (c:cs) -> withPA (PA 2 InR) $ lt (pretty c) <+> "\\/" <+> rt (pretty (COr cs))+ COr [] -> "False"+ CAll b -> lunbind b $ \(xs, c) ->+ "∀" <+> intercalate "," (map pretty xs) <> "." <+> pretty c++-- A helper function for creating a single constraint from a list of constraints.+cAnd :: [Constraint] -> Constraint+cAnd cs = case filter nontrivial cs of+ [] -> CTrue+ [c] -> c+ cs' -> CAnd cs'+ where+ nontrivial CTrue = False+ nontrivial _ = True++instance Semigroup Constraint where+ c1 <> c2 = cAnd [c1,c2]+ sconcat = cAnd . NE.toList+ stimes = stimesIdempotent++instance Monoid Constraint where+ mempty = CTrue+ mappend = (<>)+ mconcat = cAnd
+ src/Disco/Typecheck/Erase.hs view
@@ -0,0 +1,123 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Typecheck.Erase+-- Copyright : (c) 2016 disco team (see LICENSE)+-- License : BSD-style (see LICENSE)+-- Maintainer : byorgey@gmail.com+--+-- Typecheck the Disco surface language and transform it into a+-- type-annotated AST.+--+-----------------------------------------------------------------------------++module Disco.Typecheck.Erase where++import Unbound.Generics.LocallyNameless+import Unbound.Generics.LocallyNameless.Unsafe++import Control.Arrow ((***))+import Data.Coerce++import Disco.AST.Desugared+import Disco.AST.Surface+import Disco.AST.Typed+import Disco.Names (QName (..))++-- | Erase all the type annotations from a term.+erase :: ATerm -> Term+erase (ATVar _ (QName _ x)) = TVar (coerce x)+erase (ATPrim _ x) = TPrim x+erase (ATLet _ bs) = TLet $ bind (mapTelescope eraseBinding tel) (erase at)+ where (tel,at) = unsafeUnbind bs+erase ATUnit = TUnit+erase (ATBool _ b) = TBool b+erase (ATChar c) = TChar c+erase (ATString s) = TString s+erase (ATNat _ i) = TNat i+erase (ATRat r) = TRat r+erase (ATAbs q _ b) = TAbs q $ bind (map erasePattern x) (erase at)+ where (x,at) = unsafeUnbind b+erase (ATApp _ t1 t2) = TApp (erase t1) (erase t2)+erase (ATTup _ ats) = TTup (map erase ats)+erase (ATCase _ brs) = TCase (map eraseBranch brs)+erase (ATChain _ at lnks) = TChain (erase at) (map eraseLink lnks)+erase (ATTyOp _ op ty) = TTyOp op ty+erase (ATContainer _ c ats aell) = TContainer c (map (erase *** fmap erase) ats) ((fmap . fmap) erase aell)+erase (ATContainerComp _ c b) = TContainerComp c $ bind (mapTelescope eraseQual tel) (erase at)+ where (tel,at) = unsafeUnbind b+erase (ATTest _ x) = erase x++eraseBinding :: ABinding -> Binding+eraseBinding (ABinding mty x (unembed -> at)) = Binding mty (coerce x) (embed (erase at))++erasePattern :: APattern -> Pattern+erasePattern (APVar _ n) = PVar (coerce n)+erasePattern (APWild _) = PWild+erasePattern APUnit = PUnit+erasePattern (APBool b) = PBool b+erasePattern (APChar c) = PChar c+erasePattern (APString s) = PString s+erasePattern (APTup _ alp) = PTup $ map erasePattern alp+erasePattern (APInj _ s apt) = PInj s (erasePattern apt)+erasePattern (APNat _ n) = PNat n+erasePattern (APCons _ ap1 ap2) = PCons (erasePattern ap1) (erasePattern ap2)+erasePattern (APList _ alp) = PList $ map erasePattern alp+erasePattern (APAdd _ s p t) = PAdd s (erasePattern p) (erase t)+erasePattern (APMul _ s p t) = PMul s (erasePattern p) (erase t)+erasePattern (APSub _ p t) = PSub (erasePattern p) (erase t)+erasePattern (APNeg _ p) = PNeg (erasePattern p)+erasePattern (APFrac _ p1 p2) = PFrac (erasePattern p1) (erasePattern p2)++eraseBranch :: ABranch -> Branch+eraseBranch b = bind (mapTelescope eraseGuard tel) (erase at)+ where (tel,at) = unsafeUnbind b++eraseGuard :: AGuard -> Guard+eraseGuard (AGBool (unembed -> at)) = GBool (embed (erase at))+eraseGuard (AGPat (unembed -> at) p) = GPat (embed (erase at)) (erasePattern p)+eraseGuard (AGLet b) = GLet (eraseBinding b)++eraseLink :: ALink -> Link+eraseLink (ATLink bop at) = TLink bop (erase at)++eraseQual :: AQual -> Qual+eraseQual (AQBind x (unembed -> at)) = QBind (coerce x) (embed (erase at))+eraseQual (AQGuard (unembed -> at)) = QGuard (embed (erase at))++eraseProperty :: AProperty -> Property+eraseProperty = erase++------------------------------------------------------------+-- DTerm erasure++eraseDTerm :: DTerm -> Term+eraseDTerm (DTVar _ (QName _ x)) = TVar (coerce x)+eraseDTerm (DTPrim _ x) = TPrim x+eraseDTerm DTUnit = TUnit+eraseDTerm (DTBool _ b) = TBool b+eraseDTerm (DTChar c) = TChar c+eraseDTerm (DTNat _ n) = TNat n+eraseDTerm (DTRat r) = TRat r+eraseDTerm (DTAbs q _ b) = TAbs q $ bind [PVar . coerce $ x] (eraseDTerm dt)+ where (x, dt) = unsafeUnbind b+eraseDTerm (DTApp _ d1 d2) = TApp (eraseDTerm d1) (eraseDTerm d2)+eraseDTerm (DTPair _ d1 d2) = TTup [eraseDTerm d1, eraseDTerm d2]+eraseDTerm (DTCase _ bs) = TCase (map eraseDBranch bs)+eraseDTerm (DTTyOp _ op ty) = TTyOp op ty+eraseDTerm (DTNil _) = TList [] Nothing+eraseDTerm (DTTest _ x) = eraseDTerm x++eraseDBranch :: DBranch -> Branch+eraseDBranch b = bind (mapTelescope eraseDGuard tel) (eraseDTerm d)+ where+ (tel, d) = unsafeUnbind b++eraseDGuard :: DGuard -> Guard+eraseDGuard (DGPat (unembed -> d) p) = GPat (embed (eraseDTerm d)) (eraseDPattern p)++eraseDPattern :: DPattern -> Pattern+eraseDPattern (DPVar _ x) = PVar (coerce x)+eraseDPattern (DPWild _) = PWild+eraseDPattern DPUnit = PUnit+eraseDPattern (DPPair _ x1 x2) = PTup (map (PVar . coerce) [x1,x2])+eraseDPattern (DPInj _ s x) = PInj s (PVar (coerce x))
+ src/Disco/Typecheck/Graph.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Typecheck.Graph+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- A thin layer on top of graphs from the @fgl@ package, which+-- allows dealing with vertices by label instead of by integer+-- @Node@ values.+-----------------------------------------------------------------------------++module Disco.Typecheck.Graph where++import Prelude hiding (map, (<>))+import qualified Prelude as P++import Control.Arrow ((&&&))+import Data.Map (Map)+import qualified Data.Map as M+import Data.Maybe (fromJust, isJust, mapMaybe)+import Data.Set (Set)+import qualified Data.Set as S+import Data.Tuple (swap)++import qualified Data.Graph.Inductive.Graph as G+import Data.Graph.Inductive.PatriciaTree (Gr)+import qualified Data.Graph.Inductive.Query.DFS as G (components,+ condensation, topsort')++import Disco.Pretty+import Disco.Util ((!))++-- | Directed graphs, with vertices labelled by @a@ and unlabelled+-- edges.+data Graph a = G (Gr a ()) (Map a G.Node) (Map G.Node a)+ deriving Show++instance Pretty a => Pretty (Graph a) where+ pretty (G g _ _) = parens (prettyVertices <> ", " <> prettyEdges)+ -- (V = {(0, x), (1, N)}, E = {0 -> 1, 2 -> 3})+ where+ vs = G.labNodes g+ es = G.labEdges g++ prettyVertex (n,a) = parens (text (show n) <> ", " <> pretty a)+ prettyVertices = "V = " <> braces (intercalate "," (P.map prettyVertex vs))+ prettyEdge (v1,v2,_) = text (show v1) <+> "->" <+> text (show v2)+ prettyEdges = "E = " <> braces (intercalate "," (P.map prettyEdge es))++-- | Create a graph with the given set of vertices and directed edges.+-- If any edges refer to vertices that are not in the given vertex+-- set, they will simply be dropped.+mkGraph :: (Show a, Ord a) => Set a -> Set (a,a) -> Graph a+mkGraph vs es = G (G.mkGraph vs' es') a2n n2a+ where+ vs' = zip [0..] (S.toList vs)+ n2a = M.fromList vs'+ a2n = M.fromList . P.map swap $ vs'+ es' = mapMaybe mkEdge (S.toList es)+ mkEdge (a1,a2) = (,,) <$> M.lookup a1 a2n <*> M.lookup a2 a2n <*> pure ()++-- | Return the set of vertices (nodes) of a graph.+nodes :: Graph a -> Set a+nodes (G _ m _) = M.keysSet m++-- | Return the set of directed edges of a graph.+edges :: Ord a => Graph a -> Set (a,a)+edges (G g _ m) = S.fromList $ P.map (\(n1,n2,()) -> (m ! n1, m ! n2)) (G.labEdges g)++-- | Map a function over all the vertices of a graph. @Graph@ is not+-- a @Functor@ instance because of the @Ord@ constraint on @b@.+map :: Ord b => (a -> b) -> Graph a -> Graph b+map f (G g m1 m2) = G (G.nmap f g) (M.mapKeys f m1) (M.map f m2)++-- | Delete a vertex.+delete :: (Show a, Ord a) => a -> Graph a -> Graph a+delete a (G g a2n n2a) = G (G.delNode n g) (M.delete a a2n) (M.delete n n2a)+ where+ n = a2n ! a++-- | The @condensation@ of a graph is the graph of its strongly+-- connected components, /i.e./ each strongly connected component is+-- compressed to a single node, labelled by the set of vertices in+-- the component. There is an edge from component A to component B+-- in the condensed graph iff there is an edge from any vertex in+-- component A to any vertex in component B in the original graph.+condensation :: Ord a => Graph a -> Graph (Set a)+condensation (G g _ n2a) = G g' as2n n2as+ where+ g' = G.nmap (S.fromList . P.map (n2a !)) (G.condensation g)+ vs' = G.labNodes g'+ n2as = M.fromList vs'+ as2n = M.fromList . P.map swap $ vs'++-- | Get a list of the weakly connected components of a graph,+-- providing the set of vertices in each. Equivalently, return the+-- strongly connected components of the graph when considered as an+-- undirected graph.+wcc :: Ord a => Graph a -> [Set a]+wcc = P.map (S.map snd) . wccIDs++wccIDs :: Ord a => Graph a -> [Set (G.Node, a)]+wccIDs (G g _a2n n2a) = P.map (S.fromList . P.map (id &&& (n2a !))) (G.components g)++-- | Do a topological sort on a DAG.+topsort :: Graph a -> [a]+topsort (G g _a2n _n2a) = G.topsort' g++-- | A miscellaneous utility function to turn a @Graph Maybe@ into a+-- @Maybe Graph@: the result is @Just@ iff all the vertices in the+-- input graph are.+sequenceGraph :: Ord a => Graph (Maybe a) -> Maybe (Graph a)+sequenceGraph g = case all isJust (nodes g) of+ False -> Nothing+ True -> Just $ map fromJust g++-- | Get a list of all the /successors/ of a given node in the graph,+-- /i.e./ all the nodes reachable from the given node by a directed+-- path. Does not include the given node itself.+suc :: (Show a, Ord a) => Graph a -> a -> [a]+suc (G g a2n n2a) = P.map (n2a !) . G.suc g . (a2n !)++-- | Get a list of all the /predecessors/ of a given node in the+-- graph, /i.e./ all the nodes from which from the given node is+-- reachable by a directed path. Does not include the given node+-- itself.+pre :: (Show a, Ord a) => Graph a -> a -> [a]+pre (G g a2n n2a) = P.map (n2a !) . G.pre g . (a2n !)++-- | Given a graph, return two mappings: the first maps each vertex to+-- its set of successors; the second maps each vertex to its set of+-- predecessors. Equivalent to+--+-- > (M.fromList *** M.fromList) . unzip . map (\a -> ((a, suc g a), (a, pre g a))) . nodes $ g+--+-- but much more efficient.+cessors :: (Show a, Ord a) => Graph a -> (Map a (Set a), Map a (Set a))+cessors g@(G gg _ _) = (succs, preds)+ where+ as = G.topsort' gg+ succs = foldr collectSuccs M.empty as -- build successors map+ collectSuccs a m = M.insert a succsSet m+ where+ ss = suc g a+ succsSet = S.fromList ss `S.union` S.unions (P.map (m !) ss)++ preds = foldr collectPreds M.empty (reverse as) -- build predecessors map+ collectPreds a m = M.insert a predsSet m+ where+ ss = pre g a+ predsSet = S.fromList ss `S.union` S.unions (P.map (m !) ss)
+ src/Disco/Typecheck/Solve.hs view
@@ -0,0 +1,1046 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Typecheck.Solve+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Constraint solver for the constraints generated during type+-- checking/inference.+-----------------------------------------------------------------------------++module Disco.Typecheck.Solve where++import Unbound.Generics.LocallyNameless (Alpha, Name, Subst, fv,+ name2Integer, string2Name,+ substs)++import Data.Coerce+import GHC.Generics (Generic)++import Control.Arrow ((&&&), (***))+import Control.Lens hiding (use, (%=), (.=))+import Control.Monad (unless, zipWithM)+import Data.Bifunctor (first, second)+import Data.Either (partitionEithers)+import Data.List (find, foldl', intersect,+ partition)+import Data.Map (Map, (!))+import qualified Data.Map as M+import Data.Maybe (fromJust, fromMaybe,+ mapMaybe)+import Data.Monoid (First (..))+import Data.Set (Set)+import qualified Data.Set as S+import Data.Tuple++import Disco.Effects.Fresh+import Disco.Effects.State+import Polysemy+import Polysemy.Error+import Polysemy.Output++import Disco.Messages+import Disco.Pretty hiding ((<>))+import Disco.Subst+import qualified Disco.Subst as Subst+import Disco.Typecheck.Constraints+import Disco.Typecheck.Graph (Graph)+import qualified Disco.Typecheck.Graph as G+import Disco.Typecheck.Unify+import Disco.Types+import Disco.Types.Qualifiers+import Disco.Types.Rules++--------------------------------------------------+-- Solver errors++-- | Type of errors which can be generated by the constraint solving+-- process.+data SolveError where+ NoWeakUnifier :: SolveError+ NoUnify :: SolveError+ UnqualBase :: Qualifier -> BaseTy -> SolveError+ Unqual :: Qualifier -> Type -> SolveError+ QualSkolem :: Qualifier -> Name Type -> SolveError+ deriving Show++instance Semigroup SolveError where+ e <> _ = e++--------------------------------------------------+-- Error utilities++runSolve :: Sem (Fresh ': Error SolveError ': r) a -> Sem r (Either SolveError a)+runSolve = runError . runFresh++-- | Run a list of actions, and return the results from those which do+-- not throw an error. If all of them throw an error, rethrow the+-- first one.+filterErrors :: Member (Error e) r => [Sem r a] -> Sem r [a]+filterErrors ms = do+ es <- mapM try ms+ case partitionEithers es of+ (e:_, []) -> throw e+ (_, as) -> return as++-- | A variant of 'asum' which picks the first action that succeeds,+-- or re-throws the error of the last one if none of them+-- do. Precondition: the list must not be empty.+asum' :: Member (Error e) r => [Sem r a] -> Sem r a+asum' [] = error "Impossible: asum' []"+asum' [m] = m+asum' (m:ms) = m `catch` (\_ -> asum' ms)++--------------------------------------------------+-- Simple constraints++data SimpleConstraint where+ (:<:) :: Type -> Type -> SimpleConstraint+ (:=:) :: Type -> Type -> SimpleConstraint+ deriving (Show, Eq, Ord, Generic, Alpha, Subst Type)++instance Pretty SimpleConstraint where+ pretty = \case+ ty1 :<: ty2 -> pretty ty1 <+> "<:" <+> pretty ty2+ ty1 :=: ty2 -> pretty ty1 <+> "=" <+> pretty ty2++--------------------------------------------------+-- Simplifier types++-- Uses TH to generate lenses so it has to go here before other stuff.++---------------------------------+-- Variable maps++-- | Information about a particular type variable. More information+-- may be added in the future (e.g. polarity).+data TyVarInfo = TVI+ { _tyVarIlk :: First Ilk -- ^ The ilk (unification or skolem) of the variable, if known+ , _tyVarSort :: Sort -- ^ The sort (set of qualifiers) of the type variable.+ }+ deriving (Show)++makeLenses ''TyVarInfo++instance Pretty TyVarInfo where+ pretty (TVI (First ilk) s) = maybe (pure "?") pretty ilk <> "%" <> pretty s++-- | Create a 'TyVarInfo' given an 'Ilk' and a 'Sort'.+mkTVI :: Ilk -> Sort -> TyVarInfo+mkTVI = TVI . First . Just++-- | We can learn different things about a type variable from+-- different places; the 'Semigroup' instance allows combining+-- information about a type variable into a single record.+instance Semigroup TyVarInfo where+ TVI i1 s1 <> TVI i2 s2 = TVI (i1 <> i2) (s1 <> s2)++-- | A 'TyVarInfoMap' records what we know about each type variable;+-- it is a mapping from type variable names to 'TyVarInfo' records.+newtype TyVarInfoMap = VM { unVM :: Map (Name Type) TyVarInfo }+ deriving (Show)++instance Pretty TyVarInfoMap where+ pretty (VM m) = pretty m++-- | Utility function for acting on a 'TyVarInfoMap' by acting on the+-- underlying 'Map'.+onVM ::+ (Map (Name Type) TyVarInfo -> Map (Name Type) TyVarInfo) ->+ TyVarInfoMap -> TyVarInfoMap+onVM f (VM m) = VM (f m)++-- | Look up a given variable name in a 'TyVarInfoMap'.+lookupVM :: Name Type -> TyVarInfoMap -> Maybe TyVarInfo+lookupVM v = M.lookup v . unVM++-- | Remove the mapping for a particular variable name (if it exists)+-- from a 'TyVarInfoMap'.+deleteVM :: Name Type -> TyVarInfoMap -> TyVarInfoMap+deleteVM = onVM . M.delete++-- | Given a list of type variable names, add them all to the+-- 'TyVarInfoMap' as 'Skolem' variables (with a trivial sort).+addSkolems :: [Name Type] -> TyVarInfoMap -> TyVarInfoMap+addSkolems vs = onVM $ \vm -> foldl' (flip (\v -> M.insert v (mkTVI Skolem mempty))) vm vs++-- | The @Semigroup@ instance for 'TyVarInfoMap' unions the two maps,+-- combining the info records for any variables occurring in both+-- maps.+instance Semigroup TyVarInfoMap where+ VM sm1 <> VM sm2 = VM (M.unionWith (<>) sm1 sm2)++instance Monoid TyVarInfoMap where+ mempty = VM M.empty+ mappend = (<>)++-- | Get the sort of a particular variable recorded in a+-- 'TyVarInfoMap'. Returns the trivial (empty) sort for a variable+-- not in the map.+getSort :: TyVarInfoMap -> Name Type -> Sort+getSort (VM m) v = maybe topSort (view tyVarSort) (M.lookup v m)++-- | Get the 'Ilk' of a variable recorded in a 'TyVarInfoMap'.+-- Returns @Nothing@ if the variable is not in the map, or if its+-- ilk is not known.+getIlk :: TyVarInfoMap -> Name Type -> Maybe Ilk+getIlk (VM m) v = (m ^? ix v . tyVarIlk) >>= getFirst++-- | Extend the sort of a type variable by combining its existing sort+-- with the given one. Has no effect if the variable is not already+-- in the map.+extendSort :: Name Type -> Sort -> TyVarInfoMap -> TyVarInfoMap+extendSort x s = onVM (at x . _Just . tyVarSort %~ (`S.union` s))++---------------------------------+-- Simplifier state++-- The simplification stage maintains a mutable state consisting of+-- the current qualifier map (containing wanted qualifiers for type+-- variables), the list of remaining SimpleConstraints, and the+-- current substitution. It also keeps track of seen constraints, so+-- expansion of recursive types can stop when encountering a+-- previously seen constraint.+data SimplifyState = SS+ { _ssVarMap :: TyVarInfoMap+ , _ssConstraints :: [SimpleConstraint]+ , _ssSubst :: S+ , _ssSeen :: Set SimpleConstraint+ }++makeLenses ''SimplifyState++lkup :: (Ord k, Show k, Show (Map k a)) => String -> Map k a -> k -> a+lkup messg m k = fromMaybe (error errMsg) (M.lookup k m)+ where+ errMsg = unlines+ [ "Key lookup error:"+ , " Key = " ++ show k+ , " Map = " ++ show m+ , " Location: " ++ messg+ ]++--------------------------------------------------+-- Top-level solver algorithm++solveConstraint+ :: Members '[Fresh, Error SolveError, Output Message] r+ => TyDefCtx -> Constraint -> Sem r S+solveConstraint tyDefns c = do++ -- Step 1. Open foralls (instantiating with skolem variables) and+ -- collect wanted qualifiers; also expand disjunctions. Result in a+ -- list of possible constraint sets; each one consists of equational+ -- and subtyping constraints in addition to qualifiers.++ debug "Solving:"+ debugPretty c++ debug "------------------------------"+ debug "Decomposing constraints..."++ qcList <- decomposeConstraint c++ -- Now try continuing with each set and pick the first one that has+ -- a solution.+ asum' (map (uncurry (solveConstraintChoice tyDefns)) qcList)++solveConstraintChoice+ :: Members '[Fresh, Error SolveError, Output Message] r+ => TyDefCtx -> TyVarInfoMap -> [SimpleConstraint] -> Sem r S+solveConstraintChoice tyDefns quals cs = do++ debugPretty quals+ debug $ vcat (map pretty' cs)++ -- Step 2. Check for weak unification to ensure termination. (a la+ -- Traytel et al).++ let toEqn (t1 :<: t2) = (t1,t2)+ toEqn (t1 :=: t2) = (t1,t2)+ _ <- note NoWeakUnifier $ weakUnify tyDefns (map toEqn cs)++ -- Step 3. Simplify constraints, resulting in a set of atomic+ -- subtyping constraints. Also simplify/update qualifier set+ -- accordingly.++ debug "------------------------------"+ debug "Running simplifier..."++ (vm, atoms, theta_simp) <- simplify tyDefns quals cs+ debug "Done running simplifier. Results:"++ debugPretty vm+ debug $ vcat $ map (pretty' . (\(x,y) -> TyAtom x :<: TyAtom y)) atoms+ debugPretty theta_simp++ -- Step 4. Turn the atomic constraints into a directed constraint+ -- graph.++ debug "------------------------------"+ debug "Generating constraint graph..."++ -- Some variables might have qualifiers but not participate in any+ -- equality or subtyping relations (see issue #153); make sure to+ -- extract them and include them in the constraint graph as isolated+ -- vertices+ let mkAVar (v, First (Just Skolem)) = AVar (S v)+ mkAVar (v, _ ) = AVar (U v)+ vars = S.fromList . map (mkAVar . second (view tyVarIlk)) . M.assocs . unVM $ vm+ g = mkConstraintGraph vars atoms++ debugPretty g++ -- Step 5.+ -- Check for any weakly connected components containing more+ -- than one skolem, or a skolem and a base type; such components are+ -- not allowed. Other WCCs with a single skolem simply unify to+ -- that skolem.++ debug "------------------------------"+ debug "Checking WCCs for skolems..."++ (g', theta_skolem) <- checkSkolems tyDefns vm g+ debugPretty theta_skolem++ -- We don't need to ensure that theta_skolem respects sorts since+ -- checkSkolems will only unify skolem vars with unsorted variables.+++ -- Step 6. Eliminate cycles from the graph, turning each strongly+ -- connected component into a single node, unifying all the atoms in+ -- each component.++ debug "------------------------------"+ debug "Collapsing SCCs..."++ (g'', theta_cyc) <- elimCycles tyDefns g'++ debugPretty g''+ debugPretty theta_cyc++ -- Check that the resulting substitution respects sorts...+ let sortOK (x, TyAtom (ABase ty)) = hasSort ty (getSort vm x)+ sortOK (_, TyAtom (AVar (U _))) = True+ sortOK p = error $ "Impossible! sortOK " ++ show p+ unless (all sortOK (Subst.toList theta_cyc))+ $ throw NoUnify++ -- ... and update the sort map if we unified any type variables.+ -- Just make sure that if theta_cyc maps x |-> y, then y picks up+ -- the sort of x.++ debug "Old sort map:"+ debugPretty vm++ let vm' = foldr updateVarMap vm (Subst.toList theta_cyc)+ where+ updateVarMap :: (Name Type, Type) -> TyVarInfoMap -> TyVarInfoMap+ updateVarMap (x, TyAtom (AVar (U y))) vmm = extendSort y (getSort vmm x) vmm+ updateVarMap _ vmm = vmm++ debug "Updated sort map:"+ debugPretty vm++ -- Steps 7 & 8: solve the graph, iteratively finding satisfying+ -- assignments for each type variable based on its successor and+ -- predecessor base types in the graph; then unify all the type+ -- variables in any remaining weakly connected components.++ debug "------------------------------"+ debug "Solving for type variables..."++ theta_sol <- solveGraph vm' g''+ debugPretty theta_sol++ debug "------------------------------"+ debug "Composing final substitution..."++ let theta_final = theta_sol @@ theta_cyc @@ theta_skolem @@ theta_simp+ debugPretty theta_final++ return theta_final+++--------------------------------------------------+-- Step 1. Constraint decomposition.++decomposeConstraint+ :: Members '[Fresh, Error SolveError] r+ => Constraint -> Sem r [(TyVarInfoMap, [SimpleConstraint])]+decomposeConstraint (CSub t1 t2) = return [(mempty, [t1 :<: t2])]+decomposeConstraint (CEq t1 t2) = return [(mempty, [t1 :=: t2])]+decomposeConstraint (CQual q ty) = (:[]) . (, []) <$> decomposeQual ty q+decomposeConstraint (CAnd cs) = map mconcat . sequence <$> mapM decomposeConstraint cs+decomposeConstraint CTrue = return [mempty]+decomposeConstraint (CAll ty) = do+ (vars, c) <- unbind ty+ let c' = substs (mkSkolems vars) c+ (map . first . addSkolems) vars <$> decomposeConstraint c'++ where+ mkSkolems :: [Name Type] -> [(Name Type, Type)]+ mkSkolems = map (id &&& TySkolem)++decomposeConstraint (COr cs) = concat <$> filterErrors (map decomposeConstraint cs)++decomposeQual+ :: Members '[Fresh, Error SolveError] r+ => Type -> Qualifier -> Sem r TyVarInfoMap+decomposeQual (TyAtom a) q = checkQual q a+ -- XXX Really we should be able to check by induction whether a+ -- user-defined type has a certain sort.+decomposeQual ty@(TyCon (CUser _) _) q = throw $ Unqual q ty+decomposeQual ty@(TyCon c tys) q+ = case qualRules c q of+ Nothing -> throw $ Unqual q ty+ Just qs -> mconcat <$> zipWithM (maybe (return mempty) . decomposeQual) tys qs++checkQual+ :: Members '[Fresh, Error SolveError] r+ => Qualifier -> Atom -> Sem r TyVarInfoMap+checkQual q (AVar (U v)) = return . VM . M.singleton v $ mkTVI Unification (S.singleton q)+checkQual q (AVar (S v)) = throw $ QualSkolem q v+checkQual q (ABase bty) =+ case hasQual bty q of+ True -> return mempty+ False -> throw $ UnqualBase q bty++--------------------------------------------------+-- Step 3. Constraint simplification.++-- | This step does unification of equality constraints, as well as+-- structural decomposition of subtyping constraints. For example,+-- if we have a constraint (x -> y) <: (z -> Int), then we can+-- decompose it into two constraints, (z <: x) and (y <: Int); if we+-- have a constraint v <: (a,b), then we substitute v ↦ (x,y) (where+-- x and y are fresh type variables) and continue; and so on.+--+-- After this step, the remaining constraints will all be atomic+-- constraints, that is, only of the form (v1 <: v2), (v <: b), or+-- (b <: v), where v is a type variable and b is a base type.++simplify+ :: Members '[Error SolveError, Output Message] r+ => TyDefCtx -> TyVarInfoMap -> [SimpleConstraint] -> Sem r (TyVarInfoMap, [(Atom, Atom)], S)+simplify tyDefns origVM cs+ = (\(SS vm' cs' s' _) -> (vm', map extractAtoms cs', s'))+ -- contFreshMT :: Monad m => FreshMT m a -> Integer -> m a+ -- "Run a FreshMT computation given a starting index for fresh name generation."+ <$> runFresh' n (execState (SS origVM cs idS S.empty) simplify')+ where++ fvNums :: Alpha a => [a] -> [Integer]+ fvNums = map (name2Integer :: Name Type -> Integer) . toListOf fv++ -- Find first unused integer in constraint free vars and sort map+ -- domain, and use it to start the fresh var generation, so we don't+ -- generate any "fresh" names that interfere with existing names+ n1 = maximum0 . fvNums $ cs+ n = succ . maximum . (n1:) . fvNums . M.keys . unVM $ origVM++ maximum0 [] = 0+ maximum0 xs = maximum xs++ -- Extract the type atoms from an atomic constraint.+ extractAtoms :: SimpleConstraint -> (Atom, Atom)+ extractAtoms (TyAtom a1 :<: TyAtom a2) = (a1, a2)+ extractAtoms c = error $ "Impossible: simplify left non-atomic or non-subtype constraint " ++ show c++ -- Iterate picking one simplifiable constraint and simplifying it+ -- until none are left.+ simplify'+ :: Members '[State SimplifyState, Fresh, Error SolveError, Output Message] r+ => Sem r ()+ simplify' = do+ -- q <- gets fst+ -- debug (pretty q)+ -- debug ""++ mc <- pickSimplifiable+ case mc of+ Nothing -> return ()+ Just s -> do++ debug $ "Simplifying:" <+> pretty' s++ simplifyOne s+ simplify'++ -- Pick out one simplifiable constraint, removing it from the list+ -- of constraints in the state. Return Nothing if no more+ -- constraints can be simplified.+ pickSimplifiable+ :: Members '[State SimplifyState, Fresh, Error SolveError] r+ => Sem r (Maybe SimpleConstraint)+ pickSimplifiable = do+ curCs <- use ssConstraints+ case pick simplifiable curCs of+ Nothing -> return Nothing+ Just (a,as) -> do+ ssConstraints .= as+ return (Just a)++ -- Pick the first element from a list satisfying the given+ -- predicate, returning the element and the list with the element+ -- removed.+ pick :: (a -> Bool) -> [a] -> Maybe (a,[a])+ pick _ [] = Nothing+ pick p (a:as)+ | p a = Just (a,as)+ | otherwise = second (a:) <$> pick p as++ -- Check if a constraint can be simplified. An equality+ -- constraint can always be "simplified" via unification. A+ -- subtyping constraint can be simplified if either it involves a+ -- type constructor (in which case we can decompose it), or if it+ -- involves two base types (in which case it can be removed if the+ -- relationship holds).+ simplifiable :: SimpleConstraint -> Bool+ simplifiable (_ :=: _) = True+ simplifiable (TyCon {} :<: TyCon {}) = True+ simplifiable (TyVar {} :<: TyCon {}) = True+ simplifiable (TyCon {} :<: TyVar {}) = True+ simplifiable (TyCon (CUser _) _ :<: _) = True+ simplifiable (_ :<: TyCon (CUser _) _) = True+ simplifiable (TyAtom (ABase _) :<: TyAtom (ABase _)) = True++ simplifiable _ = False++ -- Simplify the given simplifiable constraint. If the constraint+ -- has already been seen before (due to expansion of a recursive+ -- type), just throw it away and stop.+ simplifyOne+ :: Members '[State SimplifyState, Fresh, Error SolveError] r+ => SimpleConstraint -> Sem r ()+ simplifyOne c = do+ seen <- use ssSeen+ case c `S.member` seen of+ True -> return ()+ False -> do+ ssSeen %= S.insert c+ simplifyOne' c++ simplifyOne'+ :: Members '[State SimplifyState, Fresh, Error SolveError] r+ => SimpleConstraint -> Sem r ()++ -- If we have an equality constraint, run unification on it. The+ -- resulting substitution is applied to the remaining constraints+ -- as well as prepended to the current substitution.++ simplifyOne' (ty1 :=: ty2) =+ case unify tyDefns [(ty1, ty2)] of+ Nothing -> throw NoUnify+ Just s' -> extendSubst s'++ -- If we see a constraint of the form (T <: a), where T is a+ -- user-defined type and a is a type variable, then just turn it+ -- into an equality (T = a). This is sound but probably not+ -- complete. The alternative seems quite complicated, possibly+ -- even undecidable. See https://github.com/disco-lang/disco/issues/207 .+ simplifyOne' (ty1@(TyCon (CUser _) _) :<: ty2@TyVar{})+ = simplifyOne' (ty1 :=: ty2)++ -- Otherwise, expand the user-defined type and continue.+ simplifyOne' (TyCon (CUser t) ts :<: ty2) =+ case M.lookup t tyDefns of+ Nothing -> error $ show t ++ " not in ty defn map!"+ Just (TyDefBody _ body) ->+ ssConstraints %= ((body ts :<: ty2) :)++ -- Turn a <: T into a = T. See comment above.+ simplifyOne' (ty1@TyVar{} :<: ty2@(TyCon (CUser _) _))+ = simplifyOne' (ty1 :=: ty2)++ simplifyOne' (ty1 :<: TyCon (CUser t) ts) =+ case M.lookup t tyDefns of+ Nothing -> error $ show t ++ " not in ty defn map!"+ Just (TyDefBody _ body) ->+ ssConstraints %= ((ty1 :<: body ts) :)++ -- Given a subtyping constraint between two type constructors,+ -- decompose it if the constructors are the same (or fail if they+ -- aren't), taking into account the variance of each argument to+ -- the constructor. Container types are a special case;+ -- recursively generate a subtyping constraint for their+ -- constructors as well.+ simplifyOne' (TyCon c1@(CContainer ctr1) tys1 :<: TyCon (CContainer ctr2) tys2) =+ ssConstraints %=+ (( (TyAtom ctr1 :<: TyAtom ctr2)+ : zipWith3 variance (arity c1) tys1 tys2+ )+ ++)++ simplifyOne' (TyCon c1 tys1 :<: TyCon c2 tys2)+ | c1 /= c2 = throw NoUnify+ | otherwise =+ ssConstraints %= (zipWith3 variance (arity c1) tys1 tys2 ++)++ -- Given a subtyping constraint between a variable and a type+ -- constructor, expand the variable into the same constructor+ -- applied to fresh type variables.+ simplifyOne' con@(TyVar a :<: TyCon c _) = expandStruct a c con+ simplifyOne' con@(TyCon c _ :<: TyVar a ) = expandStruct a c con++ -- Given a subtyping constraint between two base types, just check+ -- whether the first is indeed a subtype of the second. (Note+ -- that we only pattern match here on type atoms, which could+ -- include variables, but this will only ever get called if+ -- 'simplifiable' was true, which checks that both are base+ -- types.)+ simplifyOne' (TyAtom (ABase b1) :<: TyAtom (ABase b2)) = do+ case isSubB b1 b2 of+ True -> return ()+ False -> throw NoUnify++ simplifyOne' (s :<: t) =+ error $ "Impossible! simplifyOne' " ++ show s ++ " :<: " ++ show t++ expandStruct+ :: Members '[State SimplifyState, Fresh, Error SolveError] r+ => Name Type -> Con -> SimpleConstraint -> Sem r ()+ expandStruct a c con = do+ as <- mapM (const (TyVar <$> fresh (string2Name "a"))) (arity c)+ let s' = a |-> TyCon c as+ ssConstraints %= (con:)+ extendSubst s'++ -- 1. compose s' with current subst+ -- 2. apply s' to constraints+ -- 3. apply s' to qualifier map and decompose+ extendSubst+ :: Members '[State SimplifyState, Fresh, Error SolveError] r+ => S -> Sem r ()+ extendSubst s' = do+ ssSubst %= (s'@@)+ ssConstraints %= applySubst s'+ substVarMap s'++ substVarMap+ :: Members '[State SimplifyState, Fresh, Error SolveError] r+ => S -> Sem r ()+ substVarMap s' = do+ vm <- use ssVarMap++ -- 1. Get quals for each var in domain of s' and match them with+ -- the types being substituted for those vars.++ let tySorts :: [(Type, Sort)]+ tySorts = map (second (view tyVarSort)) . mapMaybe (traverse (`lookupVM` vm) . swap) $ Subst.toList s'++ tyQualList :: [(Type, Qualifier)]+ tyQualList = concatMap (sequenceA . second S.toList) tySorts++ -- 2. Decompose the resulting qualifier constraints++ vm' <- mconcat <$> mapM (uncurry decomposeQual) tyQualList++ -- 3. delete domain of s' from vm and merge in decomposed quals.++ ssVarMap .= vm' <> foldl' (flip deleteVM) vm (dom s')++ -- The above works even when unifying two variables. Say we have+ -- the TyVarInfoMap+ --+ -- a |-> {add}+ -- b |-> {sub}+ --+ -- and we get back theta = [a |-> b]. The domain of theta+ -- consists solely of a, so we look up a in the TyVarInfoMap and get+ -- {add}. We therefore generate the constraint 'add (theta a)'+ -- = 'add b' which can't be decomposed at all, and hence yields+ -- the TyVarInfoMap {b |-> {add}}. We then delete a from the+ -- original TyVarInfoMap and merge the result with the new TyVarInfoMap,+ -- yielding {b |-> {sub,add}}.+++ -- Create a subtyping constraint based on the variance of a type+ -- constructor argument position: in the usual order for+ -- covariant, and reversed for contravariant.+ variance Co ty1 ty2 = ty1 :<: ty2+ variance Contra ty1 ty2 = ty2 :<: ty1++--------------------------------------------------+-- Step 4: Build constraint graph++-- | Given a list of atoms and atomic subtype constraints (each pair+-- @(a1,a2)@ corresponds to the constraint @a1 <: a2@) build the+-- corresponding constraint graph.+mkConstraintGraph :: (Show a, Ord a) => Set a -> [(a, a)] -> Graph a+mkConstraintGraph as cs = G.mkGraph nodes (S.fromList cs)+ where+ nodes = as `S.union` S.fromList (cs ^.. traverse . each)++--------------------------------------------------+-- Step 5: Check skolems++-- | Check for any weakly connected components containing more than+-- one skolem, or a skolem and a base type, or a skolem and any+-- variables with nontrivial sorts; such components are not allowed.+-- If there are any WCCs with a single skolem, no base types, and+-- only unsorted variables, just unify them all with the skolem and+-- remove those components.+checkSkolems+ :: Members '[Error SolveError, Output Message] r+ => TyDefCtx -> TyVarInfoMap -> Graph Atom -> Sem r (Graph UAtom, S)+checkSkolems tyDefns vm graph = do+ let skolemWCCs :: [Set Atom]+ skolemWCCs = filter (any isSkolem) $ G.wcc graph++ ok wcc = S.size (S.filter isSkolem wcc) <= 1+ && all (\case { ABase _ -> False+ ; AVar (S _) -> True+ ; AVar (U v) -> maybe True (S.null . view tyVarSort) (lookupVM v vm) })+ wcc++ (good, bad) = partition ok skolemWCCs++ unless (null bad) $ throw NoUnify++ -- take all good sets and+ -- (1) delete them from the graph+ -- (2) unify them all with the skolem+ unifyWCCs graph idS good++ where+ noSkolems :: Atom -> UAtom+ noSkolems (ABase b) = UB b+ noSkolems (AVar (U v)) = UV v+ noSkolems (AVar (S v)) = error $ "Skolem " ++ show v ++ " remaining in noSkolems"++ unifyWCCs g s [] = return (G.map noSkolems g, s)+ unifyWCCs g s (u:us) = do+ debug $ "Unifying" <+> pretty' (u:us) <> "..."++ let g' = foldl' (flip G.delete) g u++ ms' = unifyAtoms tyDefns (S.toList u)+ case ms' of+ Nothing -> throw NoUnify+ Just s' -> unifyWCCs g' (atomToTypeSubst s' @@ s) us++--------------------------------------------------+-- Step 6: Eliminate cycles++-- | Eliminate cycles in the constraint set by collapsing each+-- strongly connected component to a single node, (unifying all the+-- types in the SCC). A strongly connected component is a maximal+-- set of nodes where every node is reachable from every other by a+-- directed path; since we are using directed edges to indicate a+-- subtyping constraint, this means every node must be a subtype of+-- every other, and the only way this can happen is if all are in+-- fact equal.+--+-- Of course, this step can fail if the types in a SCC are not+-- unifiable. If it succeeds, it returns the collapsed graph (which+-- is now guaranteed to be acyclic, i.e. a DAG) and a substitution.+elimCycles+ :: Members '[Error SolveError] r+ => TyDefCtx -> Graph UAtom -> Sem r (Graph UAtom, S)+elimCycles tyDefns = elimCyclesGen uatomToTypeSubst (unifyUAtoms tyDefns)++elimCyclesGen+ :: forall a b r. (Subst a a, Ord a, Members '[Error SolveError] r)+ => (Substitution a -> Substitution b) -> ([a] -> Maybe (Substitution a))+ -> Graph a -> Sem r (Graph a, Substitution b)+elimCyclesGen genSubst genUnify g+ = note NoUnify+ $ (G.map fst &&& (genSubst . compose . S.map snd . G.nodes)) <$> g'+ where++ g' :: Maybe (Graph (a, Substitution a))+ g' = G.sequenceGraph $ G.map unifySCC (G.condensation g)++ unifySCC :: Set a -> Maybe (a, Substitution a)+ unifySCC uatoms = case S.toList uatoms of+ [] -> error "Impossible! unifySCC on the empty set"+ as@(a:_) -> (flip applySubst a &&& id) <$> genUnify as++------------------------------------------------------------+-- Steps 7 and 8: Constraint resolution+------------------------------------------------------------++-- | Rels stores the set of base types and variables related to a+-- given variable in the constraint graph (either predecessors or+-- successors, but not both).+data Rels = Rels+ { baseRels :: Set BaseTy+ , varRels :: Set (Name Type)+ }+ deriving (Show, Eq)++-- | A RelMap associates each variable to its sets of base type and+-- variable predecessors and successors in the constraint graph.+newtype RelMap = RelMap { unRelMap :: Map (Name Type, Dir) Rels}++instance Pretty RelMap where+ pretty (RelMap rm) = vcat (map prettyVar byVar)+ where+ vars = S.map fst (M.keysSet rm)+ byVar = map (\x -> (rm!(x,SubTy), x, rm!(x,SuperTy))) (S.toList vars)++ prettyVar (subs, x, sups) = hsep [prettyRel subs, "<:", pretty x, "<:", prettyRel sups]+ prettyRel rs = pretty (baseRels rs) <> ", " <> pretty (varRels rs)++-- | Modify a @RelMap@ to record the fact that we have solved for a+-- type variable. In particular, delete the variable from the+-- @RelMap@ as a key, and also update the relative sets of every+-- other variable to remove this variable and add the base type we+-- chose for it.+substRel :: Name Type -> BaseTy -> RelMap -> RelMap+substRel x ty+ = RelMap+ . M.delete (x,SuperTy)+ . M.delete (x,SubTy)+ . M.map (\r@(Rels b v) -> if x `S.member` v then Rels (S.insert ty b) (S.delete x v) else r)+ . unRelMap++-- | Essentially dirtypesBySort vm rm dir t s x finds all the+-- dir-types (sub- or super-) of t which have sort s, relative to+-- the variables in x. This is \overbar{T}_S^X (resp. \underbar...)+-- from Traytel et al.+dirtypesBySort :: TyVarInfoMap -> RelMap -> Dir -> BaseTy -> Sort -> Set (Name Type) -> [BaseTy]+dirtypesBySort vm (RelMap relMap) dir t s x++ -- Keep only those supertypes t' of t+ = keep (dirtypes dir t) $ \t' ->+ -- which have the right sort, and such that+ hasSort t' s &&++ -- for all variables beta \in x,+ forAll x (\beta ->++ -- there is at least one type t'' which is a subtype of t'+ -- which would be a valid solution for beta, that is,+ exists (dirtypes (other dir) t') $ \t'' ->++ -- t'' has the sort beta is supposed to have, and+ hasSort t'' (getSort vm beta) &&++ -- t'' is a supertype of every base type predecessor of beta.+ forAll (baseRels (lkup "dirtypesBySort, beta rel" relMap (beta, other dir)))+ (isDirB dir t''))++ -- The above comments are written assuming dir = Super; of course,+ -- if dir = Sub then just swap "super" and "sub" everywhere.++ where+ forAll, exists :: Foldable t => t a -> (a -> Bool) -> Bool+ forAll = flip all+ exists = flip any+ keep = flip filter++-- | Sort-aware infimum or supremum.+limBySort :: TyVarInfoMap -> RelMap -> Dir -> [BaseTy] -> Sort -> Set (Name Type) -> Maybe BaseTy+limBySort vm rm dir ts s x+ = (\is -> find (\lim -> all (\u -> isDirB dir u lim) is) is)+ . isects+ . map (\t -> dirtypesBySort vm rm dir t s x)+ $ ts+ where+ isects = foldr1 intersect++lubBySort, glbBySort :: TyVarInfoMap -> RelMap -> [BaseTy] -> Sort -> Set (Name Type) -> Maybe BaseTy+lubBySort vm rm = limBySort vm rm SuperTy+glbBySort vm rm = limBySort vm rm SubTy++-- | From the constraint graph, build the sets of sub- and super- base+-- types of each type variable, as well as the sets of sub- and+-- supertype variables. For each type variable x in turn, try to+-- find a common supertype of its base subtypes which is consistent+-- with the sort of x and with the sorts of all its sub-variables,+-- as well as symmetrically a common subtype of its supertypes, etc.+-- Assign x one of the two: if it has only successors, assign it+-- their inf; otherwise, assign it the sup of its predecessors. If+-- it has both, we have a choice of whether to assign it the sup of+-- predecessors or inf of successors; both lead to a sound &+-- complete algorithm. We choose to assign it the sup of its+-- predecessors in this case, since it seems nice to default to+-- "simpler" types lower down in the subtyping chain.+solveGraph+ :: Members '[Fresh, Error SolveError, Output Message] r+ => TyVarInfoMap -> Graph UAtom -> Sem r S+solveGraph vm g = atomToTypeSubst . unifyWCC <$> go topRelMap+ where+ unifyWCC :: Substitution BaseTy -> Substitution Atom+ unifyWCC s = compose (map mkEquateSubst wccVarGroups) @@ fmap ABase s+ where+ wccVarGroups :: [Set (Name Type)]+ wccVarGroups = map (S.map getVar) . filter (all uisVar) . applySubst s $ G.wcc g+ getVar (UV v) = v+ getVar (UB b) = error+ $ "Impossible! Base type " ++ show b ++ " in solveGraph.getVar"++ mkEquateSubst :: Set (Name Type) -> Substitution Atom+ mkEquateSubst = mkEquations . S.toList++ mkEquations (a:as) = Subst.fromList . map (\v -> (coerce v, AVar (U a))) $ as+ mkEquations [] = error "Impossible! Empty set of names in mkEquateSubst"++ -- After picking concrete base types for all the type+ -- variables we can, the only thing possibly remaining in+ -- the graph are components containing only type variables+ -- and no base types. It is sound, and simplifies the+ -- generated types considerably, to simply unify any type+ -- variables which are related by subtyping constraints.+ -- That is, we collect all the type variables in each+ -- weakly connected component and unify them.+ --+ -- As an example where this final step makes a difference,+ -- consider a term like @\x. (\y.y) x@. A fresh type+ -- variable is generated for the type of @x@, and another+ -- for the type of @y@; the application of @(\y.y)@ to @x@+ -- induces a subtyping constraint between the two type+ -- variables. The most general type would be something+ -- like @forall a b. (a <: b) => a -> b@, but we want to+ -- avoid generating unnecessary subtyping constraints (the+ -- type system might not even support subtyping qualifiers+ -- like this). Instead, we unify the two type variables+ -- and the resulting type is @forall a. a -> a@.++ -- Get the successor and predecessor sets for all the type variables.+ topRelMap :: RelMap+ topRelMap+ = RelMap+ . M.map (uncurry Rels . (S.fromAscList *** S.fromAscList)+ . partitionEithers . map uatomToEither . S.toList)+ $ M.mapKeys (,SuperTy) subMap `M.union` M.mapKeys (,SubTy) superMap++ subMap, superMap :: Map (Name Type) (Set UAtom)+ (subMap, superMap) = (onlyVars *** onlyVars) $ G.cessors g++ onlyVars :: Map UAtom (Set UAtom) -> Map (Name Type) (Set UAtom)+ onlyVars = M.mapKeys fromVar . M.filterWithKey (\a _ -> uisVar a)+ where+ fromVar (UV x) = x+ fromVar _ = error "Impossible! UB but uisVar."++ go+ :: Members '[Fresh, Error SolveError, Output Message] r+ => RelMap -> Sem r (Substitution BaseTy)+ go relMap@(RelMap rm) = debugPretty relMap >> case as of++ -- No variables left that have base type constraints.+ [] -> return idS++ -- Solve one variable at a time. See below.+ (a:_) -> do+ debug $ "Solving for" <+> pretty' a+ case solveVar a of+ Nothing -> do+ debug $ "Couldn't solve for" <+> pretty' a+ throw NoUnify++ -- If we solved for a, delete it from the maps, apply the+ -- resulting substitution to the remainder (updating the+ -- relMap appropriately), and recurse. The substitution we+ -- want will be the composition of the substitution for a+ -- with the substitution generated by the recursive call.+ --+ -- Note we don't need to delete a from the TyVarInfoMap; we+ -- never use the set of keys from the TyVarInfoMap for+ -- anything (indeed, some variables might not be keys if+ -- they have an empty sort), so it doesn't matter if old+ -- variables hang around in it.+ Just s -> do+ debugPretty s+ (@@ s) <$> go (substRel a (fromJust $ Subst.lookup (coerce a) s) relMap)++ where+ -- NOTE we can't solve a bunch in parallel! Might end up+ -- assigning them conflicting solutions if some depend on+ -- others. For example, consider the situation+ --+ -- Z+ -- |+ -- a3+ -- / \+ -- a1 N+ --+ -- If we try to solve in parallel we will end up assigning a1+ -- -> Z (since it only has base types as an upper bound) and+ -- a3 -> N (since it has both upper and lower bounds, and by+ -- default we pick the lower bound), but this is wrong since+ -- we should have a1 < a3.+ --+ -- If instead we solve them one at a time, we could e.g. first+ -- solve a1 -> Z, and then we would find a3 -> Z as well.+ -- Alternately, if we first solve a3 -> N then we will have a1+ -- -> N as well. Both are acceptable.+ --+ -- In fact, this exact graph comes from (^x.x+1) which was+ -- erroneously being inferred to have type Z -> N when I first+ -- wrote the code.++ -- Get only the variables we can solve on this pass, which+ -- have base types in their predecessor or successor set. If+ -- there are no such variables, then start picking any+ -- remaining variables with a sort and pick types for them+ -- (disco doesn't have qualified polymorphism so we can't just+ -- leave them).+ asBase+ = map fst+ . filter (not . S.null . baseRels . lkup "solveGraph.go.as" rm)+ $ M.keys rm+ as = case asBase of+ [] -> filter ((/= topSort) . getSort vm) . map fst $ M.keys rm+ _ -> asBase++ -- Solve for a variable, failing if it has no solution, otherwise returning+ -- a substitution for it.+ solveVar :: Name Type -> Maybe (Substitution BaseTy)+ solveVar v =+ case ((v,SuperTy), (v,SubTy)) & over both (S.toList . baseRels . lkup "solveGraph.solveVar" rm) of+ -- No sub- or supertypes; the only way this can happen is+ -- if it has a nontrivial sort.+ --+ -- Traytel et al. don't seem to have a rule saying what to+ -- do in this case (see Fig. 16 on p. 16 of their long+ -- version). We used to just pick a type that inhabits+ -- the sort, but this is wrong; see+ -- https://github.com/disco-lang/disco/issues/192.+ --+ -- For now, let's assume that any situation in which we+ -- have no base sub- or supertypes but we do have+ -- nontrivial sorts means that we are dealing with numeric+ -- types; so we can just call N a base subtype and go from there.++ ([], []) ->+ -- Debug.trace (show v ++ " has no sub- or supertypes. Assuming N as a subtype.")+ (coerce v |->) <$> lubBySort vm relMap [N] (getSort vm v)+ (varRels (lkup "solveVar none, rels" rm (v,SubTy)))++ -- Only supertypes. Just assign a to their inf, if one exists.+ (bsupers, []) ->+ -- Debug.trace (show v ++ " has only supertypes (" ++ show bsupers ++ ")") $+ (coerce v |->) <$> glbBySort vm relMap bsupers (getSort vm v)+ (varRels (lkup "solveVar bsupers, rels" rm (v,SuperTy)))++ -- Only subtypes. Just assign a to their sup.+ ([], bsubs) ->+ -- Debug.trace (show v ++ " has only subtypes (" ++ show bsubs ++ ")") $+ -- Debug.trace ("sortmap: " ++ show vm) $+ -- Debug.trace ("relmap: " ++ show relMap) $+ -- Debug.trace ("sort for " ++ show v ++ ": " ++ show (getSort vm v)) $+ -- Debug.trace ("relvars: " ++ show (varRels (relMap ! (v,SubTy)))) $+ (coerce v |->) <$> lubBySort vm relMap bsubs (getSort vm v)+ (varRels (lkup "solveVar bsubs, rels" rm (v,SubTy)))++ -- Both successors and predecessors. Both must have a+ -- valid bound, and the bounds must not overlap. Assign a+ -- to the sup of its predecessors.+ (bsupers, bsubs) -> do+ ub <- glbBySort vm relMap bsupers (getSort vm v)+ (varRels (rm ! (v,SuperTy)))+ lb <- lubBySort vm relMap bsubs (getSort vm v)+ (varRels (rm ! (v,SubTy)))+ case isSubB lb ub of+ True -> Just (coerce v |-> lb)+ False -> Nothing
+ src/Disco/Typecheck/Unify.hs view
@@ -0,0 +1,142 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Typecheck.Unify+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Unification.+--+-----------------------------------------------------------------------------++module Disco.Typecheck.Unify where++import Unbound.Generics.LocallyNameless (Name, fv)++import Control.Lens (anyOf)+import Control.Monad.State+import qualified Data.Map as M+import Data.Set (Set)+import qualified Data.Set as S++import Disco.Subst+import Disco.Types++-- XXX todo: might be better if unification took sorts into account+-- directly. As it is, however, I think it works properly;+-- e.g. suppose we have a with sort {sub} and we unify it with Bool.+-- unify will just return a substitution [a |-> Bool]. But then when+-- we call extendSubst, and in particular substSortMap, the sort {sub}+-- will be applied to Bool and decomposed which will throw an error.++-- | Given a list of equations between types, return a substitution+-- which makes all the equations satisfied (or fail if it is not+-- possible).+--+-- This is not the most efficient way to implement unification but+-- it is simple.+unify :: TyDefCtx -> [(Type, Type)] -> Maybe S+unify = unify' (==)++-- | Given a list of equations between types, return a substitution+-- which makes all the equations equal *up to* identifying all base+-- types. So, for example, Int = Nat weakly unifies but Int = (Int+-- -> Int) does not. This is used to check whether subtyping+-- constraints are structurally sound before doing constraint+-- simplification/solving, to ensure termination.+weakUnify :: TyDefCtx -> [(Type, Type)] -> Maybe S+weakUnify = unify' (\_ _ -> True)++-- | Given a list of equations between types, return a substitution+-- which makes all the equations satisfied (or fail if it is not+-- possible), up to the given comparison on base types.+unify' :: (BaseTy -> BaseTy -> Bool) -> TyDefCtx+ -> [(Type, Type)] -> Maybe S+unify' baseEq tyDefns eqs = evalStateT (go eqs) S.empty+ where+ go :: [(Type, Type)] -> StateT (Set (Type,Type)) Maybe S+ go [] = return idS+ go (e:es) = do+ u <- unifyOne e+ case u of+ Left sub -> (@@ sub) <$> go (applySubst sub es)+ Right newEs -> go (newEs ++ es)++ unifyOne :: (Type, Type) -> StateT (Set (Type,Type)) Maybe (Either S [(Type, Type)])+ unifyOne pair = do+ seen <- get+ case pair `S.member` seen of+ True -> return $ Left idS+ False -> unifyOne' pair++ unifyOne' :: (Type, Type) -> StateT (Set (Type,Type)) Maybe (Either S [(Type, Type)])++ unifyOne' (ty1, ty2)+ | ty1 == ty2 = return $ Left idS++ unifyOne' (TyVar x, ty2)+ | occurs x ty2 = mzero+ | otherwise = return $ Left (x |-> ty2)+ unifyOne' (ty1, x@(TyVar _))+ = unifyOne (x, ty1)++ -- At this point we know ty2 isn't the same skolem nor a unification variable.+ -- Skolems don't unify with anything.+ unifyOne' (TySkolem _, _) = mzero+ unifyOne' (_, TySkolem _) = mzero++ -- Unify two container types: unify the container descriptors as+ -- well as the type arguments+ unifyOne' p@(TyCon (CContainer ctr1) tys1, TyCon (CContainer ctr2) tys2) = do+ modify (S.insert p)+ return $ Right ((TyAtom ctr1, TyAtom ctr2) : zip tys1 tys2)++ -- If one of the types to be unified is a user-defined type,+ -- unfold its definition before continuing the matching+ unifyOne' p@(TyCon (CUser t) tys1, ty2) = do+ modify (S.insert p)+ case M.lookup t tyDefns of+ Nothing -> mzero+ Just (TyDefBody _ body) -> return $ Right [(body tys1, ty2)]++ unifyOne' p@(ty1, TyCon (CUser t) tys2) = do+ modify (S.insert p)+ case M.lookup t tyDefns of+ Nothing -> mzero+ Just (TyDefBody _ body) -> return $ Right [(ty1, body tys2)]++ -- Unify any other pair of type constructor applications: the type+ -- constructors must match exactly+ unifyOne' p@(TyCon c1 tys1, TyCon c2 tys2)+ | c1 == c2 = do+ modify (S.insert p)+ return $ Right (zip tys1 tys2)+ | otherwise = mzero+ unifyOne' (TyAtom (ABase b1), TyAtom (ABase b2))+ | baseEq b1 b2 = return $ Left idS+ | otherwise = mzero+ unifyOne' _ = mzero -- Atom = Cons+++equate :: TyDefCtx -> [Type] -> Maybe S+equate tyDefns tys = unify tyDefns eqns+ where+ eqns = zip tys (tail tys)++occurs :: Name Type -> Type -> Bool+occurs x = anyOf fv (==x)+++unifyAtoms :: TyDefCtx -> [Atom] -> Maybe (Substitution Atom)+unifyAtoms tyDefns = fmap (fmap fromTyAtom) . equate tyDefns . map TyAtom+ where+ fromTyAtom (TyAtom a) = a+ fromTyAtom _ = error "fromTyAtom on non-TyAtom!"++unifyUAtoms :: TyDefCtx -> [UAtom] -> Maybe (Substitution UAtom)+unifyUAtoms tyDefns = fmap (fmap fromTyAtom) . equate tyDefns . map (TyAtom . uatomToAtom)+ where+ fromTyAtom (TyAtom (ABase b)) = UB b+ fromTyAtom (TyAtom (AVar (U v))) = UV v+ fromTyAtom _ = error "fromTyAtom on wrong thing!"
+ src/Disco/Typecheck/Util.hs view
@@ -0,0 +1,151 @@++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Typecheck.Util+-- Copyright : (c) 2016 disco team (see LICENSE)+-- License : BSD-style (see LICENSE)+-- Maintainer : byorgey@gmail.com+--+-- Definition of type contexts, type errors, and various utilities+-- used during type checking.+--+-----------------------------------------------------------------------------++module Disco.Typecheck.Util where++import Disco.Effects.Fresh+import Polysemy+import Polysemy.Error+import Polysemy.Output+import Polysemy.Reader+import Polysemy.Writer+import Unbound.Generics.LocallyNameless (Name, bind, string2Name)++import qualified Data.Map as M+import Data.Tuple (swap)+import Prelude hiding (lookup)++import Disco.AST.Surface+import Disco.Context+import Disco.Messages+import Disco.Names (ModuleName)+import Disco.Typecheck.Constraints+import Disco.Typecheck.Solve+import Disco.Types++------------------------------------------------------------+-- Contexts+------------------------------------------------------------++-- | A typing context is a mapping from term names to types.+type TyCtx = Ctx Term PolyType++------------------------------------------------------------+-- Errors+------------------------------------------------------------++-- | Potential typechecking errors.+data TCError+ = Unbound (Name Term) -- ^ Encountered an unbound variable+ | Ambiguous (Name Term) [ModuleName] -- ^ Encountered an ambiguous name.+ | NoType (Name Term) -- ^ No type is specified for a definition+ | NotCon Con Term Type -- ^ The type of the term should have an+ -- outermost constructor matching Con, but+ -- it has type 'Type' instead+ | EmptyCase -- ^ Case analyses cannot be empty.+ | PatternType Con Pattern Type -- ^ The given pattern should have the type, but it doesn't.+ -- instead it has a kind of type given by the Con.+ | DuplicateDecls (Name Term) -- ^ Duplicate declarations.+ | DuplicateDefns (Name Term) -- ^ Duplicate definitions.+ | DuplicateTyDefns String -- ^ Duplicate type definitions.+ | CyclicTyDef String -- ^ Cyclic type definition.+ | NumPatterns -- ^ # of patterns does not match type in definition+ | NoSearch Type -- ^ Type can't be quantified over.+ | Unsolvable SolveError -- ^ The constraint solver couldn't find a solution.+ | NotTyDef String -- ^ An undefined type name was used.+ | NoTWild -- ^ Wildcards are not allowed in terms.+ | NotEnoughArgs Con -- ^ Not enough arguments provided to type constructor.+ | TooManyArgs Con -- ^ Too many arguments provided to type constructor.+ | UnboundTyVar (Name Type) -- ^ Unbound type variable+ | NoPolyRec String [String] [Type] -- ^ Polymorphic recursion is not allowed+ | NoError -- ^ Not an error. The identity of the+ -- @Monoid TCError@ instance.+ deriving Show++instance Semigroup TCError where+ _ <> r = r++-- | 'TCError' is a monoid where we simply discard the first error.+instance Monoid TCError where+ mempty = NoError+ mappend = (<>)++------------------------------------------------------------+-- Constraints+------------------------------------------------------------++-- | Emit a constraint.+constraint :: Member (Writer Constraint) r => Constraint -> Sem r ()+constraint = tell++-- | Emit a list of constraints.+constraints :: Member (Writer Constraint) r => [Constraint] -> Sem r ()+constraints = constraint . cAnd++-- | Close over the current constraint with a forall.+forAll :: Member (Writer Constraint) r => [Name Type] -> Sem r a -> Sem r a+forAll nms = censor (CAll . bind nms)++-- | Run a computation that generates constraints, returning the+-- generated 'Constraint' along with the output. Note that this+-- locally dispatches the constraint writer effect.+--+-- This function is somewhat low-level; typically you should use+-- 'solve' instead, which also solves the generated constraints.+withConstraint :: Sem (Writer Constraint ': r) a -> Sem r (a, Constraint)+withConstraint = fmap swap . runWriter++-- | Run a computation and solve its generated constraint, returning+-- the resulting substitution (or failing with an error). Note that+-- this locally dispatches the constraint writer effect.+solve+ :: Members '[Reader TyDefCtx, Error TCError, Output Message] r+ => Sem (Writer Constraint ': r) a -> Sem r (a, S)+solve m = do+ (a, c) <- withConstraint m+ tds <- ask @TyDefCtx+ res <- runSolve . solveConstraint tds $ c+ case res of+ Left e -> throw (Unsolvable e)+ Right s -> return (a, s)++------------------------------------------------------------+-- Contexts+------------------------------------------------------------++-- | Look up the definition of a named type. Throw a 'NotTyDef' error+-- if it is not found.+lookupTyDefn ::+ Members '[Reader TyDefCtx, Error TCError] r+ => String -> [Type] -> Sem r Type+lookupTyDefn x args = do+ d <- ask @TyDefCtx+ case M.lookup x d of+ Nothing -> throw (NotTyDef x)+ Just (TyDefBody _ body) -> return $ body args++-- | Run a subcomputation with an extended type definition context.+withTyDefns :: Member (Reader TyDefCtx) r => TyDefCtx -> Sem r a -> Sem r a+withTyDefns tyDefnCtx = local (M.union tyDefnCtx)++------------------------------------------------------------+-- Fresh name generation+------------------------------------------------------------++-- | Generate a type variable with a fresh name.+freshTy :: Member Fresh r => Sem r Type+freshTy = TyVar <$> fresh (string2Name "a")++-- | Generate a fresh variable as an atom.+freshAtom :: Member Fresh r => Sem r Atom+freshAtom = AVar . U <$> fresh (string2Name "c")
+ src/Disco/Types.hs view
@@ -0,0 +1,778 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -fno-warn-orphans #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Types+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- The "Disco.Types" module defines the set of types used in the disco+-- language type system, along with various utility functions.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Types+ (+ -- * Disco language types+ -- ** Atomic types++ BaseTy(..), isCtr, Var(..), Ilk(..), pattern U, pattern S+ , Atom(..)+ , isVar, isBase, isSkolem+ , UAtom(..), uisVar, uatomToAtom, uatomToEither++ -- ** Type constructors++ , Con(..)+ , pattern CList, pattern CBag, pattern CSet++ -- ** Types++ , Type(..)++ , pattern TyVar+ , pattern TySkolem+ , pattern TyVoid+ , pattern TyUnit+ , pattern TyBool+ , pattern TyProp+ , pattern TyN+ , pattern TyZ+ , pattern TyF+ , pattern TyQ+ , pattern TyC+ -- , pattern TyFin+ , pattern (:->:)+ , pattern (:*:)+ , pattern (:+:)+ , pattern TyList+ , pattern TyBag+ , pattern TySet+ , pattern TyGraph+ , pattern TyMap+ , pattern TyContainer+ , pattern TyUser+ , pattern TyString++ -- ** Quantified types++ , PolyType(..)+ , toPolyType, closeType++ -- * Type predicates++ , isNumTy, isEmptyTy, isFiniteTy, isSearchable++ -- * Type substitutions++ , Substitution, atomToTypeSubst, uatomToTypeSubst++ -- * Strictness+ , Strictness(..), strictness++ -- * Utilities+ , isTyVar+ , containerVars+ , countType+ , unpair+ , S+ , TyDefBody(..)+ , TyDefCtx++ -- * HasType class+ , HasType(..)+ )+ where++import Data.Coerce+import Data.Data (Data)+import Disco.Data ()+import GHC.Generics (Generic)+import Unbound.Generics.LocallyNameless hiding (lunbind)++import Control.Lens (toListOf)+import Data.List (nub)+import Data.Map (Map)+import qualified Data.Map as M+import Data.Set (Set)+import qualified Data.Set as S+import Data.Void+import Math.Combinatorics.Exact.Binomial (choose)++import Disco.Effects.LFresh++import Disco.Pretty hiding ((<>))+import Disco.Subst (Substitution)+import Disco.Types.Qualifiers++--------------------------------------------------+-- Disco types+--------------------------------------------------++----------------------------------------+-- Base types++-- | Base types are the built-in types which form the basis of the+-- disco type system, out of which more complex types can be built.+data BaseTy where++ -- | The void type, with no inhabitants.+ Void :: BaseTy++ -- | The unit type, with one inhabitant.+ Unit :: BaseTy++ -- | Booleans.+ B :: BaseTy++ -- | Propositions.+ P :: BaseTy++ -- | Natural numbers.+ N :: BaseTy++ -- | Integers.+ Z :: BaseTy++ -- | Fractionals (i.e. nonnegative rationals).+ F :: BaseTy++ -- | Rationals.+ Q :: BaseTy++ -- | Unicode characters.+ C :: BaseTy++ -- Finite types. The single argument is a natural number defining+ -- the exact number of inhabitants.+ -- Fin :: Integer -> BaseTy++ -- | Set container type. It's a bit odd putting these here since+ -- they have kind * -> * and all the other base types have kind *;+ -- but there's nothing fundamentally wrong with it and in+ -- particular this allows us to reuse all the existing constraint+ -- solving machinery for container subtyping.+ CtrSet :: BaseTy++ -- | Bag container type.+ CtrBag :: BaseTy++ -- | List container type.+ CtrList :: BaseTy++ deriving (Show, Eq, Ord, Generic, Data, Alpha, Subst BaseTy, Subst Atom, Subst UAtom, Subst Type)++instance Pretty BaseTy where+ pretty = \case+ Void -> text "Void"+ Unit -> text "Unit"+ B -> text "Bool"+ P -> text "Prop"+ N -> text "ℕ"+ Z -> text "ℤ"+ Q -> text "ℚ"+ F -> text "𝔽"+ C -> text "Char"+ CtrList -> text "List"+ CtrBag -> text "Bag"+ CtrSet -> text "Set"++-- | Test whether a 'BaseTy' is a container (set, bag, or list).+isCtr :: BaseTy -> Bool+isCtr = (`elem` [CtrSet, CtrBag, CtrList])++----------------------------------------+-- Type variables++-- | 'Var' represents /type variables/, that is, variables which stand+-- for some type. There are two kinds of type variables:+--+-- * /Unification variables/ stand for an unknown type, about which+-- we might learn additional information during the typechecking+-- process. For example, given a function of type @List a -> List+-- a@, if we typecheck an application of the function to the list+-- @[1,2,3]@, we would learn that @List a@ has to be @List N@, and+-- hence that @a@ has to be @N@.+--+-- * /Skolem variables/ stand for a fixed generic type, and are used+-- to typecheck universally quantified type signatures (/i.e./+-- type signatures which contain type variables). For example, if+-- a function has the declared type @List a -> N@, it amounts to a+-- claim that the function will work no matter what type is+-- substituted for @a@. We check this by making up a new skolem+-- variable for @a@. Skolem variables are equal to themselves,+-- but nothing else. In contrast to a unification variable,+-- "learning something" about a skolem variable is an error: it+-- means that the function will only work for certain types, in+-- contradiction to its claim to work for any type at all.+data Ilk = Skolem | Unification+ deriving (Eq, Ord, Read, Show, Generic, Data, Alpha, Subst Atom, Subst Type)++instance Pretty Ilk where+ pretty = \case+ Skolem -> "S"+ Unification -> "U"++-- | 'Var' represents /type variables/, that is, variables which stand+-- for some type.+data Var where+ V :: Ilk -> Name Type -> Var+ deriving (Show, Eq, Ord, Generic, Data, Alpha, Subst Atom, Subst Type)++pattern U :: Name Type -> Var+pattern U v = V Unification v++pattern S :: Name Type -> Var+pattern S v = V Skolem v++{-# COMPLETE U, S #-}++----------------------------------------+-- Atomic types++-- | An /atomic type/ is either a base type or a type variable. The+-- alternative is a /compound type/ which is built out of type+-- constructors. The reason we split out the concept of atomic+-- types into its own data type 'Atom' is because constraints+-- involving compound types can always be simplified/translated into+-- constraints involving only atomic types. After that+-- simplification step, we want to be able to work with collections+-- of constraints that are guaranteed to contain only atomic types.+data Atom where+ AVar :: Var -> Atom+ ABase :: BaseTy -> Atom+ deriving (Show, Eq, Ord, Generic, Data, Alpha, Subst Type)++instance Subst Atom Atom where+ isvar (AVar (U x)) = Just (SubstName (coerce x))+ isvar _ = Nothing++instance Pretty Atom where+ pretty = \case+ AVar (U v) -> pretty v+ AVar (S v) -> text "$" <> pretty v+ ABase b -> pretty b++-- | Is this atomic type a variable?+isVar :: Atom -> Bool+isVar (AVar _) = True+isVar _ = False++-- | Is this atomic type a base type?+isBase :: Atom -> Bool+isBase = not . isVar++-- | Is this atomic type a skolem variable?+isSkolem :: Atom -> Bool+isSkolem (AVar (S _)) = True+isSkolem _ = False++-- | /Unifiable/ atomic types are the same as atomic types but without+-- skolem variables. Hence, a unifiable atomic type is either a base+-- type or a unification variable.+--+-- Again, the reason this has its own type is that at some stage of+-- the typechecking/constraint solving process, these should be the+-- only things around; we can get rid of skolem variables because+-- either they impose no constraints, or result in an error if they+-- are related to something other than themselves. After checking+-- these things, we can just focus on base types and unification+-- variables.+data UAtom where+ UB :: BaseTy -> UAtom+ UV :: Name Type -> UAtom+ deriving (Show, Eq, Ord, Generic, Alpha, Subst BaseTy)++instance Subst UAtom UAtom where+ isvar (UV x) = Just (SubstName (coerce x))+ isvar _ = Nothing++instance Pretty UAtom where+ pretty (UB b) = pretty b+ pretty (UV n) = pretty n++-- | Is this unifiable atomic type a (unification) variable?+uisVar :: UAtom -> Bool+uisVar (UV _) = True+uisVar _ = False++-- | Convert a unifiable atomic type into a regular atomic type.+uatomToAtom :: UAtom -> Atom+uatomToAtom (UB b) = ABase b+uatomToAtom (UV x) = AVar (U x)++-- | Convert a unifiable atomic type to an explicit @Either@ type.+uatomToEither :: UAtom -> Either BaseTy (Name Type)+uatomToEither (UB b) = Left b+uatomToEither (UV v) = Right v++----------------------------------------+-- Type constructors++-- | /Compound types/, such as functions, product types, and sum+-- types, are an application of a /type constructor/ to one or more+-- argument types.+data Con where+ -- | Function type constructor, @T1 -> T2@.+ CArr :: Con+ -- | Product type constructor, @T1 * T2@.+ CProd :: Con+ -- | Sum type constructor, @T1 + T2@.+ CSum :: Con++ -- | Container type (list, bag, or set) constructor. Note this+ -- looks like it could contain any 'Atom', but it will only ever+ -- contain either a type variable or a 'CtrList', 'CtrBag', or+ -- 'CtrSet'.+ --+ -- See also 'CList', 'CBag', and 'CSet'.+ CContainer :: Atom -> Con+++ -- | Key value maps, Map k v+ CMap :: Con++ -- | Graph constructor, Graph a+ CGraph :: Con++ -- | The name of a user defined algebraic datatype.+ CUser :: String -> Con++ deriving (Show, Eq, Ord, Generic, Data, Alpha)++instance Pretty Con where+ pretty = \case+ CMap -> text "Map"+ CGraph -> text "Graph"+ CUser s -> text s+ CList -> text "List"+ CBag -> text "Bag"+ CSet -> text "Set"+ CContainer v -> pretty v+ c -> error $ "Impossible: got Con " ++ show c ++ " in pretty @Con"++-- | 'CList' is provided for convenience; it represents a list type+-- constructor (/i.e./ @List a@).+pattern CList :: Con+pattern CList = CContainer (ABase CtrList)++-- | 'CBag' is provided for convenience; it represents a bag type+-- constructor (/i.e./ @Bag a@).+pattern CBag :: Con+pattern CBag = CContainer (ABase CtrBag)++-- | 'CSet' is provided for convenience; it represents a set type+-- constructor (/i.e./ @Set a@).+pattern CSet :: Con+pattern CSet = CContainer (ABase CtrSet)++{-# COMPLETE CArr, CProd, CSum, CList, CBag, CSet, CGraph, CMap, CUser #-}++----------------------------------------+-- Types++-- | The main data type for representing types in the disco language.+-- A type can be either an atomic type, or the application of a type+-- constructor to one or more type arguments.+--+-- @Type@s are broken down into two cases (@TyAtom@ and @TyCon@) for+-- ease of implementation: there are many situations where all atoms+-- can be handled generically in one way and all type constructors+-- can be handled generically in another. However, using this+-- representation to write down specific types is tedious; for+-- example, to represent the type @N -> a@ one must write something+-- like @TyCon CArr [TyAtom (ABase N), TyAtom (AVar (U a))]@. For+-- this reason, pattern synonyms such as ':->:', 'TyN', and+-- 'TyVar' are provided so that one can use them to construct and+-- pattern-match on types when convenient. For example, using these+-- synonyms the foregoing example can be written @TyN :->: TyVar a@.+data Type where++ -- | Atomic types (variables and base types), /e.g./ @N@, @Bool@, /etc./+ TyAtom :: Atom -> Type++ -- | Application of a type constructor to type arguments, /e.g./ @N+ -- -> Bool@ is the application of the arrow type constructor to the+ -- arguments @N@ and @Bool@.+ TyCon :: Con -> [Type] -> Type++ deriving (Show, Eq, Ord, Generic, Data, Alpha)++instance Pretty Type where+ pretty (TyAtom a) = pretty a+ pretty (ty1 :->: ty2) = withPA tarrPA $+ lt (pretty ty1) <+> text "→" <+> rt (pretty ty2)+ pretty (ty1 :*: ty2) = withPA tmulPA $+ lt (pretty ty1) <+> text "×" <+> rt (pretty ty2)+ pretty (ty1 :+: ty2) = withPA taddPA $+ lt (pretty ty1) <+> text "+" <+> rt (pretty ty2)+ pretty (TyCon c []) = pretty c+ pretty (TyCon c tys) = do+ ds <- setPA initPA $ punctuate (text ",") (map pretty tys)+ pretty c <> parens (hsep ds)++instance Subst Type Qualifier+instance Subst Type Rational where+ subst _ _ = id+ substs _ = id+instance Subst Type Void where+ subst _ _ = id+ substs _ = id+instance Subst Type Con where+ isCoerceVar (CContainer (AVar (U x)))+ = Just (SubstCoerce x substCtrTy)+ where+ substCtrTy (TyAtom a) = Just (CContainer a)+ substCtrTy _ = Nothing+ isCoerceVar _ = Nothing+instance Subst Type Type where+ isvar (TyAtom (AVar (U x))) = Just (SubstName x)+ isvar _ = Nothing++pattern TyVar :: Name Type -> Type+pattern TyVar v = TyAtom (AVar (U v))++pattern TySkolem :: Name Type -> Type+pattern TySkolem v = TyAtom (AVar (S v))++pattern TyVoid :: Type+pattern TyVoid = TyAtom (ABase Void)++pattern TyUnit :: Type+pattern TyUnit = TyAtom (ABase Unit)++pattern TyBool :: Type+pattern TyBool = TyAtom (ABase B)++pattern TyProp :: Type+pattern TyProp = TyAtom (ABase P)++pattern TyN :: Type+pattern TyN = TyAtom (ABase N)++pattern TyZ :: Type+pattern TyZ = TyAtom (ABase Z)++pattern TyF :: Type+pattern TyF = TyAtom (ABase F)++pattern TyQ :: Type+pattern TyQ = TyAtom (ABase Q)++pattern TyC :: Type+pattern TyC = TyAtom (ABase C)+++-- pattern TyFin :: Integer -> Type+-- pattern TyFin n = TyAtom (ABase (Fin n))++infixr 5 :->:++pattern (:->:) :: Type -> Type -> Type+pattern (:->:) ty1 ty2 = TyCon CArr [ty1, ty2]++infixr 7 :*:++pattern (:*:) :: Type -> Type -> Type+pattern (:*:) ty1 ty2 = TyCon CProd [ty1, ty2]++infixr 6 :+:++pattern (:+:) :: Type -> Type -> Type+pattern (:+:) ty1 ty2 = TyCon CSum [ty1, ty2]++pattern TyList :: Type -> Type+pattern TyList elTy = TyCon CList [elTy]++pattern TyBag :: Type -> Type+pattern TyBag elTy = TyCon CBag [elTy]++pattern TySet :: Type -> Type+pattern TySet elTy = TyCon CSet [elTy]++pattern TyContainer :: Atom -> Type -> Type+pattern TyContainer c elTy = TyCon (CContainer c) [elTy]++pattern TyGraph :: Type -> Type+pattern TyGraph elTy = TyCon CGraph [elTy]++pattern TyMap :: Type -> Type -> Type+pattern TyMap tyKey tyValue = TyCon CMap [tyKey, tyValue]++-- | An application of a user-defined type.+pattern TyUser :: String -> [Type] -> Type+pattern TyUser nm args = TyCon (CUser nm) args++pattern TyString :: Type+pattern TyString = TyList TyC++{-# COMPLETE+ TyVar, TySkolem, TyVoid, TyUnit, TyBool, TyProp, TyN, TyZ, TyF, TyQ, TyC,+ (:->:), (:*:), (:+:), TyList, TyBag, TySet, TyGraph, TyMap, TyUser #-}++-- | Is this a type variable?+isTyVar :: Type -> Bool+isTyVar (TyAtom (AVar _)) = True+isTyVar _ = False++-- orphans+instance (Ord a, Subst t a) => Subst t (Set a) where+ subst x t = S.map (subst x t)+ substs s = S.map (substs s)+instance (Ord k, Subst t a) => Subst t (Map k a) where+ subst x t = M.map (subst x t)+ substs s = M.map (substs s)++-- | The definition of a user-defined type contains:+--+-- * The actual names of the type variable arguments used in the+-- definition (we keep these around only to help with+-- pretty-printing)+-- * A function representing the body of the definition. It takes a+-- list of type arguments and returns the body of the definition+-- with the type arguments substituted.+--+-- We represent type definitions this way (using a function, as+-- opposed to a chunk of abstract syntax) because it makes some+-- things simpler, and we don't particularly need to do anything+-- more complicated.+data TyDefBody = TyDefBody [String] ([Type] -> Type)++instance Show TyDefBody where+ show _ = "<tydef>"++-- | A 'TyDefCtx' is a mapping from type names to their corresponding+-- definitions.+type TyDefCtx = M.Map String TyDefBody++-- | Pretty-print a type definition.+instance Pretty (String, TyDefBody) where++ pretty (tyName, TyDefBody ps body)+ = "type" <+> (text tyName <> prettyArgs ps) <+> text "=" <+> pretty (body (map (TyVar . string2Name) ps))+ where+ prettyArgs [] = empty+ prettyArgs _ = do+ ds <- punctuate (text ",") (map text ps)+ parens (hsep ds)++---------------------------------+-- Universally quantified types++-- | 'PolyType' represents a polymorphic type of the form @forall a1+-- a2 ... an. ty@ (note, however, that n may be 0, that is, we can+-- have a "trivial" polytype which quantifies zero variables).+newtype PolyType = Forall (Bind [Name Type] Type)+ deriving (Show, Generic, Data, Alpha, Subst Type)++-- | Pretty-print a polytype. Note that we never explicitly print+-- @forall@; quantification is implicit, as in Haskell.+instance Pretty PolyType where+ pretty (Forall bnd) = lunbind bnd $+ \(_, body) -> pretty body++-- | Convert a monotype into a trivial polytype that does not quantify+-- over any type variables. If the type can contain free type+-- variables, use 'closeType' instead.+toPolyType :: Type -> PolyType+toPolyType ty = Forall (bind [] ty)++-- | Convert a monotype into a polytype by quantifying over all its+-- free type variables.+closeType :: Type -> PolyType+closeType ty = Forall (bind (nub $ toListOf fv ty) ty)++--------------------------------------------------+-- Counting inhabitants+--------------------------------------------------++-- | Compute the number of inhabitants of a type. @Nothing@ means the+-- type is countably infinite.+countType :: Type -> Maybe Integer+countType TyVoid = Just 0+countType TyUnit = Just 1+countType TyBool = Just 2+-- countType (TyFin n) = Just n+countType TyC = Just (17 * 2^(16 :: Integer))+countType (ty1 :+: ty2) = (+) <$> countType ty1 <*> countType ty2+countType (ty1 :*: ty2)+ | isEmptyTy ty1 = Just 0+ | isEmptyTy ty2 = Just 0+ | otherwise = (*) <$> countType ty1 <*> countType ty2+countType (ty1 :->: ty2) =+ case (countType ty1, countType ty2) of+ (Just 0, _) -> Just 1+ (_, Just 0) -> Just 0+ (_, Just 1) -> Just 1+ (c1, c2) -> (^) <$> c2 <*> c1+countType (TyList ty)+ | isEmptyTy ty = Just 1+ | otherwise = Nothing+countType (TyBag ty)+ | isEmptyTy ty = Just 1+ | otherwise = Nothing+countType (TySet ty) = (2^) <$> countType ty++ -- t = number of elements in vertex type.+ -- n = number of vertices in the graph.+ -- For each n in [0..t], we can choose which n values to use for the+ -- vertices; then for each ordered pair of vertices (u,v)+ -- (including the possibility that u = v), we choose whether or+ -- not there is a directed edge u -> v.+ --+ -- https://oeis.org/A135748++countType (TyGraph ty) =+ (\t -> sum $ map (\n -> (t `choose` n) * 2^(n*n)) [0 .. t]) <$>+ countType ty++countType (TyMap tyKey tyValue)+ | isEmptyTy tyKey = Just 1 -- If we can't have any keys or values,+ | isEmptyTy tyValue = Just 1 -- only option is empty map+ | otherwise = (\k v -> (v+1) ^ k) <$> countType tyKey <*> countType tyValue+ -- (v+1)^k since for each key, we can choose among v values to associate with it,+ -- or we can choose to not have the key in the map.++-- All other types are infinite. (TyN, TyZ, TyQ, TyF)+countType _ = Nothing++--------------------------------------------------+-- Type predicates+--------------------------------------------------++-- | Check whether a type is a numeric type (@N@, @Z@, @F@, @Q@, or @Zn@).+isNumTy :: Type -> Bool+-- isNumTy (TyFin _) = True+isNumTy ty = ty `elem` [TyN, TyZ, TyF, TyQ]++-- | Decide whether a type is empty, /i.e./ uninhabited.+isEmptyTy :: Type -> Bool+isEmptyTy ty+ | Just 0 <- countType ty = True+ | otherwise = False++-- | Decide whether a type is finite.+isFiniteTy :: Type -> Bool+isFiniteTy ty+ | Just _ <- countType ty = True+ | otherwise = False++-- XXX coinductively check whether user-defined types are searchable+-- e.g. L = Unit + N * L ought to be searchable.+-- | Decide whether a type is searchable, i.e. effectively enumerable.+isSearchable :: Type -> Bool+isSearchable TyProp = False+isSearchable ty+ | isNumTy ty = True+ | isFiniteTy ty = True+isSearchable (TyList ty) = isSearchable ty+isSearchable (TySet ty) = isSearchable ty+isSearchable (ty1 :+: ty2) = isSearchable ty1 && isSearchable ty2+isSearchable (ty1 :*: ty2) = isSearchable ty1 && isSearchable ty2+isSearchable (ty1 :->: ty2) = isFiniteTy ty1 && isSearchable ty2+isSearchable _ = False++--------------------------------------------------+-- Strictness+--------------------------------------------------++-- | @Strictness@ represents the strictness (either strict or lazy) of+-- a function application or let-expression.+data Strictness = Strict | Lazy+ deriving (Eq, Show, Generic, Alpha)++-- | Numeric types are strict; others are lazy.+strictness :: Type -> Strictness+strictness ty+ | isNumTy ty = Strict+ | otherwise = Lazy++--------------------------------------------------+-- Utilities+--------------------------------------------------++-- | Decompose a nested product @T1 * (T2 * ( ... ))@ into a list of+-- types.+unpair :: Type -> [Type]+unpair (ty1 :*: ty2) = ty1 : unpair ty2+unpair ty = [ty]++-- | Define @S@ as a substitution on types (the most common kind)+-- for convenience.+type S = Substitution Type++-- | Convert a substitution on atoms into a substitution on types.+atomToTypeSubst :: Substitution Atom -> Substitution Type+atomToTypeSubst = fmap TyAtom++-- | Convert a substitution on unifiable atoms into a substitution on+-- types.+uatomToTypeSubst :: Substitution UAtom -> Substitution Type+uatomToTypeSubst = atomToTypeSubst . fmap uatomToAtom++-- | Return a set of all the free container variables in a type.+containerVars :: Type -> Set (Name Type)+containerVars (TyCon (CContainer (AVar (U x))) tys)+ = x `S.insert` foldMap containerVars tys+containerVars (TyCon _ tys) = foldMap containerVars tys+containerVars _ = S.empty++------------------------------------------------------------+-- HasType class+------------------------------------------------------------++-- | A type class for things whose type can be extracted or set.+class HasType t where++ -- | Get the type of a thing.+ getType :: t -> Type++ -- | Set the type of a thing, when that is possible; the default+ -- implementation is for 'setType' to do nothing.+ setType :: Type -> t -> t+ setType _ = id+++++++++++++++++++++++++++++++++++++
+ src/Disco/Types/Qualifiers.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE OverloadedStrings #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Types.Qualifiers+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- Type qualifiers and sorts.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Types.Qualifiers where++import GHC.Generics+import Unbound.Generics.LocallyNameless++import Data.Set (Set)+import qualified Data.Set as S++import Disco.Pretty+import Disco.Syntax.Operators++------------------------------------------------------------+-- Qualifiers+------------------------------------------------------------++-- | A "qualifier" is kind of like a type class in Haskell; but unlike+-- Haskell, disco users cannot define their own. Rather, there is a+-- finite fixed list of qualifiers supported by disco. For example,+-- @QSub@ denotes types which support a subtraction operation. Each+-- qualifier corresponds to a set of types which satisfy it (see+-- 'hasQual' and 'qualRules').+--+-- These qualifiers generally arise from uses of various operations.+-- For example, the expression @\\x y. x - y@ would be inferred to+-- have a type @a -> a -> a [subtractive a]@, that is, a function of+-- type @a -> a -> a@ where @a@ is any type that supports+-- subtraction.+--+-- These qualifiers can appear in a 'CQual' constraint; see+-- "Disco.Typecheck.Constraint".+data Qualifier+ = QNum -- ^ Numeric, i.e. a semiring supporting + and *+ | QSub -- ^ Subtractive, i.e. supports -+ | QDiv -- ^ Divisive, i.e. supports /+ | QCmp -- ^ Comparable, i.e. supports decidable ordering/comparison (see Note [QCmp])+ | QEnum -- ^ Enumerable, i.e. supports ellipsis notation [x .. y]+ | QBool -- ^ Boolean, i.e. supports and, or, not (Bool or Prop)+ | QBasic -- ^ Things that do not involve Prop.+ | QSimple -- ^ Things for which we can derive a *Haskell* Ord instance+ deriving (Show, Eq, Ord, Generic, Alpha)++instance Pretty Qualifier where+ pretty = \case+ QNum -> "num"+ QSub -> "sub"+ QDiv -> "div"+ QCmp -> "cmp"+ QEnum -> "enum"+ QBool -> "bool"+ QBasic -> "basic"+ QSimple -> "simple"++-- ~~~~ Note [QCmp]+--+-- XXX edit this! I don't think we actually need type info for+-- comparisons at runtime any more, if we disallow functions from+-- being QCmp. With the switch to eager semantics + disallowing+-- function comparison, it's now the case that QCmp should mean+-- *decidable* (terminating) comparison.+--+-- It used to be the case that every type in disco supported+-- (semi-decidable) linear ordering, so in one sense the QCmp+-- constraint was unnecessary. However, in order to do a comparison we+-- need to know the type at runtime. Currently, we use QCmp to track+-- which types have comparisons done on them, and reject any type+-- variables with a QCmp constraint (just as we reject any other type+-- variables with remaining constraints). Every type with comparisons+-- done on it must be statically known at compile time.+--+-- However, there's now another reason: the Prop type does not support+-- comparisons at all.+--+-- Eventually, one could imagine compiling to something like System F+-- with explicit type lambdas and applications; then the QCmp+-- constraints would tell us which type applications need to be kept+-- and which can be erased.++-- | A helper function that returns the appropriate qualifier for a+-- binary arithmetic operation.+bopQual :: BOp -> Qualifier+bopQual Add = QNum+bopQual Mul = QNum+bopQual Div = QDiv+bopQual Sub = QSub+bopQual SSub = QNum+-- bopQual And = QBool+-- bopQual Or = QBool+-- bopQual Impl = QBool+bopQual _ = error "No qualifier for binary operation"++------------------------------------------------------------+-- Sorts+------------------------------------------------------------++-- | A 'Sort' represents a set of qualifiers, and also represents a+-- set of types (in general, the intersection of the sets+-- corresponding to the qualifiers).+type Sort = Set Qualifier++-- | The special sort \(\top\) which includes all types.+topSort :: Sort+topSort = S.empty
+ src/Disco/Types/Rules.hs view
@@ -0,0 +1,270 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Types.Rules+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- "Disco.Types.Rules" defines some generic rules about arity,+-- subtyping, and sorts for disco base types.+--+-----------------------------------------------------------------------------++-- SPDX-License-Identifier: BSD-3-Clause++module Disco.Types.Rules+ ( -- * Arity++ Variance(..), arity++ -- * Qualifiers+ , Qualifier(..), bopQual++ -- * Sorts+ , Sort, topSort++ -- * Subtyping rules++ , Dir(..), other++ , isSubA, isSubB, isDirB+ , supertypes, subtypes, dirtypes++ -- * Qualifier and sort rules++ , hasQual, hasSort+ , qualRules, sortRules+ , pickSortBaseTy+ )+ where++import Control.Monad ((>=>))+import Data.List (foldl')+import Data.Map (Map)+import qualified Data.Map as M+import qualified Data.Set as S++import Disco.Types+import Disco.Types.Qualifiers++------------------------------------------------------------+-- Arity+------------------------------------------------------------++-- | A particular type argument can be either co- or contravariant+-- with respect to subtyping.+data Variance = Co | Contra+ deriving (Show, Read, Eq, Ord)++-- | The arity of a type constructor is a list of variances,+-- expressing both how many type arguments the constructor takes,+-- and the variance of each argument. This is used to decompose+-- subtyping constraints.+--+-- For example, @arity CArr = [Contra, Co]@ since function arrow is+-- contravariant in its first argument and covariant in its second.+-- That is, @S1 -> T1 <: S2 -> T2@ (@<:@ means "is a subtype of") if+-- and only if @S2 <: S1@ and @T1 <: T2@.+arity :: Con -> [Variance]+arity CArr = [Contra, Co]+arity CProd = [Co, Co]+arity CSum = [Co, Co]+arity (CContainer _) = [Co]+arity CMap = [Contra, Co]+arity CGraph = [Co]+arity (CUser _) = error "Impossible! arity CUser"+ -- CUsers should always be replaced by their definitions before arity+ -- is called.++------------------------------------------------------------+-- Subtyping rules+------------------------------------------------------------++-- | A "direction" for the subtyping relation (either subtype or+-- supertype).+data Dir = SubTy | SuperTy+ deriving (Eq, Ord, Read, Show)++-- | Swap directions.+other :: Dir -> Dir+other SubTy = SuperTy+other SuperTy = SubTy++--------------------------------------------------+-- Subtype checks++-- | Check whether one atomic type is a subtype of the other. Returns+-- @True@ if either they are equal, or if they are base types and+-- 'isSubB' returns true.+isSubA :: Atom -> Atom -> Bool+isSubA a1 a2 | a1 == a2 = True+isSubA (ABase t1) (ABase t2) = isSubB t1 t2+isSubA _ _ = False++-- | Check whether one base type is a subtype of another.+isSubB :: BaseTy -> BaseTy -> Bool+isSubB b1 b2 | b1 == b2 = True+isSubB N Z = True+isSubB N F = True+isSubB N Q = True+isSubB Z Q = True+isSubB F Q = True+isSubB B P = True+isSubB _ _ = False++-- | Check whether one base type is a sub- or supertype of another.+isDirB :: Dir -> BaseTy -> BaseTy -> Bool+isDirB SubTy b1 b2 = isSubB b1 b2+isDirB SuperTy b1 b2 = isSubB b2 b1++-- | List all the supertypes of a given base type.+supertypes :: BaseTy -> [BaseTy]+supertypes N = [N, Z, F, Q]+supertypes Z = [Z, Q]+supertypes F = [F, Q]+supertypes B = [B, P]+supertypes ty = [ty]++-- | List all the subtypes of a given base type.+subtypes :: BaseTy -> [BaseTy]+subtypes Q = [Q, F, Z, N]+subtypes F = [F, N]+subtypes Z = [Z, N]+subtypes P = [P, B]+subtypes ty = [ty]++-- | List all the sub- or supertypes of a given base type.+dirtypes :: Dir -> BaseTy -> [BaseTy]+dirtypes SubTy = subtypes+dirtypes SuperTy = supertypes++------------------------------------------------------------+-- Qualifier and sort rules+------------------------------------------------------------++-- | Check whether a given base type satisfies a qualifier.+hasQual :: BaseTy -> Qualifier -> Bool+hasQual P QCmp = False -- can't compare Props+hasQual _ QCmp = True+hasQual P QBasic = False+hasQual _ QBasic = True+hasQual P QSimple = False+hasQual _ QSimple = True+-- hasQual (Fin _) q | q `elem` [QNum, QSub, QEnum] = True+-- hasQual (Fin n) QDiv = isPrime n+hasQual b QNum = b `elem` [N, Z, F, Q]+hasQual b QSub = b `elem` [Z, Q]+hasQual b QDiv = b `elem` [F, Q]+hasQual b QEnum = b `elem` [N, Z, F, Q, C]+hasQual b QBool = b `elem` [B, P]++-- | Check whether a base type has a certain sort, which simply+-- amounts to whether it satisfies every qualifier in the sort.+hasSort :: BaseTy -> Sort -> Bool+hasSort = all . hasQual++-- | 'qualRulesMap' encodes some of the rules by which applications of+-- type constructors can satisfy various qualifiers.+--+-- Each constructor maps to a set of rules. Each rule is a mapping+-- from a qualifier to the list of qualifiers needed on the type+-- constructor's arguments for the bigger type to satisfy the+-- qualifier.+--+-- Note in Disco we can get away with any given qualifier requiring+-- /at most one/ qualifier on each type argument. Then we can+-- derive the 'sortRules' by combining 'qualRules'. In general,+-- however, you could imagine some particular qualifier requiring a+-- set of qualifiers (i.e. a general sort) on a type argument. In+-- that case one would just have to encode 'sortRules' directly.+qualRulesMap :: Map Con (Map Qualifier [Maybe Qualifier])+qualRulesMap = M.fromList+ [ CProd ==> M.fromList+ [ QCmp ==> [Just QCmp, Just QCmp],+ QSimple ==> [Just QSimple, Just QSimple]+ ]+ , CSum ==> M.fromList+ [ QCmp ==> [Just QCmp, Just QCmp],+ QSimple ==> [Just QSimple, Just QSimple]+ ]+ , CList ==> M.fromList+ [ QCmp ==> [Just QCmp],+ QSimple ==> [Just QSimple]+ ]+ , CBag ==> M.fromList+ [ QCmp ==> [Just QCmp],+ QSimple ==> [Just QSimple]+ ]+ , CSet ==> M.fromList+ [ QCmp ==> [Just QCmp],+ QSimple ==> [Just QSimple]+ ]+ , CGraph ==> M.fromList+ [ QCmp ==> [Just QCmp],+ QNum ==> [Nothing]+ ]+ , CMap ==> M.fromList+ [ QCmp ==> [Just QCmp, Just QCmp]+ ]+ ]+ where+ (==>) :: a -> b -> (a,b)+ (==>) = (,)++ -- We could (theoretically) make graphs and maps also be simple values if we require the map's values are also simple.++ -- Eventually we can easily imagine adding an opt-in mode where+ -- numeric operations can be used on pairs and functions, then the+ -- qualRules would become dependent on what language extension/mode+ -- was chosen. For example we could have rules like+ --+ -- [ CArr ==> M.fromList+ -- [ QNum ==> [Nothing, Just QNum] -- (a -> b) can be +, * iff b can+ -- , QSub ==> [Nothing, Just QSub] -- ditto for subtraction+ -- , QDiv ==> [Nothing, Just QDiv] -- and division+ -- ]+ -- , CProd ==> M.fromList+ -- [ QNum ==> [Just QNum, Just QNum] -- (a,b) can be +, * iff a and b can+ -- , QSub ==> [Just QSub, Just QSub] -- etc.+ -- , QDiv ==> [Just QDiv, Just QDiv]+ -- ]+ -- ]++-- | Given a constructor T and a qualifier we want to hold of a type T+-- t1 t2 ..., return a list of qualifiers that need to hold of t1,+-- t2, ...+qualRules :: Con -> Qualifier -> Maybe [Maybe Qualifier]+-- T t1 t2 ... is basic (contains no Prop) iff t1, t2 ... all are.+qualRules c QBasic = Just (map (const (Just QBasic)) (arity c))+-- Otherwise, just look up in the qualRulesMap.+qualRules c q = (M.lookup c >=> M.lookup q) qualRulesMap++-- | @sortRules T s = [s1, ..., sn]@ means that sort @s@ holds of+-- type @(T t1 ... tn)@ if and only if @s1 t1 /\ ... /\ sn tn@.+-- For now this is just derived directly from 'qualRules'.+--+-- This is the @arity@ function described in section 4.1 of Traytel et+-- al.+sortRules :: Con -> Sort -> Maybe [Sort]+sortRules c s = do+ -- If any of the quals q in sort s are not in the map corresponding+ -- to tycon c, there's no way to make c an instance of q, so fail+ -- (the mapM will succeed only if all lookups succeed)+ needQuals <- mapM (qualRules c) (S.toList s)++ -- Otherwise we are left with a list (corresponding to all the quals+ -- in sort s) of lists (each one corresponds to the type args of c).+ -- We zip them together to produce a list of sorts.+ return $ foldl' (zipWith (\srt -> maybe srt (`S.insert` srt))) (repeat topSort) needQuals++-- | Pick a base type (generally the "simplest") that satisfies a given sort.+pickSortBaseTy :: Sort -> BaseTy+pickSortBaseTy s+ | QDiv `S.member` s && QSub `S.member` s = Q+ | QDiv `S.member` s = F+ | QSub `S.member` s = Z+ | QNum `S.member` s = N+ | QCmp `S.member` s = N+ | QEnum `S.member` s = N+ | QBool `S.member` s = B+ | QSimple `S.member` s = N+ | otherwise = Unit
+ src/Disco/Util.hs view
@@ -0,0 +1,30 @@+-----------------------------------------------------------------------------+-- |+-- Module : Disco.Util+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Miscellaneous utilities.+--+-----------------------------------------------------------------------------++module Disco.Util where++import qualified Data.Map as M++infixr 1 ==>++-- | A synonym for pairing which makes convenient syntax for+-- constructing literal maps via M.fromList.+(==>) :: a -> b -> (a,b)+(==>) = (,)++for :: [a] -> (a -> b) -> [b]+for = flip map++(!) :: (Show k, Ord k) => M.Map k v -> k -> v+m ! k = case M.lookup k m of+ Nothing -> error $ "key " ++ show k ++ " is not an element in the map"+ Just v -> v
+ src/Disco/Value.hs view
@@ -0,0 +1,574 @@+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}++-----------------------------------------------------------------------------+-- |+-- Module : Disco.Value+-- Copyright : disco team and contributors+-- Maintainer : byorgey@gmail.com+--+-- SPDX-License-Identifier: BSD-3-Clause+--+-- Disco runtime values and environments.+--+-----------------------------------------------------------------------------++module Disco.Value+ ( -- * Values++ Value(.., VNil, VCons, VFun)+ , SimpleValue(..)+ , toSimpleValue, fromSimpleValue++ -- ** Conversion++ , ratv, vrat+ , intv, vint+ , charv, vchar+ , enumv+ , pairv, vpair+ , listv, vlist++ -- * Props & testing+ , ValProp(..), TestResult(..), TestReason_(..), TestReason+ , SearchType(..), SearchMotive(.., SMExists, SMForall)+ , TestVars(..), TestEnv(..), emptyTestEnv, getTestEnv, extendPropEnv, extendResultEnv+ , testIsOk, testIsError, testReason, testEnv++ -- * Environments++ , Env++ -- * Memory+ , Cell(..), Mem, emptyMem, allocate, allocateRec, lkup, set++ -- * Pretty-printing++ , prettyValue', prettyValue, prettyTestFailure, prettyTestResult+ ) where++import Prelude hiding ((<>))+import qualified Prelude as P++import Control.Monad (forM)+import Data.Bifunctor (first)+import Data.Char (chr, ord, toLower)+import Data.IntMap (IntMap)+import qualified Data.IntMap as IM+import Data.List (foldl')+import Data.Map (Map)+import qualified Data.Map as M+import Data.Ratio++import Algebra.Graph (Graph, foldg)++import Disco.AST.Core+import Disco.AST.Generic (Side (..))+import Disco.AST.Typed (AProperty)+import Disco.Context as Ctx+import Disco.Error+import Disco.Names+import Disco.Pretty+import Disco.Syntax.Operators (BOp (Add, Mul))+import Disco.Typecheck.Erase (eraseProperty)+import Disco.Types++import Disco.Effects.LFresh+import Polysemy+import Polysemy.Input+import Polysemy.Reader+import Polysemy.State+import Unbound.Generics.LocallyNameless (Name)++------------------------------------------------------------+-- Value type+------------------------------------------------------------++-- | Different types of values which can result from the evaluation+-- process.+data Value where++ -- | A numeric value, which also carries a flag saying how+ -- fractional values should be diplayed.+ VNum :: RationalDisplay -> Rational -> Value++ -- | A built-in function constant.+ VConst :: Op -> Value++ -- | An injection into a sum type.+ VInj :: Side -> Value -> Value++ -- | The unit value.+ VUnit :: Value++ -- | A pair of values.+ VPair :: Value -> Value -> Value++ -- | A closure, i.e. a function body together with its+ -- environment.+ VClo :: Env -> [Name Core] -> Core -> Value++ -- | A disco type can be a value. For now, there are only a very+ -- limited number of places this could ever show up (in+ -- particular, as an argument to @enumerate@ or @count@).+ VType :: Type -> Value++ -- | A reference, i.e. a pointer to a memory cell. This is used to+ -- implement (optional, user-requested) laziness as well as+ -- recursion.+ VRef :: Int -> Value++ -- | A literal function value. @VFun@ is only used when+ -- enumerating function values in order to decide comparisons at+ -- higher-order function types. For example, in order to+ -- compare two values of type @(Bool -> Bool) -> Bool@ for+ -- equality, we have to enumerate all functions of type @Bool ->+ -- Bool@ as @VFun@ values.+ --+ -- We assume that all @VFun@ values are /strict/, that is, their+ -- arguments should be fully evaluated to RNF before being+ -- passed to the function.+ VFun_ :: ValFun -> Value++ -- | A proposition.+ VProp :: ValProp -> Value++ -- | A literal bag, containing a finite list of (perhaps only+ -- partially evaluated) values, each paired with a count. This is+ -- also used to represent sets (with the invariant that all counts+ -- are equal to 1).+ VBag :: [(Value, Integer)] -> Value++ -- | A graph, stored using an algebraic repesentation.+ VGraph :: Graph SimpleValue -> Value++ -- | A map from keys to values. Differs from functions because we can+ -- actually construct the set of entries, while functions only have this+ -- property when the key type is finite.+ VMap :: Map SimpleValue Value -> Value++ deriving Show++-- | Convenient pattern for the empty list.+pattern VNil :: Value+pattern VNil = VInj L VUnit++-- | Convenient pattern for list cons.+pattern VCons :: Value -> Value -> Value+pattern VCons h t = VInj R (VPair h t)++-- | Values which can be used as keys in a map, i.e. those for which a+-- Haskell Ord instance can be easily created. These should always+-- be of a type for which the QSimple qualifier can be constructed.+-- At the moment these are always fully evaluated (containing no+-- indirections) and thus don't need memory management. At some+-- point in the future constructors for simple graphs and simple+-- maps could be created, if the value type is also QSimple. The+-- only reason for actually doing this would be constructing graphs+-- of graphs or maps of maps, or the like.+data SimpleValue where+ SNum :: RationalDisplay -> Rational -> SimpleValue+ SUnit :: SimpleValue+ SInj :: Side -> SimpleValue -> SimpleValue+ SPair :: SimpleValue -> SimpleValue -> SimpleValue+ SBag :: [(SimpleValue, Integer)] -> SimpleValue+ SType :: Type -> SimpleValue+ deriving (Show, Eq, Ord)++toSimpleValue :: Value -> SimpleValue+toSimpleValue = \case+ VNum d n -> SNum d n+ VUnit -> SUnit+ VInj s v1 -> SInj s (toSimpleValue v1)+ VPair v1 v2 -> SPair (toSimpleValue v1) (toSimpleValue v2)+ VBag bs -> SBag (map (first toSimpleValue) bs)+ VType t -> SType t+ t -> error $ "A non-simple value was passed as simple: " ++ show t++fromSimpleValue :: SimpleValue -> Value+fromSimpleValue (SNum d n) = VNum d n+fromSimpleValue SUnit = VUnit+fromSimpleValue (SInj s v) = VInj s (fromSimpleValue v)+fromSimpleValue (SPair v1 v2) = VPair (fromSimpleValue v1) (fromSimpleValue v2)+fromSimpleValue (SBag bs) = VBag $ map (first fromSimpleValue) bs+fromSimpleValue (SType t) = VType t++-- | A @ValFun@ is just a Haskell function @Value -> Value@. It is a+-- @newtype@ just so we can have a custom @Show@ instance for it and+-- then derive a @Show@ instance for the rest of the @Value@ type.+newtype ValFun = ValFun (Value -> Value)++instance Show ValFun where+ show _ = "<fun>"++pattern VFun :: (Value -> Value) -> Value+pattern VFun f = VFun_ (ValFun f)++------------------------------------------------------------+-- Converting to and from Value+------------------------------------------------------------++-- XXX write some comments about partiality++-- | A convenience function for creating a default @VNum@ value with a+-- default (@Fractional@) flag.+ratv :: Rational -> Value+ratv = VNum mempty++vrat :: Value -> Rational+vrat (VNum _ r) = r+vrat v = error $ "vrat " ++ show v++-- | A convenience function for creating a default @VNum@ value with a+-- default (@Fractional@) flag.+intv :: Integer -> Value+intv = ratv . (% 1)++vint :: Value -> Integer+vint (VNum _ n) = numerator n+vint v = error $ "vint " ++ show v++vchar :: Value -> Char+vchar = chr . fromIntegral . vint++charv :: Char -> Value+charv = intv . fromIntegral . ord++-- | Turn any instance of @Enum@ into a @Value@, by creating a+-- constructor with an index corresponding to the enum value.+enumv :: Enum e => e -> Value+enumv e = VInj (toEnum $ fromEnum e) VUnit++pairv :: (a -> Value) -> (b -> Value) -> (a,b) -> Value+pairv av bv (a,b) = VPair (av a) (bv b)++vpair :: (Value -> a) -> (Value -> b) -> Value -> (a,b)+vpair va vb (VPair a b) = (va a, vb b)+vpair _ _ v = error $ "vpair " ++ show v++listv :: (a -> Value) -> [a] -> Value+listv _ [] = VNil+listv eltv (a:as) = VCons (eltv a) (listv eltv as)++vlist :: (Value -> a) -> Value -> [a]+vlist _ VNil = []+vlist velt (VCons v vs) = velt v : vlist velt vs+vlist _ v = error $ "vlist " ++ show v+++------------------------------------------------------------+-- Propositions+------------------------------------------------------------++data SearchType+ = Exhaustive+ -- ^ All possibilities were checked.+ | Randomized Integer Integer+ -- ^ A number of small cases were checked exhaustively and+ -- then a number of additional cases were checked at random.+ deriving Show++-- | The answer (success or failure) we're searching for, and+-- the result (success or failure) we return when we find it.+-- The motive @(False, False)@ corresponds to a "forall" quantifier+-- (look for a counterexample, fail if you find it) and the motive+-- @(True, True)@ corresponds to "exists". The other values+-- arise from negations.+newtype SearchMotive = SearchMotive (Bool, Bool)+ deriving Show++pattern SMForall :: SearchMotive+pattern SMForall = SearchMotive (False, False)++pattern SMExists :: SearchMotive+pattern SMExists = SearchMotive (True, True)++-- | A collection of variables that might need to be reported for+-- a test, along with their types and user-legible names.+newtype TestVars = TestVars [(String, Type, Name Core)]+ deriving newtype (Show, Semigroup, Monoid)++-- | A variable assignment found during a test.+newtype TestEnv = TestEnv [(String, Type, Value)]+ deriving newtype (Show, Semigroup, Monoid)++emptyTestEnv :: TestEnv+emptyTestEnv = TestEnv []++getTestEnv :: TestVars -> Env -> Either EvalError TestEnv+getTestEnv (TestVars tvs) e = fmap TestEnv . forM tvs $ \(s, ty, name) -> do+ let value = Ctx.lookup' (localName name) e+ case value of+ Just v -> return (s, ty, v)+ Nothing -> Left (UnboundError name)++-- | The possible outcomes of a property test, parametrized over+-- the type of values. A @TestReason@ explains why a proposition+-- succeeded or failed.+data TestReason_ a+ = TestBool+ -- ^ The prop evaluated to a boolean.+ | TestEqual Type a a+ -- ^ The test was an equality test. Records the values being+ -- compared and also their type (which is needed for printing).+ | TestNotFound SearchType+ -- ^ The search didn't find any examples/counterexamples.+ | TestFound TestResult+ -- ^ The search found an example/counterexample.+ | TestRuntimeError EvalError+ -- ^ The prop failed at runtime. This is always a failure, no+ -- matter which quantifiers or negations it's under.+ deriving (Show, Functor, Foldable, Traversable)++type TestReason = TestReason_ Value++-- | The possible outcomes of a proposition.+data TestResult = TestResult Bool TestReason TestEnv+ deriving Show++-- | Whether the property test resulted in a runtime error.+testIsError :: TestResult -> Bool+testIsError (TestResult _ (TestRuntimeError _) _) = True+testIsError _ = False++-- | Whether the property test resulted in success.+testIsOk :: TestResult -> Bool+testIsOk (TestResult b _ _) = b++-- | The reason the property test had this result.+testReason :: TestResult -> TestReason+testReason (TestResult _ r _) = r++testEnv :: TestResult -> TestEnv+testEnv (TestResult _ _ e) = e++-- | A @ValProp@ is the normal form of a Disco value of type @Prop@.+data ValProp+ = VPDone TestResult+ -- ^ A prop that has already either succeeded or failed.+ | VPSearch SearchMotive [Type] Value TestEnv+ -- ^ A pending search.+ deriving Show++extendPropEnv :: TestEnv -> ValProp -> ValProp+extendPropEnv g (VPDone (TestResult b r e)) = VPDone (TestResult b r (g P.<> e))+extendPropEnv g (VPSearch sm tys v e) = VPSearch sm tys v (g P.<> e)++extendResultEnv :: TestEnv -> TestResult -> TestResult+extendResultEnv g (TestResult b r e) = TestResult b r (g P.<> e)++------------------------------------------------------------+-- Environments+------------------------------------------------------------++-- | An environment is a mapping from names to values.+type Env = Ctx Core Value++------------------------------------------------------------+-- Memory+------------------------------------------------------------++-- | 'Mem' represents a memory, containing 'Cell's+data Mem = Mem { next :: Int, mu :: IntMap Cell } deriving Show+data Cell = Blackhole | E Env Core | V Value deriving Show++emptyMem :: Mem+emptyMem = Mem 0 IM.empty++-- | Allocate a new memory cell containing an unevaluated expression+-- with the current environment. Return the index of the allocated+-- cell.+allocate :: Members '[State Mem] r => Env -> Core -> Sem r Int+allocate e t = do+ Mem n m <- get+ put $ Mem (n+1) (IM.insert n (E e t) m)+ return n++-- | Allocate new memory cells for a group of mutually recursive+-- bindings, and return the indices of the allocate cells.+allocateRec :: Members '[State Mem] r => Env -> [(QName Core, Core)] -> Sem r [Int]+allocateRec e bs = do+ Mem n m <- get+ let newRefs = zip [n ..] bs+ e' = foldl' (flip (\(i,(x,_)) -> Ctx.insert x (VRef i))) e newRefs+ m' = foldl' (flip (\(i,(_,c)) -> IM.insert i (E e' c))) m newRefs+ n' = n + length bs+ put $ Mem n' m'+ return [n .. n'-1]++-- | Look up the cell at a given index.+lkup :: Members '[State Mem] r => Int -> Sem r (Maybe Cell)+lkup n = gets (IM.lookup n . mu)++-- | Set the cell at a given index.+set :: Members '[State Mem] r => Int -> Cell -> Sem r ()+set n c = modify $ \(Mem nxt m) -> Mem nxt (IM.insert n c m)++------------------------------------------------------------+-- Pretty-printing values+------------------------------------------------------------++prettyValue' :: Member (Input TyDefCtx) r => Type -> Value -> Sem r Doc+prettyValue' ty v = runLFresh . runReader initPA $ prettyValue ty v++prettyValue :: Members '[Input TyDefCtx, LFresh, Reader PA] r => Type -> Value -> Sem r Doc++-- Lazily expand any user-defined types+prettyValue (TyUser x args) v = do+ tydefs <- input+ let (TyDefBody _ body) = tydefs M.! x -- This can't fail if typechecking succeeded+ prettyValue (body args) v++prettyValue _ VUnit = "■"+prettyValue TyProp _ = prettyPlaceholder TyProp+prettyValue TyBool (VInj s _) = text $ map toLower (show (s == R))+prettyValue TyBool v =+ error $ "Non-VInj passed with Bool type to prettyValue: " ++ show v+prettyValue TyC (vchar -> c) = text (show c)+prettyValue (TyList TyC) (vlist vchar -> cs) = doubleQuotes . text . concatMap prettyChar $ cs+ where+ prettyChar = drop 1 . reverse . drop 1 . reverse . show . (:[])+prettyValue (TyList ty) (vlist id -> xs) = do+ ds <- punctuate (text ",") (map (prettyValue ty) xs)+ brackets (hsep ds)++prettyValue ty@(_ :*: _) v = parens (prettyTuple ty v)++prettyValue (ty1 :+: _) (VInj L v) = "left" <> prettyVP ty1 v+prettyValue (_ :+: ty2) (VInj R v) = "right" <> prettyVP ty2 v+prettyValue (_ :+: _) v =+ error $ "Non-VInj passed with sum type to prettyValue: " ++ show v++prettyValue _ (VNum d r)+ | denominator r == 1 = text $ show (numerator r)+ | otherwise = text $ case d of+ Fraction -> show (numerator r) ++ "/" ++ show (denominator r)+ Decimal -> prettyDecimal r++prettyValue ty@(_ :->: _) _ = prettyPlaceholder ty++prettyValue (TySet ty) (VBag xs) = braces $ prettySequence ty "," (map fst xs)+prettyValue (TySet _) v =+ error $ "Non-VBag passed with Set type to prettyValue: " ++ show v+prettyValue (TyBag ty) (VBag xs) = prettyBag ty xs+prettyValue (TyBag _) v =+ error $ "Non-VBag passed with Bag type to prettyValue: " ++ show v++prettyValue (TyMap tyK tyV) (VMap m) =+ "map" <> parens (braces (prettySequence (tyK :*: tyV) "," (assocsToValues m)))+ where+ assocsToValues = map (\(k,v) -> VPair (fromSimpleValue k) v) . M.assocs+prettyValue (TyMap _ _) v =+ error $ "Non-map value with map type passed to prettyValue: " ++ show v++prettyValue (TyGraph ty) (VGraph g) =+ foldg+ "emptyGraph"+ (("vertex" <>) . prettyVP ty . fromSimpleValue)+ (\l r -> withPA (getPA Add) $ lt l <+> "+" <+> rt r)+ (\l r -> withPA (getPA Mul) $ lt l <+> "*" <+> rt r)+ g+prettyValue (TyGraph _) v =+ error $ "Non-graph value with graph type passed to prettyValue: " ++ show v++prettyValue ty@TyAtom{} v =+ error $ "Invalid atomic type passed to prettyValue: " ++ show ty ++ " " ++ show v++prettyValue ty@TyCon{} v =+ error $ "Invalid type constructor passed to prettyValue: " ++ show ty ++ " " ++ show v++-- | Pretty-print a value with guaranteed parentheses. Do nothing for+-- tuples; add an extra set of parens for other values.+prettyVP :: Members '[Input TyDefCtx, LFresh, Reader PA] r => Type -> Value -> Sem r Doc+prettyVP ty@(_ :*: _) = prettyValue ty+prettyVP ty = parens . prettyValue ty++prettyPlaceholder :: Members '[Reader PA, LFresh] r => Type -> Sem r Doc+prettyPlaceholder ty = "<" <> pretty ty <> ">"++prettyTuple :: Members '[Input TyDefCtx, LFresh, Reader PA] r => Type -> Value -> Sem r Doc+prettyTuple (ty1 :*: ty2) (VPair v1 v2) = prettyValue ty1 v1 <> "," <+> prettyTuple ty2 v2+prettyTuple ty v = prettyValue ty v++-- | 'prettySequence' pretty-prints a lists of values separated by a delimiter.+prettySequence :: Members '[Input TyDefCtx, LFresh, Reader PA] r => Type -> Doc -> [Value] -> Sem r Doc+prettySequence ty del vs = hsep =<< punctuate (return del) (map (prettyValue ty) vs)++-- | Pretty-print a literal bag value.+prettyBag :: Members '[Input TyDefCtx, LFresh, Reader PA] r => Type -> [(Value,Integer)] -> Sem r Doc+prettyBag _ [] = bag empty+prettyBag ty vs+ | all ((==1) . snd) vs = bag $ prettySequence ty "," (map fst vs)+ | otherwise = bag $ hsep =<< punctuate (return ",") (map prettyCount vs)+ where+ prettyCount (v,1) = prettyValue ty v+ prettyCount (v,n) = prettyValue ty v <+> "#" <+> text (show n)++------------------------------------------------------------+-- Pretty-printing for test results+------------------------------------------------------------++prettyTestFailure+ :: Members '[Input TyDefCtx, LFresh, Reader PA] r+ => AProperty -> TestResult -> Sem r Doc+prettyTestFailure _ (TestResult True _ _) = empty+prettyTestFailure prop (TestResult False r env) =+ prettyFailureReason prop r+ $+$+ prettyTestEnv "Counterexample:" env++prettyTestResult+ :: Members '[Input TyDefCtx, LFresh, Reader PA] r+ => AProperty -> TestResult -> Sem r Doc+prettyTestResult prop r | not (testIsOk r) = prettyTestFailure prop r+prettyTestResult prop (TestResult _ r _) =+ ("Test passed:" <+> pretty (eraseProperty prop))+ $+$+ prettySuccessReason r++prettySuccessReason+ :: Members '[Input TyDefCtx, LFresh, Reader PA] r+ => TestReason -> Sem r Doc+prettySuccessReason (TestFound (TestResult _ _ vs)) = prettyTestEnv "Found example:" vs+prettySuccessReason (TestNotFound Exhaustive) = "No counterexamples exist."+prettySuccessReason (TestNotFound (Randomized n m)) =+ "Checked" <+> text (show (n + m)) <+> "possibilities without finding a counterexample."+prettySuccessReason _ = empty++prettyFailureReason+ :: Members '[Input TyDefCtx, LFresh, Reader PA] r+ => AProperty -> TestReason -> Sem r Doc+prettyFailureReason prop TestBool = "Test is false:" <+> pretty (eraseProperty prop)+prettyFailureReason prop (TestEqual ty v1 v2) =+ "Test result mismatch for:" <+> pretty (eraseProperty prop)+ $+$+ bulletList "-"+ [ "Left side: " <> prettyValue ty v2+ , "Right side: " <> prettyValue ty v1+ ]+prettyFailureReason prop (TestRuntimeError e) =+ "Test failed:" <+> pretty (eraseProperty prop)+ $+$+ text (show e)+prettyFailureReason prop (TestFound (TestResult _ r _)) = prettyFailureReason prop r+prettyFailureReason prop (TestNotFound Exhaustive) =+ "No example exists:" <+> pretty (eraseProperty prop)+ $+$+ "All possible values were checked."+prettyFailureReason prop (TestNotFound (Randomized n m)) = do+ "No example was found:" <+> pretty (eraseProperty prop)+ $+$+ ("Checked" <+> text (show (n + m)) <+> "possibilities.")++prettyTestEnv+ :: Members '[Input TyDefCtx, LFresh, Reader PA] r+ => String -> TestEnv -> Sem r Doc+prettyTestEnv _ (TestEnv []) = empty+prettyTestEnv s (TestEnv vs) = text s $+$ nest 2 (vcat (map prettyBind vs))+ where+ maxNameLen = maximum . map (\(n, _, _) -> length n) $ vs+ prettyBind (x, ty, v) =+ text x <> text (replicate (maxNameLen - length x) ' ') <+> "=" <+> prettyValue ty v
+ stack.yaml view
@@ -0,0 +1,44 @@+# This file was automatically generated by stack init+# For more information, see: http://docs.haskellstack.org/en/stable/yaml_configuration/++# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)+resolver: lts-18.13++# Local packages, usually specified by relative directory name+packages:+ - "."++# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)+extra-deps:+ - unbound-generics-0.4.0+ - simple-enumeration-0.2+ - oeis-0.3.10+ - capability-0.4.0.0@sha256:d86d85a1691ef0165c77c47ea72eac75c99d21fb82947efe8b2f758991cf1837,3345+ - polysemy-1.6.0.0@sha256:29a73b1bf3d0049b12041016b7ee25e76bd8f6e99f9c37c2dde2b46368246697,6184+ - polysemy-plugin-0.4.0.0+ - polysemy-zoo-0.7.0.1@sha256:60c2921df95f61d43222a75adde4f330e9510320b416132838a354cd81b4bcc5,3846+ - compact-0.2.0.0@sha256:75ef98cb51201b4a0d6de95cbbb62be6237c092a3d594737346c70c5d56c2380,2413+ - constraints-0.12 # needed since polysemy-zoo hasn't updated upper bound to allow 0.13++# Override default flag values for local packages and extra-deps+flags: {}++# Extra package databases containing global packages+extra-package-dbs: []+# Control whether we use the GHC we find on the path+# system-ghc: true++# Require a specific version of stack, using version ranges+# require-stack-version: -any # Default+# require-stack-version: >= 1.0.0++# Override the architecture used by stack, especially useful on Windows+# arch: i386+# arch: x86_64++# Extra directories used by stack for building+# extra-include-dirs: [/path/to/dir]+# extra-lib-dirs: [/path/to/dir]++# Allow a newer minor version of GHC than the snapshot specifies+# compiler-check: newer-minor
+ test/README.md view
@@ -0,0 +1,48 @@+This directory contains a regression test suite for Disco.++Running the tests+-----------------++To run the tests, just do `stack test`.++Adding a new test case+----------------------++Adding a new test case is easy.++1. Create a directory for the new test, with a prefix denoting the+ general category of the test, a hyphen, and then an arbitrary name+ describing the content of the test.++2. In that directory, you must create two files:++ - `input` should consist of a sequence of commands or expressions+ to be evaluated by the Disco REPL, one per line.++ - `expected` should consist of the expected output.++ In fact, you don't even have to create `expected` yourself. If+ you know that Disco currently has the expected behavior for the+ commands and expressions in `input`, simply run the test suite and+ `expected` will be created automatically if it does not exist.++ You may create additional files as well, for example, one or more+ `.disco` files to be `:load`ed by a command in `input`. (Be aware+ that the test suite runs from the root directory of the+ repository, so you will have to write something like `:load+ test/category-name/foo.disco`.)++Dealing with mass test suite breakage+-------------------------------------++In certain cases many test cases may break all at once for a known+reason---for example, if a change in Disco's pretty-printer or+error messages causes the expected output of many tests to change. In+this case you need not manually paste in the new expected output for+each test case.++1. Verify by inspection that all the failing test cases are in fact+ producing the expected new output (by examining the `output` files).++2. Run `stack test --test-arguments --accept`. This will overwrite+ the `expected` files with the actual output.
+ test/Tests.hs view
@@ -0,0 +1,83 @@+module Main where++import Control.Monad (filterM)+import qualified Data.ByteString as BS+import Data.Function (on)+import Data.List (groupBy, sort)+import System.Directory (doesFileExist,+ getDirectoryContents)+import System.FilePath (isPathSeparator, (</>))+import System.IO (hGetContents)+import System.Process (StdStream (CreatePipe),+ createProcess, shell, std_out,+ system)+import Text.Printf++import Test.Tasty+import Test.Tasty.Golden.Advanced++main :: IO ()+main = do+ testDirs <- getDirectoryContents "test"+ >>= filterM (doesFileExist . (\d -> ("test" </> d </> "input")))+ let testDirs'+ = groupBy ((==) `on` extractGroup)+ . sort+ . filter (\f -> f /= "." && f /= "..")+ $ testDirs+ let testTree = testGroup "disco" $ map mkGroup testDirs'+ defaultMain testTree+ where+ mkGroup ds = testGroup (extractGroup (head ds)) $ map mkGolden ds+ -- (head ds) is safe since mkGroup is called on testDirs', which+ -- is the output of groupBy, so each element of testDirs' will+ -- be a non-empty list.++extractGroup :: FilePath -> String+extractGroup = takeWhile (/='-')++extractName :: FilePath -> String+extractName = takeWhile (not . isPathSeparator) . drop 1 . dropWhile (/='-')++mkGolden :: FilePath -> TestTree+mkGolden relDir =+ goldenVsFileWithDiff+ (extractName relDir)+ (dir </> "expected")+ (dir </> "output")+ (system ("disco -f " ++ (dir </> "input") ++ " > " ++ (dir </> "output")) >> return ())+ where+ dir = "test" </> relDir++-- | A variant of goldenVsFile that prints the result of @diff@ if+-- the files are different, so we don't have to manually call @diff@+-- every time there is a test failure.+goldenVsFileWithDiff+ :: TestName -- ^ test name+ -> FilePath -- ^ path to the «golden» file (the file that contains correct output)+ -> FilePath -- ^ path to the output file+ -> IO () -- ^ action that creates the output file+ -> TestTree -- ^ the test verifies that the output file contents is the same as the golden file contents+goldenVsFileWithDiff name ref new act =+ goldenTest+ name+ (BS.readFile ref)+ (act >> BS.readFile new)+ cmp+ upd+ where+ cmp = cmpWithDiff ref new+ upd = BS.writeFile ref++cmpWithDiff :: Eq a => FilePath -> FilePath -> a -> a -> IO (Maybe String)+cmpWithDiff f1 f2 x y = do+ if x == y+ then return Nothing+ else do+ (_, Just hout, _, _)+ <- createProcess (shell $ printf "diff %s %s" f1 f2) { std_out = CreatePipe }+ diffStr <- hGetContents hout+ return $ Just $ unlines+ [ printf "Files '%s' and '%s' differ:" f1 f2+ , diffStr+ ]
+ test/arith-basic-bin/expected view
@@ -0,0 +1,24 @@+5+100+-13+3+2+0+1+0+5+0+0+0+2+17082135+30+-20+14+25+4+3/2+65536+256+418993997810706159361688281193932691483730181893512293053861295116305125939798343025058571817715732115313495568327689089179808837873330310826051531440128+592
+ test/arith-basic-bin/input view
@@ -0,0 +1,24 @@+2+3+10+20+30+40+1-2-3-4-5+1-(2-3)-(4-5)+5 .- 3+3 ∸ 5+-2 .- -3+-3 .- -2+3 .- -2+-3 .- 2+3 .- 3+0 .- 2+2 .- 0+245*69723+(-5)*(-6)+(4)(-5)+(2)7+5(5)+20/5+18/12+2^2^2^2+((2^2)^2)^2+2^507+let x = 17 in (2x+3)(x-1)
+ test/arith-basic-un/expected view
@@ -0,0 +1,23 @@+0+1+2+2+2+4+5254282669+9485374212+375828023454801203683362418972386504867736551759258677056523839782231681498337708535732725752658844333702457749526057760309227891351617765651907310968780236464694043316236562146724416478591131832593729111221580180531749232777515579969899075142213969117994877343802049421624954402214529390781647563339535024772584901607666862982567918622849636160208877365834950163790188523026247440507390382032188892386109905869706753143243921198482212075444022433366554786856559389689585638126582377224037721702239991441466026185752651502936472280911018500320375496336749951569521541850441747925844066295279671872605285792552660130702047998218334749356321677469529682551765858267502715894007887727250070780350262952377214028842297486263597879792176338220932619489509376+0+1+3+1+2/3+2/3+2/3+-6+Error: that number would not even fit in the universe!+-9+-~ : ℤ → ℤ+-3 : ℤ+-(3 : ℕ) : ℤ+-(3 : 𝔽) : ℚ
+ test/arith-basic-un/input view
@@ -0,0 +1,26 @@+sqrt 0+sqrt 1+sqrt 4+sqrt 5+sqrt 8+sqrt 20+sqrt 27607486371775073359+sqrt 89972323943429722781+sqrt (2^5000 + 1)+abs 0+abs 1+abs (-3)+abs (5 - 6)+abs (2/3)+abs (-2/3)+abs (2/(-3))+-- abs (2 : Z8)+-3!+((4!)!)!+let x = 3 in -x^2+:type -~+:type (-~ 3)+:type (-~ (3 : N))+:type (-~ (3 : F))+-- :type (-~ (3 : Z5))+-- (-~ (3 : Z5))
+ test/arith-count/expected view
@@ -0,0 +1,28 @@+1+1+2+6+24+120+93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000+33+1+1+4+6+4+1+0+317069614581796942069850800242662142665884971451889342180215921022256584404257764868994168981794290681466646945931721640583674582646104015150878777044819240419642695597347059228370594446663577155071679912730991740163983793762281849063963362837125+1+10+45+true+true+12600+true+2520+0+3628800+10+30
+ test/arith-count/input view
@@ -0,0 +1,28 @@+0!+1!+2!+3!+4!+5!+100!+1! + 2! + 3! + 4!+0 choose 0+4 choose 0+4 choose 1+4 choose 2+4 choose 3+4 choose 4+4 choose 5+1000 choose 256+10 choose []+10 choose [1]+10 choose [2]+10 choose [2] == 10 choose 2+10 choose [2,8] == 10 choose 2+10 choose [2,3,4]+10 choose [2,3,2] == 10! / (2! * 3! * 2! * 3!)+10 choose [2,3,5]+10 choose [2,3,6]+10 choose [1,1,1,1,1,1,1,1,1,1]+let x = 3 in 5 choose x+let x = [1,2,2] in 5 choose x
+ test/arith-numthry/expected view
@@ -0,0 +1,12 @@+3+3+true+true+true+true+true+false+false+false+true+true
+ test/arith-numthry/input view
@@ -0,0 +1,12 @@+24 mod 7+24 % 7+24 mod 7 == 24 % 7+3 divides 6+23948723947 divides 7115869653734173619712+(-2) divides 8+5 divides (-25)+5 divides 24+5 divides (-24)+0 divides 10+10 divides 0+0 divides 0
+ test/arith-prim/arith-prim.disco view
@@ -0,0 +1,4 @@+import num++ps : List(N)+ps = filter(isPrime, [1 .. 100])
+ test/arith-prim/expected view
@@ -0,0 +1,4 @@+Loading arith-prim.disco...+Loading num.disco...+Loaded.+[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
+ test/arith-prim/input view
@@ -0,0 +1,2 @@+:load test/arith-prim/arith-prim.disco+ps
+ test/arith-round/expected view
@@ -0,0 +1,15 @@+0+3+5+1+1+3+1+3+3+2+2+11+2+2+-2
+ test/arith-round/input view
@@ -0,0 +1,16 @@+floor (1 / 2)+floor (10 / 3)+floor 5+floor (500 / 376)+ceiling (1 / 3)+ceiling (17 / 6)+ceiling ((floor (5 / 2)) / 28)+⌊10 / 3⌋+⌈20 / 7⌉+5 // 2+37 // 13+100 // 9+(78 // 4) // (51 // 7)+((5 : F) // (2 : F)) : Nat+((-5 : Q) // (3 : F)) : Z+-- (5 : Z7) // (3 : Z7)
+ test/case-arith/case-arith.disco view
@@ -0,0 +1,54 @@+import list++f1 : N -> N+f1 (x + 1) = x+f1 0 = 0++f2 : N -> N+f2 (3 + (x + 2)) = x+f2 y = 0++f3 : Z -> Z+f3 (3 + (x + 2)) = x+f3 y = 0++f4 : N*Z -> N*Z + N*Z+f4 (x+1,y+2) = left (x,y)+f4 (x, y+2) = right (x,y)++h : N -> N+h(0) = 1+h(2k+1) = h(k)+h(2k+2) = h(k+1) + h(k)++f5 : N -> N+f5 (2x) = x+f5 (2x-1) = x++f6 : Q -> Bool+f6 (-2/3) = True+f6 _ = False++!!! forall x:Z. Zabs(x) >= 0+Zabs : Z -> Z+Zabs (-x) = x+Zabs x = x++type Tree = Unit + F * Tree * Tree++expandTree : N -> F -> Tree+expandTree 0 _ = left(unit)+expandTree n (a/b) = right(a/b, expandTree (n .- 1) (a/(a+b)), expandTree (n .- 1) ((a+b)/b))++cwTree : N -> Tree+cwTree n = expandTree n 1++inorder : Tree -> List(F)+inorder (left(unit)) = []+inorder (right(x,l,r)) = append(inorder(l), x :: inorder(r))++numerator : Q -> Z+numerator (p/q) = p++denominator : Q -> N+denominator (p/q) = q
+ test/case-arith/expected view
@@ -0,0 +1,37 @@+Loading case-arith.disco...+Loading list.disco...+Running tests...+ Zabs: OK+Loaded.+0+1+540+0+2+0+2+-1+left(1, -1)+right(0, -1)+1+1+5+7+3+1/3+200+-1/6+2+2+3+3+17+17+0+[1/4, 1/3, 4/3, 1/2, 3/5, 3/2, 5/2, 1, 2/5, 2/3, 5/3, 2, 3/4, 3, 4]+5+1+2+-2+5+5
+ test/case-arith/input view
@@ -0,0 +1,41 @@+:load test/case-arith/case-arith.disco+f1 1+f1 2+f1 541+f1 0++f2 7+f2 4+f3 7+f3 4++f4 (2,1)+f4 (0,1)++h(0)+h(1)+h(10)+h(22)+h(29360127)++{? x when 5/3 is 2x+1, 200 otherwise ?}+{? x when 2/3 is 2x+1, 200 otherwise ?}+{? x when (2/3 : Q) is 2x+1, 200 otherwise ?}++f5(3)+f5(4)+f5(5)+f5(6)++Zabs(17)+Zabs(-17)+Zabs(0)++inorder(cwTree(4))++numerator 5+denominator 5+numerator (2/5)+numerator (2/(-5))+denominator (2/5)+denominator (2/(-5))
+ test/case-basic/case-basic.disco view
@@ -0,0 +1,7 @@+foo : List(N) + Bool -> N+foo x =+ {? n when x is left (n :: _),+ 0 when x is left [],+ 1 when x is right True,+ 2 when x is right False+ ?}
+ test/case-basic/expected view
@@ -0,0 +1,7 @@+Loading case-basic.disco...+Loaded.+0+1+2+3+4
+ test/case-basic/input view
@@ -0,0 +1,6 @@+:load test/case-basic/case-basic.disco+foo (left [])+foo (right true)+foo (right false)+foo (left [3])+foo (left [4,2])
+ test/case-let/case-let.disco view
@@ -0,0 +1,5 @@+f : N -> N+f x = {? z if 2 divides x+ let z = x // 2+ , x otherwise+ ?}
+ test/case-let/expected view
@@ -0,0 +1,4 @@+Loading case-let.disco...+Loaded.+3+5
+ test/case-let/input view
@@ -0,0 +1,3 @@+:load test/case-let/case-let.disco+f 6+f 5
+ test/compile-cons/expected view
@@ -0,0 +1,1 @@+[3, 5]
+ test/compile-cons/input view
@@ -0,0 +1,1 @@+(\x. (\z. ~::~ (x,z))) 3 [5]
+ test/compile-misc/expected view
@@ -0,0 +1,9 @@+holds (∀ℕ. (λarg0. (λ_. (λk. (λx. test [(x, ℕ, x)] (3 < x)) arg0) (λ_1. matchErr)) unit))+λx, y. x+(λ_. (λk. (λy. (λp, q. p) (fst y) (snd y)) (frac (2 / 3))) (λ_1. matchErr)) unit+(λ_. (λk. case (3 < 2) of {+ left _1 -> k unit+ right px -> (λ_2. 1) px+ }) (λ_1. (λk. 17) (λ_2. matchErr))) unit+(10 choose right (5, left unit))+5!
+ test/compile-misc/input view
@@ -0,0 +1,6 @@+:compile (holds (forall x : N. x > 3))+:compile \x. \y. x+:compile {? p when 2/3 is p/q ?}+:compile {? 1 if 2 > 3, 17 otherwise ?}+:compile 10 choose 5+:compile 5!
+ test/containers-cmp/expected view
@@ -0,0 +1,4 @@+{{1, 2}, {1, 3}, {2, 3}}+true+{⟅1 # 3⟆, ⟅2 # 5⟆}+true
+ test/containers-cmp/input view
@@ -0,0 +1,4 @@+{{1,2}, {2,3}, {1,3}}+{} < {1} < {1,2} < {2,3} < {4,2,3}+{ bag [1,1,1], bag [2,2,2,2,2] }+⟅⟆ < ⟅1⟆ < ⟅1,1⟆ < ⟅2⟆ < ⟅2, 2⟆ < ⟅2, 2, 3, 3⟆ < ⟅2, 2, 2, 3⟆ < ⟅2, 2, 2, 4⟆ < ⟅3⟆
+ test/containers-comp/expected view
@@ -0,0 +1,5 @@+{1, 2, 3}+{}+{11, 13, 15, 17, 21, 23, 25, 27, 31, 33, 35, 37}+{3, 4, 5, 6}+{2, 3}
+ test/containers-comp/input view
@@ -0,0 +1,5 @@+{x | x in {1,2,3}}+{x | x in {1,2,3}, y in {}}+{x+y+z | z in {1,2,3}, x in {z-1,z+1}, y in {10,20,30}}+{x+y+z | x in {1,2}, y in {1,2}, z in {1,2}}+{ x | x in {1,2,3}, x > 1 }
+ test/containers-convert/expected view
@@ -0,0 +1,10 @@+[1, 2, 3, 3]+[1, 2, 3]+[1, 2, 2, 3]+⟅1, 2, 3 # 2⟆+⟅1, 2, 3⟆+⟅1, 2 # 2, 3⟆+{1, 2, 3}+{1, 2, 3}+{1, 2, 3}+[1, 2, 3, 5, 6, 6]
+ test/containers-convert/input view
@@ -0,0 +1,10 @@+list [1,2,3,3]+list {2,3,1,2}+list ⟅2,3,1,2⟆+bag [1,2,3,3]+bag {2,3,1,2}+bag ⟅2,3,1,2⟆+set [1,2,3,3]+set {2,3,1,2}+set ⟅2,3,1,2⟆+let sort : List(N) -> List(N) = \l. list (bag l) in sort [1,5,2,6,6,3]
+ test/containers-each/expected view
@@ -0,0 +1,12 @@+λxs. each(λx. x + 1, xs) : List(ℕ) → List(ℕ)+λxs. each(list, xs) : List(List(a)) → List(List(a))+each(λx. x + 1, [1, 2, 3]) : List(ℕ)+each(λx. x + 1, ⟅1, 2, 3⟆) : Bag(ℕ)+each(λx. x + 1, {1, 2, 3}) : Set(ℕ)+[2, 3, 4]+⟅2, 3, 4⟆+⟅0, 1 # 2, 4 # 2⟆+⟅1 # 100⟆+{2, 3, 4}+{0, 1, 4}+{1}
+ test/containers-each/input view
@@ -0,0 +1,12 @@+:type \xs. each (\x.x+1, xs)+:type \xs. each(list,xs)+:type each (\x.x+1, [1,2,3])+:type each (\x.x+1, ⟅1,2,3⟆)+:type each (\x.x+1, {1,2,3})+each(\x.x+1, [1,2,3])+each(\x.x+1, ⟅1,2,3⟆)+each(\x.x^2, ⟅-2, -1, 0, 1, 2⟆)+each(\x.1, ⟅1..100⟆)+each(\x.x+1, {1,2,3})+each(\x.x^2, {-2 .. 2})+each(\x.1, {1..100})
+ test/containers-ellipsis/expected view
@@ -0,0 +1,5 @@+{1, 2, 3, 4, 5}+{1, 3, 6, 10, 15, 21, 28, 36, 45}+⟅1, 2, 3, 4, 5⟆+⟅1, 3, 6, 10, 15, 21, 28, 36, 45⟆+"abcdefghijklmnopqrstuvwxyz"
+ test/containers-ellipsis/input view
@@ -0,0 +1,5 @@+{ 1 .. 5 }+{ 1, 3, 6 .. 50 }+⟅ 1 .. 5 ⟆+⟅ 1, 3, 6 .. 50 ⟆+[ 'a' .. 'z' ]
+ test/containers-filter/expected view
@@ -0,0 +1,6 @@+Loading list.disco...+[4, 5, 6, 7, 8, 9, 10]+⟅4, 5, 6, 7, 8, 9, 10⟆+{4, 5, 6, 7, 8, 9, 10}+⟅2 # 2, 4⟆+[4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
+ test/containers-filter/input view
@@ -0,0 +1,6 @@+import list+filter(\x.x > 3, [1 .. 10])+filter(\x.x > 3, ⟅1 .. 10⟆)+filter(\x.x > 3, {1 .. 10})+filter(\x.x mod 2 == 0, ⟅1, 2, 1, 3, 2, 4⟆)+take(10, filter(\x.x > 3, [1 .. 1000]))
+ test/containers-join/expected view
@@ -0,0 +1,7 @@+[]+[1, 2, 3, 4, 5, 6, 7]+{1, 2, 3}+{1, 2, 3}+⟅1 # 4, 2 # 2, 3 # 2⟆+⟅1 # 7, 2 # 4, 3 # 2⟆+⟅1 # 15, 2 # 4, 3 # 6⟆
+ test/containers-join/input view
@@ -0,0 +1,8 @@+using primitives+$join []+$join [[1,2], [3,4,5], [6,7]]+$join {{1}, {2}, {3}}+$join {{1,2}, {2,3}, {1,3}}+$join ⟅⟅1⟆, ⟅1,2⟆, ⟅2,3⟆, ⟅1,1,3⟆⟆+$join ⟅⟅1⟆, ⟅1⟆, ⟅1,2⟆, ⟅2,3⟆, ⟅1,2⟆, ⟅1,2⟆, ⟅1,1,3⟆⟆+$join ($join (bag [bagFromCounts {(⟅1⟆, 2)}, bagFromCounts {(⟅1,2⟆, 3)}, bagFromCounts {(⟅2,3⟆, 1)}, bagFromCounts {(⟅1,1,3⟆, 5)}]))
+ test/containers-merge/expected view
@@ -0,0 +1,8 @@+⟅1 # 2, 2 # 5, 3 # 2⟆+⟅1 # 2, 2⟆+⟅2 # 2⟆+⟅1 # 2, 2 # 3, 3 # 2⟆+{2}+{1, 2, 3, 4}+{1, 3}+{1, 3, 4}
+ test/containers-merge/input view
@@ -0,0 +1,9 @@+using primitives+$merge(~+~, bag [1,1,2,2,2], bag [2,2,3,3])+$merge(~.-~, bag [1,1,2,2,2], bag [2,2,3,3])+$merge(~min~, bag [1,1,2,2,2], bag [2,2,3,3])+$merge(~max~, bag [1,1,2,2,2], bag [2,2,3,3])+$merge(~min~, {1,2,3}, {2,4})+$merge(~max~, {1,2,3}, {2,4})+$merge(~.-~, {1,2,3}, {2,4})+$merge(\p. {? (x + y) mod 2 when p is (x,y) ?}, {1,2,3}, {2,4})
+ test/containers-ops/expected view
@@ -0,0 +1,60 @@+{1, 2}+{} union {1, 2} : Set(ℕ)+{2, -3} union {1 / 2} : Set(ℚ)+{1, 2, 3}+{4}+{}+{1}+true+true+true+{{}, {1}, {1, 2}, {1, 2, 3}, {1, 3}, {2}, {2, 3}, {3}}+{{}}+{{}, {1}}+{{}, {{}}}+{{}, {{}}, {{}, {{}}}, {{{}}}}+power(power(power({}))) : Set(Set(Set(Set(ℕ))))+{{}, {"hi"}, {"hi", "there"}, {"there"}}+⟅1, 2⟆+⟅1, 2⟆+bag([]) union bag([1, 2]) : Bag(ℕ)+bag([2, -3]) union bag([1 / 2]) : Bag(ℚ)+⟅1, 2 # 5, 3⟆+⟅4⟆+⟅⟆+⟅2 # 2, 3, 5⟆+⟅1⟆+true+true+true+⟅⟅⟆⟆+⟅⟅⟆, ⟅1⟆⟆+⟅⟅⟆, ⟅1⟆, ⟅1, 2⟆, ⟅2⟆⟆+⟅⟅⟆, ⟅'a'⟆, ⟅'a', 'b'⟆ # 2, ⟅'a', 'b' # 2⟆, ⟅'b'⟆ # 2, ⟅'b' # 2⟆⟆+⟅⟅⟆, ⟅'a'⟆ # 2, ⟅'a', 'b'⟆ # 6, ⟅'a', 'b' # 2⟆ # 6, ⟅'a', 'b' # 3⟆ # 2, ⟅'a' # 2⟆, ⟅'a' # 2, 'b'⟆ # 3, ⟅'a' # 2, 'b' # 2⟆ # 3, ⟅'a' # 2, 'b' # 3⟆, ⟅'b'⟆ # 3, ⟅'b' # 2⟆ # 3, ⟅'b' # 3⟆⟆+true+true+false+false+true+false+false+true+true+true+false+false+true+false+true+true+⟅'x' # 3, 'y' # 2⟆+⟅'x' # 3, 'y'⟆+⟅'x', 'y'⟆+{(1, 3), (2, 1), (3, 2)}+⟅1 # 4, 2 # 3⟆+⟅1 # 3, 2, 3 # 2⟆+⟅'a' # 5, 'b' # 2⟆+⟅'a' # 4, 'b' # 4⟆+⟅'a' # 3, 'b' # 4⟆+⟅'a' # 4, 'b' # 4⟆
+ test/containers-ops/input view
@@ -0,0 +1,60 @@+{} union {1,2}+:type {} union {1,2}+:type {2,-3} union {1/2}+{1,2,2} union {2,2,2,3}+{3,4,5} intersect {4,2}+{3,4,5} intersect {1,6}+{1,2,3} \ {3,2}+({5,6} ⊆ {5,6,7} : Bool)+{} ⊆ {{6}}+{{6}} ⊆ {{6}}+power {1,2,3}+power {}+power {1,1,1}+power (power {})+power (power (power {}))+:type power (power (power {}))+power {"hi", "there"}+bag [] union bag [1,2]+bag [] union bag [1,2] : Bag(N)+:type bag [] union bag [1,2]+:type bag [2,-3] union bag [1/2]+bag [1,2,2] union bag [2,2,2,3]+bag [3,4,5] intersect bag [4,2]+bag [3,4,5] intersect bag [1,6]+bag [2,2,2,3,2,4,5] intersect bag [2,3,2,5,5,6]+bag [1,2,3] \ bag [3,2]+(bag [5,6] ⊆ bag [5,6,7] : Bool)+bag [] ⊆ bag [bag [6]]+bag [bag [6]] ⊆ bag [bag [6]]+power (bag [])+power (bag [1])+power (bag [1,2])+power (bag ['a', 'b', 'b'])+power (bag ['a', 'b', 'a', 'b', 'b'])+2 elem {1,2,3}+2 ∈ {1,2,3}+4 elem {1,2,3}+4 elem {}+2 elem (bag [1,3,2,3,2])+4 elem (bag [1,3,2,3,2])+4 elem (bag [])+1 elem [1,2,3]+2 elem [1,2,3]+3 elem [1,2,3]+4 elem [1,2,3]+4 elem []+[1,2] elem {[2,3], [1,2], [4,5]}+[1,2] elem {[2,3], [2,1], [4,5]}+{1,2} elem {{2,3}, {2,1}, {4,5}}+{} elem (power {1,2,3})+⟅ 'x', 'x', 'y', 'y', 'x' ⟆+⟅ 'x', 'x', 'y', 'x' ⟆+⟅ 'y', 'x' ⟆+bagCounts (bag [1,1,2,3,1,3])+bagFromCounts {(1,1), (1,3), (4,0), (2,3)}+bagFromCounts (bagCounts (bag [1,1,2,3,1,3]))+let x = 3 in ⟅ 'a' # (2 + x), 'b', 'b' ⟆+bagFromCounts [('a', 1), ('a', 1), ('a', 2), ('b', 3), ('b', 1)]+bagFromCounts {('a', 1), ('a', 1), ('a', 2), ('b', 3), ('b', 1)}+bagFromCounts ⟅('a', 1), ('a', 1), ('a', 2), ('b', 3), ('b', 1)⟆
+ test/containers-reduce/containers-reduce.disco view
@@ -0,0 +1,14 @@+import num++!!! ∀ x : ℕ. (x > 0) ==> reduce(~*~, 1, factor x) == x+dummy : Unit+dummy = unit++||| The size (cardinality) of a set.+!!! setSize {} == 0+!!! setSize {1} == 1+!!! setSize {1..10} == 10+!!! setSize {1,1,1,2,3} == 3+!!! ∀ s : Set(N). setSize s == setSize (s ∪ s)+setSize : Set(N) -> N+setSize s = reduce(~+~, 0, each (\x.1, bag s))
+ test/containers-reduce/expected view
@@ -0,0 +1,15 @@+Loading containers-reduce.disco...+Loading num.disco...+Running tests...+ dummy: OK+ setSize: OK+Loaded.+reduce : (a × a → a) × a × List(a) → a+reduce(~+~, 0, [1 .. 10]) : ℕ+55+60+true+2351+6+7+true
+ test/containers-reduce/input view
@@ -0,0 +1,10 @@+:load test/containers-reduce/containers-reduce.disco+:type reduce+:type reduce(~+~, 0, [1 .. 10])+reduce(~+~, 0, [1 .. 10])+reduce(~+~, 5, [1 .. 10])+reduce(~*~, 1, [1 .. 10]) == 10!+reduce(\p. {? d + 10*r when p is (d,r) ?}, 0, [1,5,3,2])+reduce(~+~, 0, {1, 1, 2, 3})+reduce(~+~, 0, ⟅1, 1, 2, 3⟆)+reduce(~*~, 1, factor 11846808) == 11846808
+ test/error-ambiguous/a.disco view
@@ -0,0 +1,2 @@+x : Int+x = 1
+ test/error-ambiguous/ambiguous.disco view
@@ -0,0 +1,8 @@+import a+import b++x : Int+x = 0++y : Int+y = x + 1
+ test/error-ambiguous/b.disco view
@@ -0,0 +1,2 @@+x : Int+x = 2
+ test/error-ambiguous/expected view
@@ -0,0 +1,8 @@+Loading ambiguous.disco...+Loading a.disco...+Loading b.disco...+Error: the name x is ambiguous. It could refer to:+ a.x+ ambiguous.x+ b.x+https://disco-lang.readthedocs.io/en/latest/reference/ambiguous.html
+ test/error-ambiguous/input view
@@ -0,0 +1,1 @@+:load test/error-ambiguous/ambiguous.disco
+ test/error-cyclic/cyclic.disco view
@@ -0,0 +1,3 @@+type A = B+type B = C+type C = A
+ test/error-cyclic/expected view
@@ -0,0 +1,3 @@+Loading cyclic.disco...+Error: cyclic type definition for A.+https://disco-lang.readthedocs.io/en/latest/reference/cyc-ty.html
+ test/error-cyclic/input view
@@ -0,0 +1,1 @@+:load test/error-cyclic/cyclic.disco
+ test/error-duplicatedecls/dupdecls.disco view
@@ -0,0 +1,5 @@+x : Z+x = 3++x : Z+x = 4
+ test/error-duplicatedecls/expected view
@@ -0,0 +1,3 @@+Loading dupdecls.disco...+Error: duplicate type signature for x.+https://disco-lang.readthedocs.io/en/latest/reference/dup-sig.html
+ test/error-duplicatedecls/input view
@@ -0,0 +1,1 @@+:load test/error-duplicatedecls/dupdecls.disco
+ test/error-duplicatedefns/dupdefns.disco view
@@ -0,0 +1,3 @@+x : N+x = 1+x = 2
+ test/error-duplicatedefns/expected view
@@ -0,0 +1,3 @@+Loading dupdefns.disco...+Error: duplicate definition for x.+https://disco-lang.readthedocs.io/en/latest/reference/dup-def.html
+ test/error-duplicatedefns/input view
@@ -0,0 +1,1 @@+:load test/error-duplicatedefns/dupdefns.disco
+ test/error-duplicatetydefns/duptydefns.disco view
@@ -0,0 +1,2 @@+type XYZ = N+type XYZ = Z
+ test/error-duplicatetydefns/expected view
@@ -0,0 +1,3 @@+Loading duptydefns.disco...+Error: duplicate definition for type XYZ.+https://disco-lang.readthedocs.io/en/latest/reference/dup-tydef.html
+ test/error-duplicatetydefns/input view
@@ -0,0 +1,1 @@+:load test/error-duplicatetydefns/duptydefns.disco
+ test/error-emptycase/expected view
@@ -0,0 +1,2 @@+Error: empty case expressions {? ?} are not allowed.+https://disco-lang.readthedocs.io/en/latest/reference/empty-case.html
+ test/error-emptycase/input view
@@ -0,0 +1,1 @@+{? ?}
+ test/error-names/expected view
@@ -0,0 +1,2 @@+Error: there is nothing named x.+https://disco-lang.readthedocs.io/en/latest/reference/unbound.html
+ test/error-names/input view
@@ -0,0 +1,1 @@+x + 2
+ test/error-notcon/expected view
@@ -0,0 +1,5 @@+Error: the expression+ λx : ℤ. x+must have both a function type and also the incompatible type+ List(ℤ).+https://disco-lang.readthedocs.io/en/latest/reference/notcon.html
+ test/error-notcon/input view
@@ -0,0 +1,4 @@+f : List(Int) -> List(Int)+f x = x++f (\x:Z.x)
+ test/error-notype/expected view
@@ -0,0 +1,3 @@+Error: the definition of x must have an accompanying type signature.+Try writing something like 'x : Int' (or whatever the type of x should be) first.+https://disco-lang.readthedocs.io/en/latest/reference/missingtype.html
+ test/error-notype/input view
@@ -0,0 +1,1 @@+x = 3
+ test/error-numpatterns/expected view
@@ -0,0 +1,3 @@+Loading numpatterns.disco...+Error: number of arguments does not match.+https://disco-lang.readthedocs.io/en/latest/reference/num-args.html
+ test/error-numpatterns/input view
@@ -0,0 +1,1 @@+:load test/error-numpatterns/numpatterns.disco
+ test/error-numpatterns/numpatterns.disco view
@@ -0,0 +1,3 @@+f : N -> N -> Bool+f 3 = \x.x > 2+f x y = true
+ test/error-pattype/expected view
@@ -0,0 +1,14 @@+Error: the pattern+ left(x)+is supposed to have type+ List(ℤ),+but instead it has a sum type.+https://disco-lang.readthedocs.io/en/latest/reference/pattern-type.html+Error: the pattern+ (x1, y)+is supposed to have type+ ℕ,+but instead it has a product type.+https://disco-lang.readthedocs.io/en/latest/reference/pattern-type.html+Error: the shape of two types does not match.+https://disco-lang.readthedocs.io/en/latest/reference/shape-mismatch.html
+ test/error-pattype/input view
@@ -0,0 +1,8 @@+f : List(Int) -> Bool+f (left(x)) = false++g : N -> Bool+g (x,y) = false++h : Z*Z -> Bool+h(3:Z) = false
+ test/error-polyrec/expected view
@@ -0,0 +1,4 @@+Loading polyrec.disco...+Error: in the definition of Bush(a): recursive occurrences of Bush may only have type variables as arguments.+ Bush(a × a) does not follow this rule.+https://disco-lang.readthedocs.io/en/latest/reference/no-poly-rec.html
+ test/error-polyrec/input view
@@ -0,0 +1,1 @@+:load test/error-polyrec/polyrec.disco
+ test/error-polyrec/polyrec.disco view
@@ -0,0 +1,1 @@+type Bush(a) = a + Bush(a*a)
+ test/error-qualskolem/expected view
@@ -0,0 +1,4 @@+Loading qualskolem.disco...+Error: type variable a represents any type, so we cannot assume values of that type+ can be subtracted.+https://disco-lang.readthedocs.io/en/latest/reference/qual-skolem.html
+ test/error-qualskolem/input view
@@ -0,0 +1,1 @@+:load test/error-qualskolem/qualskolem.disco
+ test/error-qualskolem/qualskolem.disco view
@@ -0,0 +1,2 @@+f : a -> a+f (-x) = x
+ test/error-tyargs/error-tyargs.disco view
@@ -0,0 +1,1 @@+type T(a,b) = Unit + a*b
+ test/error-tyargs/expected view
@@ -0,0 +1,12 @@+Loading error-tyargs.disco...+Loaded.+Error: not enough arguments for the type 'T'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html+Error: not enough arguments for the type 'T'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html+Error: too many arguments for the type 'T'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html+Error: not enough arguments for the type 'List'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html+Error: too many arguments for the type 'List'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html
+ test/error-tyargs/input view
@@ -0,0 +1,7 @@+:load test/error-tyargs/error-tyargs.disco+x : T+y : T(Int)+z : T(Int,Char)+w : T(Int,Char,Bool)+q : List+p : List(Int,Char)
+ test/error-unboundtyvar/expected view
@@ -0,0 +1,3 @@+Loading unboundtyvar.disco...+Error: Unknown type variable 'b'.+https://disco-lang.readthedocs.io/en/latest/reference/unbound-tyvar.html
+ test/error-unboundtyvar/input view
@@ -0,0 +1,1 @@+:load test/error-unboundtyvar/unboundtyvar.disco
+ test/error-unboundtyvar/unboundtyvar.disco view
@@ -0,0 +1,1 @@+type T(a) = a + b
+ test/error-unqual-base/expected view
@@ -0,0 +1,3 @@+Loading unqualbase.disco...+Error: values of type ℕ cannot be subtracted.+https://disco-lang.readthedocs.io/en/latest/reference/not-qual.html
+ test/error-unqual-base/input view
@@ -0,0 +1,1 @@+:load test/error-unqual-base/unqualbase.disco
+ test/error-unqual-base/unqualbase.disco view
@@ -0,0 +1,2 @@+f : N -> N+f (-x) = x
+ test/error-unqual/expected view
@@ -0,0 +1,2 @@+Error: values of type a2 → a3 cannot be compared.+https://disco-lang.readthedocs.io/en/latest/reference/not-qual.html
+ test/error-unqual/input view
@@ -0,0 +1,1 @@+(\x.x) == (\y.y)
+ test/error-wildcard/expected view
@@ -0,0 +1,2 @@+Error: wildcards (_) are not allowed in expressions.+https://disco-lang.readthedocs.io/en/latest/reference/wildcard-expr.html
+ test/error-wildcard/input view
@@ -0,0 +1,1 @@+1 + _
+ test/graphs-basic/expected view
@@ -0,0 +1,16 @@+emptyGraph : Graph(ℕ)+emptyGraph+vertex(1) : Graph(ℕ)+vertex(1)+map({(1, {})})+vertex(1) + vertex(2) : Graph(ℕ)+vertex(1) + vertex(2)+map({(1, {}), (2, {})})+vertex('a') + vertex('b') + vertex('c') : Graph(Char)+vertex('a') + vertex('b') + vertex('c')+map({('a', {}), ('b', {}), ('c', {})})+(vertex(1) + vertex(2)) * vertex(3) : Graph(ℕ)+(vertex(1) + vertex(2)) * vertex(3)+map({(1, {3}), (2, {3}), (3, {})})+vertex(1) * (vertex(2) * (vertex(3) * (vertex(4) * (vertex(5) * emptyGraph))))+map({(1, {2, 3, 4, 5}), (2, {3, 4, 5}), (3, {4, 5}), (4, {5}), (5, {})})
+ test/graphs-basic/input view
@@ -0,0 +1,17 @@+:type emptyGraph+emptyGraph+:type vertex 1+vertex 1+summary (vertex 1)+:type vertex 1 + vertex 2+vertex 1 + vertex 2+summary (vertex 1 + vertex 2)+:type vertex 'a' + vertex 'b' + vertex 'c'+vertex 'a' + vertex 'b' + vertex 'c'+summary (vertex 'a' + vertex 'b' + vertex 'c')+:type (vertex 1 + vertex 2) * vertex 3+(vertex 1 + vertex 2) * vertex 3+summary ((vertex 1 + vertex 2) * vertex 3)+import list+foldr(~*~, emptyGraph, each(vertex, [1..5]))+summary it
+ test/graphs-equality/expected view
@@ -0,0 +1,4 @@+true+true+true+true
+ test/graphs-equality/input view
@@ -0,0 +1,4 @@+emptyGraph == emptyGraph+vertex(1) + vertex(2) == vertex(2) + vertex(1)+vertex(1) * (vertex 2 + vertex 3) == vertex 1 * vertex 2 + vertex 1 * vertex 3+vertex 1 * vertex 2 * vertex 3 == vertex 1 * vertex 2 + vertex 1 * vertex 3 + vertex 2 * vertex 3
+ test/interp-loop/expected view
@@ -0,0 +1,1 @@+Error: infinite loop detected!
+ test/interp-loop/input view
@@ -0,0 +1,2 @@+x : N+x = x
+ test/interp-strictmatch/bomb.disco view
@@ -0,0 +1,3 @@+-- A function to produce a runtime exception at any type+bomb : a -> a+bomb x = {? x if 0 == 1 ?}
+ test/interp-strictmatch/expected view
@@ -0,0 +1,13 @@+Error: division by zero.+Error: division by zero.+Error: division by zero.+Error: division by zero.+Loading bomb.disco...+Loaded.+Error: value did not match any of the branches in a case expression.+Error: value did not match any of the branches in a case expression.+Error: value did not match any of the branches in a case expression.+Error: value did not match any of the branches in a case expression.+Error: value did not match any of the branches in a case expression.+Error: division by zero.+Error: value did not match any of the branches in a case expression.
+ test/interp-strictmatch/input view
@@ -0,0 +1,13 @@+{? x when (0, 0/0) is (x+1, 3) ?}+{? x when (0, 0/0) is (x+1, 3), 2 otherwise ?}+{? x when [1,2,3/0] is [x,3,5], 2 otherwise ?}+{? x when [1,2,3/0] is [x,2,5], 2 otherwise ?}++:load test/interp-strictmatch/bomb.disco+{? 1 when (0, bomb 'a' ) is (x+1, 'a' ), 2 otherwise ?}+{? 1 when (0, bomb true) is (x+1, true), 2 otherwise ?}+{? 1 when (0, bomb unit) is (x+1, unit), 2 otherwise ?}+{? 1 when (0, bomb [] ) is (x+1, [] ), 2 otherwise ?}+{? 1 when (0, bomb "xy") is (x+1, "xy"), 2 otherwise ?}+{? 1 when left (0/0) is right 3, 2 otherwise ?}+{? 1 when left (bomb 'a') is right 'a', 2 otherwise ?}
+ test/lib-oeis/expected view
@@ -0,0 +1,8 @@+Loading oeis.disco...+right("https://oeis.org/A000045")+left(■)+left(■)+[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131]+[]+[1, 10011]+[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131]
+ test/lib-oeis/input view
@@ -0,0 +1,22 @@+import oeis++-- valid sequence+lookupSequence [1,1,2,3,5]++-- empty list+lookupSequence []++-- unknown sequence+lookupSequence [1,10011]++-- known sequence+extendSequence [1,3,5,7]++-- empty list+extendSequence []++-- unknown sequence+extendSequence [1,10011]++-- extend a long sequence+extendSequence [1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79,81,83,85,87,89,91,93,95,97,99]
+ test/list-comp/expected view
@@ -0,0 +1,11 @@+Loading list.disco...+Loading num.disco...+[1, 2, 3]+[]+[12, 22, 32, 13, 23, 33, 14, 24, 34, 15, 25, 35, 16, 26, 36, 17, 27, 37]+[(3, 4, 5), (5, 12, 13), (6, 8, 10), (9, 12, 15)]+[1]+[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]+[(10, 10), (10, 8), (10, 6), (10, 4), (10, 2), (8, 8), (8, 6), (8, 4), (8, 2), (6, 6), (6, 4), (6, 2), (4, 4), (4, 2), (2, 2)]+[(x, y) | x in [10 .. 1], even(x), y in [x .. 1], even(y)] : List(ℕ × ℕ)+[(x, y) | x in [10 .. 1], even(x), y in [x .. 1], even(y)] : List(ℕ × ℕ)
+ test/list-comp/input view
@@ -0,0 +1,12 @@+import list+import num++[x | x in [1,2,3]]+[x | x in [1,2,3], y in ([] : List(Q))]+[x+y+z | z in [1,2,3], x in [z,z+1], y in [10,20,30]]+[(a,b,c) | a in [1..15], b in [a..15], c in [b..15], a^2 + b^2 == c^2]+[g | a in [1], b in [a], c in [b], d in [c], e in [d], f in [e], g in [f]]+[g | a in [1], b in [a,a], c in [b,b], d in [c,c], e in [d,d], f in [e,e], g in [f,f]]+[(x,y) | x in [10..1], even x, y in [x..1], even y]+:type [(x,y) | x in [10..1], even x, y in [x..1], even y]+:type [(x,y) | x <- [10..1], even x, y <- [x..1], even y]
+ test/list-poly/expected view
@@ -0,0 +1,32 @@+Loading list.disco...+[1, 2, 3, 4, 5]+[5, 4, 3, 2, 1]+[1, 2, 3, 4, 5]+[1, 2, 3, 4, 5]+[1, 2, 3, 4, 5]+[1, 2, 3, 4, 5]+[]+[1]+[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]+[1, 3, 5, 7, 9]+[0, 1/2, 1, 3/2, 2, 5/2, 3]+[10, 9, 8, 7, 6]+[1, 3, 6, 10, 15, 21]+[1, 4, 9, 16, 25, 36]+true+[]+[1, 3, 4, 4, 3, 1, -2, -6, -11, -17]+[1, 3/2, 2, 5/2, 3]+[1, 3/2, 2, 5/2, 3]+[1, 3, 6, 2, 17, 28, 32, 4, 77, 3, 8, 12, -50399, -532856]+[1 .. 5] : List(ℕ)+[1, 3 .. 5] : List(ℕ)+[1, -3 .. 5] : List(ℤ)+[0, 1 / 3 .. 5] : List(𝔽)+[-2, 1 / 3 .. 5] : List(ℚ)+[2, 3 .. -6] : List(ℤ)+[2, 3 .. 8 / 3] : List(𝔽)+[-2, 3 .. 8 / 3] : List(ℚ)+[1.5]+[1, 2, 3, 4, 5]+[1, 2, 3, 4, 5]
+ test/list-poly/input view
@@ -0,0 +1,32 @@+import list+[1 .. 5]+[5 .. 1]+[1 ........ 5]+[1, 2 .. 5]+[1, 2, 3 .. 5]+[1, 2, 3, 4 .. 5]+[1, 2 .. 0]+[1 .. 1]+take(10, [1, 1 .. 1])+[1, 3 .. 10]+[0, 1/2 .. 3]+[10, 9 .. 6]+[1, 3, 6 ... 21]+[1, 4, 9 ... 38]+[x^3 | x in [1 .. 100]] == [1,8,27,64 .. 100^3]+[1, 3, 4 .. 10]+[1, 3, 4 .. -20]+[1.0, 3/2 .. 3]+[1, 3/2 .. 3.0]+take(14, [1, 3, 6, 2, 17, 28, 32, 4, 77, 3, 8, 12 .. -1000000])+:type [1 .. 5]+:type [1, 3 .. 5]+:type [1, -3 .. 5]+:type [0, 1/3 .. 5]+:type [-2, 1/3 .. 5]+:type [2, 3 .. -6]+:type [2, 3 .. 8/3]+:type [-2, 3 .. 8/3]+[1.5]+[1..5]+[1...5]
+ test/logic-bools/expected view
@@ -0,0 +1,20 @@+true+false+true+false+true+false+false+false+false+true+true+true+false+true+false+true+true+false+true+true
+ test/logic-bools/input view
@@ -0,0 +1,20 @@+true+false+True+False+true and true+true and false+false and true+false and false+true && false+true or true+true or false+false or true+false or false+true || false+not true+not false+true ==> true +true ==> false +false ==> true +false ==> false
+ test/logic-cmp/expected view
@@ -0,0 +1,45 @@+true+false+false+true+true+true+false+true+false+false+true+true+false+false+true+true+false+true+true+false+true+false+true+false+false+true+true+true+false+true+false+true+true+false+true+true+false+false+true+3+5+1+5+[1, 2, 3, 5, 2, 6]+[1, 2, 1, 3, 1, 2]
+ test/logic-cmp/input view
@@ -0,0 +1,45 @@+1 < 2+2 < 2+3 < 2+1/2 < 2/3+1 <= 2+2 <= 2+3 <= 2+3 > 1+3 > 3+3 > 4+3 >= 1+3 >= 3+3 >= 4+3 == 5+3 == 3+3 /= 5+3 /= 3+(1,2) < (1,3)+(1,3) < (2,1)+(2,2) < (2,1)+(2,3) == (2,3)+(2,3) == (2,4)+[1,2,3] == [1,2,3]+[1,2,3] == [1,2,3,4]+[1,2,3] == [1,2,4]+([] : List(N)) < [1]+[1,2] < [1,2,3]+[1,2] < [2]+[1,2,3,5,2,6] < [1,2,1,3,1,2]+[1,2,3,5,2,6] < [1,2,4,3,1,2]+(left 3 : N + N) < (left 2 : N + N)+(left 3 : N + N) < (left 4 : N + N)+(left 3 : N + N) < (right 1 : N + N)+(right 3 : N + N) < (right 1 : N + N)+(right 3 : N + N) < (right 4 : N + N)+unit <= unit+unit < unit+false < false+false < true+3 min 5+3 max 5+3 min 2 min 5 min 1 min 4+3 max 2 max 5 max 1 max 4+[1,2,3,5,2,6] max [1,2,1,3,1,2]+[1,2,3,5,2,6] min [1,2,1,3,1,2]
+ test/map-basic/expected view
@@ -0,0 +1,23 @@+Loading list.disco...+map : Set(ℕ × a) → Map(ℕ, a)+map({(1, 3), (4, 6)}) : Map(ℕ, ℕ)+map({(1, 3), (4, 6)})+insert : ℕ × a × Map(ℕ, a) → Map(ℕ, a)+map({}) : Map(ℕ, a)+map({})+insert(1, 3, insert(4, 6, map({}))) : Map(ℕ, ℕ)+map({(1, 3), (4, 6)})+map({(1, 3)})+lookup : ℕ × Map(ℕ, a) → Unit + a+right(3)+right(6)+left(■)+map({(1, "hello"), (3, "there"), (4, "you")})+map({(1, 3)})+insert(1, {"hi", "there"}, insert(2, {}, insert(4, {"why", "not", "now"}, insert(2, {"blah"}, map({}))))) : Map(ℕ, Set(List(Char)))+map({(1, {"hi", "there"}), (2, {}), (4, {"not", "now", "why"})})+map({(1, "A")})+map({(1, "B")})+map({("hi", 1), ("there", 2)})+{}+{("hi", 1), ("there", 2)}
+ test/map-basic/input view
@@ -0,0 +1,25 @@+import list+:type map+:type map {(1,3), (4,6)}+map {(1,3), (4,6)}+:type insert+:type map {}+map {}+:type insert(1, 3, insert(4, 6, map {}))+insert(1, 3, insert(4, 6, map {}))+insert(1, 3, insert(1, 4, map {}))+m : Map(N, N)+m = insert(1, 3, insert(4, 6, map {}))+:type lookup+lookup(1,m)+lookup(4,m)+lookup(5,m)+foldr(\((k,v),m). insert(k,v,m), map {}, [(1,"hello"), (3, "there"), (4, "you")])+map {(1,3), (1,1), (1,2)}+:type insert(1, {"hi","there"}, insert(2, {}, insert(4, {"why","not","now"}, insert(2, {"blah"}, map {}))))+insert(1, {"hi","there"}, insert(2, {}, insert(4, {"why","not","now"}, insert(2, {"blah"}, map {}))))+insert(1, "A", insert(1, "B", map {}))+insert(1, "B", insert(1, "A", map {}))+insert("hi", 1, insert("there", 2, map {}))+mapToSet(map{})+mapToSet(insert("hi", 1, insert("there", 2, map {})))
+ test/map-compare/expected view
@@ -0,0 +1,4 @@+true+true+true+true
+ test/map-compare/input view
@@ -0,0 +1,4 @@+map({}) == map({})+insert(1, 'x', insert(2, 'y', map {})) == insert(2, 'y', insert(1, 'x', map {}))+map {(1, 'a')} < map {(1, 'b')}+map {(1, 'a')} < map {(2, 'b')}
+ test/module-basic/a.disco view
@@ -0,0 +1,7 @@+-- Test to check whether importing a single module which doesn't import any other modules works as+-- expeced++import b++x : N+x = 3 + y
+ test/module-basic/b.disco view
@@ -0,0 +1,2 @@+y : N+y = 5
+ test/module-basic/c.disco view
@@ -0,0 +1,6 @@+-- Test for importing the standard library++import list++l : List(N)+l = each(\x. x + 1, [1,2,3])
+ test/module-basic/e.disco view
@@ -0,0 +1,5 @@+-- Importing a module in a subdirectory, with a .disco extension+import subdir/d.disco++zz : Z+zz = qq + 1
+ test/module-basic/expected view
@@ -0,0 +1,13 @@+Loading a.disco...+Loading b.disco...+Loaded.+8+5+Loading c.disco...+Loading list.disco...+Loaded.+[2, 3, 4]+Loading e.disco...+Loading subdir/d.disco...+Loaded.+-9
+ test/module-basic/input view
@@ -0,0 +1,7 @@+:l test/module-basic/a.disco+x+y+:l test/module-basic/c.disco+l+:l test/module-basic/e.disco+zz
+ test/module-basic/subdir/d.disco view
@@ -0,0 +1,2 @@+qq : Z+qq = -10
+ test/module-cycle/cyclic1.disco view
@@ -0,0 +1,3 @@+import cyclic2++x = 3
+ test/module-cycle/cyclic2.disco view
@@ -0,0 +1,3 @@+import cyclic1++y = 2
+ test/module-cycle/expected view
@@ -0,0 +1,4 @@+Loading cyclic1.disco...+Loading cyclic2.disco...+Error: module imports form a cycle:+ cyclic1 -> cyclic2 -> cyclic1
+ test/module-cycle/input view
@@ -0,0 +1,1 @@+:load test/module-cycle/cyclic1
+ test/module-notfound/expected view
@@ -0,0 +1,1 @@+Error: couldn't find a module named 'foo'.
+ test/module-notfound/input view
@@ -0,0 +1,1 @@+import foo
+ test/parse-245/expected view
@@ -0,0 +1,4 @@+2 .- 4 : ℕ+2 .- 4 : ℕ+0+0
+ test/parse-245/input view
@@ -0,0 +1,4 @@+:type 2.-4+:type 2 .- 4+2.-4+2 .- 4
+ test/parse-280/capitalvars.disco view
@@ -0,0 +1,5 @@+A : Set(ℕ)+A = {1, 3, 6}++B : Set(ℕ)+B = { x+1 | x <- A, x mod 2 == 1 }
+ test/parse-280/expected view
@@ -0,0 +1,2 @@+Loading capitalvars.disco...+Loaded.
+ test/parse-280/input view
@@ -0,0 +1,1 @@+:load test/parse-280/capitalvars.disco
+ test/parse-case-expr/expected view
@@ -0,0 +1,1 @@+[false, false]
+ test/parse-case-expr/input view
@@ -0,0 +1,1 @@+let n = 3 in {? true if 2 divides n, false otherwise ?} :: false :: []
+ test/parse-nested-list/expected view
@@ -0,0 +1,2 @@+[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[10]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] : List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(List(ℕ))))))))))))))))))))))))))))))+[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[10]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
+ test/parse-nested-list/input view
@@ -0,0 +1,2 @@+:type [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[10]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]+[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[10]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
+ test/parse-quantifiers/expected view
@@ -0,0 +1,37 @@+TAbs_ Lam () (<[PVar_ () x]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Lam () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Lam () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PVar_ () y]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Lam () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Lam () (<[PVar_ () x,PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Lam () (<[PVar_ () x]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Lam () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Lam () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PVar_ () y]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Lam () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Lam () (<[PVar_ () x,PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+8+Error: the shape of two types does not match.+https://disco-lang.readthedocs.io/en/latest/reference/shape-mismatch.html+8+8+8+TAbs_ Ex () (<[PVar_ () x]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Ex () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Ex () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PVar_ () y]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Ex () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Ex () (<[PVar_ () x,PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Ex () (<[PVar_ () x]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Ex () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ Ex () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PVar_ () y]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Ex () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ Ex () (<[PVar_ () x,PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ All () (<[PVar_ () x]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ All () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ All () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PVar_ () y]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ All () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ All () (<[PVar_ () x,PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ All () (<[PVar_ () x]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ All () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TNat_ () 3]))+TAbs_ All () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PVar_ () y]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ All () (<[PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ All () (<[PVar_ () x,PAscr_ () (PVar_ () y) (TyAtom (ABase F))]> TApp_ () (TPrim_ () (PrimBOp Gt)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1]))+TAbs_ All () (<[PTup_ () [PAscr_ () (PVar_ () x) (TyAtom (ABase N)),PAscr_ () (PVar_ () y) (TyAtom (ABase N)),PAscr_ () (PVar_ () z) (TyAtom (ABase N))]]> TApp_ () (TPrim_ () (PrimBOp Impl)) (TTup_ () [TApp_ () (TPrim_ () (PrimBOp And)) (TTup_ () [TParens_ () (TApp_ () (TPrim_ () (PrimBOp Eq)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@1])),TParens_ () (TApp_ () (TPrim_ () (PrimBOp Eq)) (TTup_ () [TVar_ () 0@1,TVar_ () 0@2]))]),TApp_ () (TPrim_ () (PrimBOp Eq)) (TTup_ () [TVar_ () 0@0,TVar_ () 0@2])]))
+ test/parse-quantifiers/input view
@@ -0,0 +1,36 @@+:parse λ x. x > 3+:parse λ (x:N). x > 3+:parse λ (x:N), y. x > y+:parse λ (x:N), (y:F). x > y+:parse λ x, (y:F). x > y+:parse \ x. x > 3+:parse \ (x:N). x > 3+:parse \ (x:N), y. x > y+:parse \ (x:N), (y:F). x > y+:parse \ x, (y:F). x > y+(\x, y. x + y) 3 5+(\(x : N, y : N). x + y) 3 5+(\(x : N, y : N). x + y) (3, 5)+(\(x:N), (y:N). x + y) 3 5+(\(x:N). \(y:N). x + y) 3 5+:parse exists x. x > 3+:parse exists (x:N). x > 3+:parse exists (x:N), y. x > y+:parse exists (x:N), (y:F). x > y+:parse exists x, (y:F). x > y+:parse ∃ x. x > 3+:parse ∃ (x:N). x > 3+:parse ∃ (x:N), y. x > y+:parse ∃ (x:N), (y:F). x > y+:parse ∃ x, (y:F). x > y+:parse forall x. x > 3+:parse forall (x:N). x > 3+:parse forall (x:N), y. x > y+:parse forall (x:N), (y:F). x > y+:parse forall x, (y:F). x > y+:parse ∀ x. x > 3+:parse ∀ (x:N). x > 3+:parse ∀ (x:N), y. x > y+:parse ∀ (x:N), (y:F). x > y+:parse ∀ x, (y:F). x > y+:parse ∀ (x : N, y : N, z: N). (x == y) and (y == z) ==> x == z
+ test/parse-top-term/expected view
@@ -0,0 +1,2 @@+Loading parse-top-term.disco...+Loaded.
+ test/parse-top-term/input view
@@ -0,0 +1,1 @@+:load test/parse-top-term/parse-top-term.disco
+ test/parse-top-term/parse-top-term.disco view
@@ -0,0 +1,14 @@+g : N -> N+g(x) = x + 1++-- each(g, f)++3 + 17++4+ + 9+ + 7+ + 16++f : List(N)+f = [1,2,3]
+ test/poly-bad/expected view
@@ -0,0 +1,12 @@+Error: the shape of two types does not match.+https://disco-lang.readthedocs.io/en/latest/reference/shape-mismatch.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html+Error: the shape of two types does not match.+https://disco-lang.readthedocs.io/en/latest/reference/shape-mismatch.html+Error: the shape of two types does not match.+https://disco-lang.readthedocs.io/en/latest/reference/shape-mismatch.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html
+ test/poly-bad/input view
@@ -0,0 +1,6 @@+let f : a -> a = \x. x+1 in f+let f : a -> a = \x. x+x in f+let f : a -> b -> a = \x, y. y in f+let f : a*b -> b = \p. {? a when p is (a,_) ?} in f+let f : List(a) -> List(a) = \x. list (bag x) in f+let f : a -> a -> Bool = \x, y. x < y in f
+ test/poly-infer-sort/expected view
@@ -0,0 +1,11 @@+let f : (a → a) → a → a = λg. λx. g(g(x)) in f(λx. x + x) : ℕ → ℕ+let f : (a → a) → a → a = λg. λx. g(g(x)) in f(λx. x - x) : ℤ → ℤ+let f : (a → a) → a → a = λg. λx. g(g(x)) in f(λx. x / x) : 𝔽 → 𝔽+let f : (a → a) → a → a = λg. λx. g(g(x)) in f(λx. -x / x) : ℚ → ℚ+λx. x : a → a+λx, y. x : a1 → a → a1+λx, y, z. x + y + z : ℕ → ℕ → ℕ → ℕ+λx, y : ℕ. x - y : ℤ → ℕ → ℤ+λw, x : ℕ, y, z : 𝔽. w - x + y + z : ℚ → ℕ → ℚ → 𝔽 → ℚ+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html
+ test/poly-infer-sort/input view
@@ -0,0 +1,10 @@+:type let f : (a -> a) -> a -> a = \g. (\x. g (g x)) in f (\x. x+x)+:type let f : (a -> a) -> a -> a = \g. (\x. g (g x)) in f (\x. x-x)+:type let f : (a -> a) -> a -> a = \g. (\x. g (g x)) in f (\x. x/x)+:type let f : (a -> a) -> a -> a = \g. (\x. g (g x)) in f (\x. -x/x)+:type \x.x+:type \x, y. x+:type \x, y, z. x + y + z+:type \x, (y:Nat). x - y+:type \w, (x:Nat), y, (z : Frac). w - x + y + z+\x, (y:Bool). x - y
+ test/poly-instantiate/expected view
@@ -0,0 +1,12 @@+Loading poly-instantiate.disco...+Loaded.+foldr : (a → r → r) → r → List(a) → r+foldr(λx, y. x) : a → List(a) → a+foldr(λx, y. y) : r → List(a) → r+foldr(λx, y. x + 1) : ℕ → List(ℕ) → ℕ+foldr(λx, y. y + 1) : ℕ → List(a) → ℕ+foldr(λx, y. y + 1)(1) : List(a) → ℕ+foldr(λx, y. y + 1)(-1) : List(a) → ℤ+foldr(λx, y. x)(false) : List(Bool) → Bool+foldr(λx, y. x + 1)(1 / 2) : List(ℕ) → 𝔽+foldr(λx, y. x - 1)(1 / 2) : List(ℤ) → ℚ
+ test/poly-instantiate/input view
@@ -0,0 +1,11 @@+:load test/poly-instantiate/poly-instantiate.disco+:type foldr+:type foldr (\x, y. x)+:type foldr (\x, y. y)+:type foldr (\x, y. x+1)+:type foldr (\x, y. y+1)+:type foldr (\x, y. y+1) 1+:type foldr (\x, y. y+1) (-1)+:type foldr (\x, y. x) false+:type foldr (\x, y. x+1) (1/2)+:type foldr (\x, y. x-1) (1/2)
+ test/poly-instantiate/poly-instantiate.disco view
@@ -0,0 +1,5 @@+using NoStdLib++foldr : (a -> r -> r) -> r -> List(a) -> r+foldr _ z [] = z+foldr f z (a :: as) = f a (foldr f z as)
+ test/poly-rectype/expected view
@@ -0,0 +1,2 @@+Loading poly-rectype.disco...+Loaded.
+ test/poly-rectype/input view
@@ -0,0 +1,1 @@+:load test/poly-rectype/poly-rectype.disco
+ test/poly-rectype/poly-rectype.disco view
@@ -0,0 +1,10 @@+type X = Unit + X++lst : List(X)+lst = [left(■), right(left(■))]++f : List(a) -> N+f(_) = 3++n : N+n = f(lst)
+ test/pretty-defn/expected view
@@ -0,0 +1,5 @@+f : ℕ → ℕ+f(x) = x + 2+type P = ℕ × ℕ+type Pair(a) = a × a+type HPair(a, b) = a × b
+ test/pretty-defn/input view
@@ -0,0 +1,10 @@+f : N -> N+f x = x+2+type P = N * N+type Pair(a) = a * a+type HPair(a,b) = a * b++:defn f+:defn P+:defn Pair+:defn HPair
+ test/pretty-functions/expected view
@@ -0,0 +1,3 @@+<a1 → a1>+<(a1 → a2) × List(a1) → List(a2)>+<List(List(a6)) → List(List(a6))>
+ test/pretty-functions/input view
@@ -0,0 +1,3 @@+\x.x+each+\xs. each (list,xs)
+ test/pretty-issue258/expected view
@@ -0,0 +1,12 @@+Loading catalan.disco...+Loading list.disco...+Loading oeis.disco...+Loaded.+treesOfSize : ℕ → List(BT)+treesOfSize(0) = [left(■)]+treesOfSize(k + 1) = [right(l, r) | x in [0 .. k], l in treesOfSize(x), r in treesOfSize(k .- x)]+Loading tree.disco...+Loaded.+treeFold : r11 × (ℕ × r11 × r11 → r11) × Tree → r11+treeFold(x, f, left(■)) = x+treeFold(x, f, right(n, l, r)) = f(n, treeFold(x, f, l), treeFold(x, f, r))
+ test/pretty-issue258/input view
@@ -0,0 +1,4 @@+:load example/catalan.disco+:defn treesOfSize+:load example/tree.disco+:defn treeFold
+ test/pretty-lit/expected view
@@ -0,0 +1,3 @@+1+1.0+3.0 / -7 : ℚ
+ test/pretty-lit/input view
@@ -0,0 +1,3 @@+:pretty 1+:pretty 1.0+:type 3.0/(-7)
+ test/pretty-ops/expected view
@@ -0,0 +1,6 @@+not true+5!+6 + 3+6 + 5 * 2+6 + 5 * 2+(6 + 5) * 2
+ test/pretty-ops/input view
@@ -0,0 +1,6 @@+:pretty ¬true+:pretty 5!+:pretty 6 + 3+:pretty 6+5*2+:pretty 6+(5*2)+:pretty (6+5)*2
+ test/pretty-pattern/expected view
@@ -0,0 +1,11 @@+λ5 * (x + 3). x+λ(x + 3) * 5. x+λ5 * (x - 3). x+λ(x - 3) * 5. x+λ5 + x - 3. x+λ5 + x - 3. x+λ5 + (x - 3). x+λ1 + x + 2 + 3. x+λ1 + (x + (2 + 3)). x+λ(p + 1) / (q + 1). p + q+λ-p / q. p + q
+ test/pretty-pattern/input view
@@ -0,0 +1,11 @@+:pretty \(5 * (x + 3)).x+:pretty \((x + 3) * 5).x+:pretty \(5 * (x - 3)).x+:pretty \((x - 3) * 5).x+:pretty \(5 + x - 3).x+:pretty \((5 + x) - 3).x+:pretty \(5 + (x - 3)).x+:pretty \1 + x + 2 + 3.x+:pretty \1 + (x + (2 + 3)).x+:pretty \(p+1)/(q+1).p+q+:pretty \-p/q.p+q
+ test/pretty-torture/expected view
@@ -0,0 +1,25 @@+Loading demo.disco...+Loaded.+{} : Set(Map(Set(ℕ), Map(Set(ℕ), Graph(Set(ℕ)))))+right(■) : P(Set(ℕ)) + Unit+[1, 2 : ℕ, 3]+TAbs_ Lam () (<[PVar_ () x]> TAscr_ () (TVar_ () 0@0) (Forall (<[]> TyAtom (ABase N))))+λx. x : ℕ+(λx. x) : ℕ+λx. x : ℕ+let f = λx. x + 1 : ℕ → ℕ in f : ℕ → ℕ+(let f = λx. x + 1 : ℕ → ℕ in f) : ℕ → ℕ+let f = λx. x + 1 : ℕ → ℕ in f(3 : ℕ)+let f = λx. x + 1 : ℕ → ℕ in f(3) : ℕ+(let f = λx. x + 1 : ℕ → ℕ in f)(3) : ℕ+(let f = λx. x + 1 : ℕ → ℕ in f)(3 : ℕ)+(let x = 3 in x) : ℕ+let x = 3 in x : ℕ+let x = 3 in x : ℕ+λx : ℕ. x+λ(x : ℕ) + 1. x+λx + 1 : ℕ. x+(λx. x)(2, 3)+right(2, 3)+Loading num.disco...+lg(24)!
+ test/pretty-torture/input view
@@ -0,0 +1,24 @@+:load example/demo.disco+:pretty {} : Set(Map(Set(ℕ), Map(Set((N)), ((Graph (Set(N)))))))+:pretty right(■) : P (Set(N)) + Unit+:pretty [1, 2 : N, 3]+:parse \x. x : N+:pretty \x. (x : ℕ)+:pretty (\x. x) : ℕ+:pretty \x. x : ℕ+:pretty let f = (λx. x + 1 : ℕ → ℕ) in (f : ℕ → ℕ)+:pretty (let f = λx. x + 1 : ℕ → ℕ in f) : ℕ → ℕ+:pretty let f = λx. x + 1 : ℕ → ℕ in f (3 : N)+:pretty let f = λx. x + 1 : ℕ → ℕ in f 3 : N+:pretty (let f = λx. x + 1 : ℕ → ℕ in f) 3 : N+:pretty (let f = λx. x + 1 : ℕ → ℕ in f) (3 : N)+:pretty (let x = 3 in x) : N+:pretty let x = 3 in (x : N)+:pretty let x = 3 in x : N+:pretty \(x:N).x+:pretty \((x:N)+1).x+:pretty \(x+1 : N).x+:pretty (\x.x)(2,3)+:pretty right(2,3)+import num+:pretty (lg 24)!
+ test/pretty-type/expected view
@@ -0,0 +1,3 @@+[1, 2, 3, 4] : List(ℕ)+[[1, 2], [3, 4]] : List(List(ℕ))+[[(2, true), (3, false)], [(-5, true)]] : List(List(ℤ × Bool))
+ test/pretty-type/input view
@@ -0,0 +1,3 @@+:type [1,2,3,4]+:type [[1,2],[3,4]]+:type [[(2,true), (3,false)], [(-5,true)]]
+ test/pretty-whnf/expected view
@@ -0,0 +1,2 @@+left(■)+<Void → ℕ>
+ test/pretty-whnf/input view
@@ -0,0 +1,2 @@+left(■) : Unit + Nat+\(x:Void). 3
+ test/prim-crash/expected view
@@ -0,0 +1,11 @@+Loading prim.disco...+User crash: nope+User crash: nope+User crash: nope+let x : ℕ = crash("nope") in 3 + x : ℕ+User crash: nope+crash : List(Char) → a+User crash: nope+User crash: nope+(λx : ℕ. 1)(crash("nope")) : ℕ+User crash: nope
+ test/prim-crash/input view
@@ -0,0 +1,11 @@+import prim+crash "nope"+let x : N = crash "nope" in 3 + 4+let x : List(N) = crash "nope" in 3 + 4+:type let x : N = crash "nope" in 3 + x+let x : N = crash "nope" in 3 + x+:type crash+(\(x: List(N)). 1) (crash "nope")+(\(x:N). 1) (crash "nope")+:type (\(x:N). 1) (crash "nope")+(\(x:N). 1) (1 + crash "nope")
+ test/prim-frac/expected view
@@ -0,0 +1,10 @@+$frac : ℚ → ℤ × ℕ+(0, 1)+(1, 1)+(5, 1)+(-6, 1)+(2, 3)+(2, 3)+(7, 3)+(-1, 8)+(1, 3)
+ test/prim-frac/input view
@@ -0,0 +1,11 @@+using primitives+:type $frac+$frac 0+$frac 1+$frac 5+$frac (-6)+$frac (2/3)+$frac (4/6)+$frac (7/3)+$frac (-1/8)+$frac ((-1)/(-3))
+ test/prim-sum/expected view
@@ -0,0 +1,3 @@+left : a1 → a1 + a+right : a1 → a + a1+[left(1), left(2), left(3)]
+ test/prim-sum/input view
@@ -0,0 +1,3 @@+:type left+:type right+each(left, [1,2,3])
+ test/prop-basic/expected view
@@ -0,0 +1,12 @@+Loading prop-basic.disco...+Loaded.+ - Test passed: injective(λx. x * 2)+ Checked 100 possibilities without finding a counterexample.+ - Test passed: idempotent(λx. x max 10)+ Checked 100 possibilities without finding a counterexample.+ - Test passed: commutative(λ(a, b). a * b)+ Checked 100 possibilities without finding a counterexample.+ - Test passed: associative(λ(a, b). a + b)+ Checked 100 possibilities without finding a counterexample.+ - Test passed: identityFor(0, λ(a, b). a max b)+ Checked 100 possibilities without finding a counterexample.
+ test/prop-basic/input view
@@ -0,0 +1,9 @@+:load test/prop-basic/prop-basic.disco+:test injective (\x. x * 2)+:test idempotent (\x. x max 10)+:test commutative (\(a, b). a * b)+:test associative (\(a, b). a + b)+:test identityFor(0, \(a, b). a max b)++-- Randomized testing doesn't do a good job with exists inside forall!+-- :test surjective (\x. x)
+ test/prop-basic/prop-basic.disco view
@@ -0,0 +1,23 @@+-- XXX need a syntax for writing qualified types!+-- really want to say something like (Cmp a, Cmp b) => (a -> b) -> Prop.++injective : (ℕ → ℕ) → Prop+injective(f) = ∀ x : N, y : N. (f(x) == f(y)) ==> (x == y)++surjective : (ℕ → ℕ) → Prop+surjective(f) = ∀ y : N. ∃ x : N. f x == y++-- bijective : (ℕ → ℕ) → Prop+-- bijective(f) = injective(f) and surjective(f)++idempotent : (ℕ → ℕ) → Prop+idempotent(f) = ∀ x : N. f(f(x)) == f(x)++commutative : (ℕ×ℕ → ℕ) → Prop+commutative(f) = ∀ x : N, y : N. f(x,y) == f(y,x)++associative : (ℕ×ℕ → ℕ) → Prop+associative(f) = ∀ (x : N, y : N, z : N). f(x, f(y,z)) == f(f(x,y), z)++identityFor : ℕ × (ℕ×ℕ → ℕ) → Prop+identityFor(e,f) = ∀ x : N. f(x,e) == x and f(e,x) == x
+ test/prop-cmp/expected view
@@ -0,0 +1,6 @@+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html
+ test/prop-cmp/input view
@@ -0,0 +1,3 @@+:type \(x:Prop). x == x+:type (forall (x:N). x >= 0) == (forall (y:N). y >= 0)+:type (exists (x:N). x > 0) < (forall (x:N). x > 0)
+ test/prop-fail/bad-tests.disco view
@@ -0,0 +1,21 @@+||| A function that doesn't do what it's supposed to.++!!! badmap (\x. x/0) [3,4,5] =!= [6,7,8]+!!! badmap (\x. x) [1,2] =!= [1,2]+!!! badmap (\x. x + 1) [3,4] > [5,6]++badmap : (Q -> Q) -> List(Q) -> List(Q)+badmap _ [] = [3]+badmap f (x::xs) = f x :: f x :: badmap f xs+++||| A function we have some mistaken beliefs about.++!!! forall a : Q, b : Q. divide a b * b =!= a++!!! forall a : Q. divide a 2 < a++!!! exists a : Q. divide a 2 =!= abs a + 1++divide : Q -> Q -> Q+divide a b = a / b
+ test/prop-fail/expected view
@@ -0,0 +1,21 @@+Loading bad-tests.disco...+Running tests...+ badmap:+ - Test failed: badmap(λx. x / 0)([3, 4, 5]) =!= [6, 7, 8]+ DivByZero+ - Test result mismatch for: badmap(λx. x)([1, 2]) =!= [1, 2]+ - Left side: [1, 2]+ - Right side: [1, 1, 2, 2, 3]+ - Test is false: badmap(λx. x + 1)([3, 4]) > [5, 6]+ divide:+ - Test failed: ∀a, b. divide(a)(b) * b =!= a+ DivByZero+ Counterexample:+ a = 0+ b = 0+ - Test is false: ∀a. divide(a)(2) < a+ Counterexample:+ a = 0+ - No example was found: ∃a. divide(a)(2) =!= abs(a) + 1+ Checked 50 possibilities.+Loaded.
+ test/prop-fail/input view
@@ -0,0 +1,1 @@+:load test/prop-fail/bad-tests.disco
+ test/prop-fairness/expected view
@@ -0,0 +1,4 @@+false+true+true+true
+ test/prop-fairness/input view
@@ -0,0 +1,4 @@+holds (forall (a : Q), (b : Q). abs a < 4 or abs b < 4)+holds (exists (a: N), (b : N), (c : N). (0 < a < b < c) and (a^2 + b^2 == c^2))+holds (exists (a : N, b : N, c : N). (0 < a < b < c) and (a^2 + b^2 == c^2))+holds (exists (x:N). exists (y:N). exists (z:N). (0 < x < y < z) and (x^2 + y^2 == z^2))
+ test/prop-higher-order/expected view
@@ -0,0 +1,19 @@+Loading higher-order.disco...+Running tests...+ pand: OK+ por: OK+Loaded.+ - Test passed: ∀x. por(x =!= 0, ∃n. n * x >= 1)+ Checked 100 possibilities without finding a counterexample.+ - Test passed: ∀f. any([∀x. f(x) =!= not x, ∀x. f(x) =!= x, ∀x. f(x) =!= false, ∀x. f(x) =!= true])+ No counterexamples exist.+ - Test is false: all([true, true, true, false, true])+ Counterexample:+ and_side = right(■)+ and_side = right(■)+ and_side = right(■)+ and_side = left(■)+ - Test passed: ∃k. hasFactors(2 ^ k + 1)+ Found example:+ k = 3+ n = 3
+ test/prop-higher-order/higher-order.disco view
@@ -0,0 +1,35 @@+||| 'and' for propositions.++!!! pand(true, true)++pand : Prop * Prop -> Prop+pand(p, q) = forall and_side : Unit + Unit. {?+ p when and_side is left _,+ q otherwise+ ?}++all : List(Prop) -> Prop+all ps = reduce(pand, true, ps)++||| 'or' for propositions.++!!! por(false, true)+!!! por(true, false)+!!! por(true, true)++por : Prop * Prop -> Prop+por(p, q) = exists or_side : Unit + Unit. {?+ p when or_side is left _,+ q otherwise+ ?}++any : List(Prop) -> Prop+any ps = reduce(por, false, ps)++||| Assert that a proposition holds on some number in a range.++existsBetween : N * N * (N -> Prop) -> Prop+existsBetween(a, b, p) = exists n:N. all [n >= a, n < b, p n]++hasFactors : N -> Prop+hasFactors n = existsBetween(2, n, \r. r divides n)
+ test/prop-higher-order/input view
@@ -0,0 +1,5 @@+:load test/prop-higher-order/higher-order.disco+:test forall x:F. por(x =!= 0, exists n:N. n * x >= 1)+:test forall f:Bool->Bool. any [forall (x: Bool). f x =!= not x, forall (x: Bool). f x =!= x, forall (x: Bool). f x =!= false, forall (x: Bool). f x =!= true]+:test all [true, true, true, false, true]+:test exists k:N. hasFactors(2^k + 1)
+ test/prop-holds/expected view
@@ -0,0 +1,7 @@+true+true+true+false+false+true+true
+ test/prop-holds/input view
@@ -0,0 +1,7 @@+holds (forall (x : Bool). x or not x)+holds (exists (x : Bool). x)+holds (exists (f : Bool + Bool -> Bool + Bool). forall (a : Bool). (f (left a) == right a) and (f (right a) == left a))+holds (exists (f : Bool -> Bool). exists (x : Bool). f(x) /= f (f (f x)))+holds (forall (a:N, b:N, c:N). a^2 + b^2 == c^2)+holds (exists (xs : List(N)). xs == [2, 2, 1])+holds (exists (xs : Set(Z)). size xs == 3 and reduce(\(a, b). a + b, 0, xs) == 0)
+ test/prop-impredicative/expected view
@@ -0,0 +1,18 @@+Loading prop-impredicative.disco...+Loaded.+Error: the type+ Prop+is not searchable (i.e. it cannot be used in a forall).+https://disco-lang.readthedocs.io/en/latest/reference/no-search.html+Error: the type+ Prop+is not searchable (i.e. it cannot be used in a forall).+https://disco-lang.readthedocs.io/en/latest/reference/no-search.html+Error: the type+ ℕ → Prop+is not searchable (i.e. it cannot be used in a forall).+https://disco-lang.readthedocs.io/en/latest/reference/no-search.html+Error: the type+ List(Prop) → ℕ+is not searchable (i.e. it cannot be used in a forall).+https://disco-lang.readthedocs.io/en/latest/reference/no-search.html
+ test/prop-impredicative/input view
@@ -0,0 +1,9 @@+:load test/prop-impredicative/prop-impredicative.disco+:type exists (x:Prop). x and (3 > 2)+:type forall (x:Prop). (true ==> x)+:type forall (f: N -> Prop). f 3+:type forall (f: List(Prop) -> N). 3 == 3+-- Should reinstate these later once we can analyze whether a type synonym has a certain sort+-- First one should be OK, second should not.+-- :type forall (f: T(N) -> N). f (left ()) == 2+-- :type forall (f: T(Prop) -> N). f (left ()) == 2
+ test/prop-impredicative/prop-impredicative.disco view
@@ -0,0 +1,1 @@+type T(a) = Unit + a * T(a) * T(a)
+ test/prop-tests/expected view
@@ -0,0 +1,6 @@+Loading prop-tests.disco...+Running tests...+ foo: OK+ reverse: OK+ x: OK+Loaded.
+ test/prop-tests/input view
@@ -0,0 +1,1 @@+:load test/prop-tests/prop-tests.disco
+ test/prop-tests/prop-tests.disco view
@@ -0,0 +1,65 @@+||| This is a foozle function.++!!! foo 3 == 5+!!! foo 9 == 28++||| We can have more documentation before more tests.++!!! foo 12 == 25+!!! ∀ n:Nat. foo (n+10) == 2n+21++foo : N -> N+foo 3 = 5+foo 9 = 28+foo n = 2n + 1++||| Reverse a list.+!!! reverse [3,6,7] == [7,6,3]+!!! ∀ a:N, b:N. reverse [a,b] == [b,a]+!!! ∀ xs:List(N). reverse (reverse xs) == xs++reverse : List(a) -> List(a)+reverse = revHelper []++revHelper : List(a) -> List(a) -> List(a)+revHelper rev [] = rev+revHelper rev (x :: xs) = revHelper (x :: rev) xs++toBin : N -> List(Bool)+toBin 0 = []+toBin 1 = []+toBin n = {? false if 2 divides n, true otherwise ?} :: toBin (n // 2)++fromBin : List(Bool) -> N+fromBin [] = 1+fromBin (false :: bs) = 2 (fromBin bs)+fromBin (true :: bs) = 2 (fromBin bs) + 1++plusIso : N + N -> N+plusIso (left n) = 2n+plusIso (right n) = 2n + 1++plusIsoR : N -> N + N+plusIsoR n =+ {? left (n // 2) if 2 divides n+ , right (n // 2) otherwise+ ?}++!!! ∀ v:Void. 2 == 3 -- this is true!+!!! ∀ u:Unit. u == u -- there is only one Unit value+!!! ∀ l:List(Void). l == l+!!! ∀ l:List(Unit). l == reverse l+!!! ∀ bs : List(Bool). toBin (fromBin bs) == bs+!!! ∀ q : F. q >= 0+!!! ∀ p : N * N. {? x + y when p is (x,y) ?} >= 0+!!! ∀ bs : Bool * Bool. {? b1 when bs is (b1,b2) if b2, false otherwise ?}+ == {? b1 and b2 when bs is (b1,b2) ?}+!!! ∀ x : N + N. plusIsoR (plusIso x) == x++!!! ∀ x : Void + N. {? true when x is right _, false otherwise ?}++x : N+x = 0++ -- x is just here to give us something to attach arbitrary test+ -- properties to.
+ test/prop-type/expected view
@@ -0,0 +1,12 @@+∃x : ℤ. x > 3 : Prop+∃x : ℚ. x > 3 : Prop+∃x : ℕ, y : ℕ. x > y : Prop+∃x : ℕ. ∃y : ℕ. x > y : Prop+∀x : ℕ, y : ℕ, z : ℕ. x + y + z == x + (y + z) : Prop+∃x : ℕ. ∀y : ℕ. x <= y : Prop+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html
+ test/prop-type/input view
@@ -0,0 +1,9 @@+:type ∃ (x:Z). x > 3+:type ∃ (x:Q). x > 3+:type ∃ (x:N), (y : N). x > y+:type ∃ (x:N). ∃ (y:N). x > y+:type ∀ (x:N), (y:N), (z:N). (x + y) + z == x + (y + z)+:type ∃ (x:N). ∀ (y:N). x <= y+:type not (∀ x:Bool. true or x)+:type (∀ x:Void. false) and true+:type (∀ x:Void. false) or true
+ test/repl-ann/expected view
@@ -0,0 +1,3 @@+Loading num.disco...+factor : ℕ → Bag(ℕ)+(λx : ℤ, y : a. (~-~ : ℤ × ℤ → ℤ)((x : ℤ, 7 : ℤ) : ℤ × ℤ) : ℤ) : ℤ → a3 → ℤ
+ test/repl-ann/input view
@@ -0,0 +1,3 @@+import num+:ann factor+:ann \x, y. x - 7
+ test/repl-compile/expected view
@@ -0,0 +1,2 @@+Loading num.disco...+num.isPrime
+ test/repl-compile/input view
@@ -0,0 +1,2 @@+import num+:compile isPrime
+ test/repl-defn/expected view
@@ -0,0 +1,4 @@+5+x : ℕ+true+6
+ test/repl-defn/input view
@@ -0,0 +1,7 @@+x : N+x = 5+x+:type x+x == 5+x = 6+x
+ test/repl-defns/expected view
@@ -0,0 +1,2 @@+2+3
+ test/repl-defns/input view
@@ -0,0 +1,6 @@+x : N+x = 2+y : N+y = x + 1+x+y
+ test/repl-desugar/expected view
@@ -0,0 +1,2 @@+Loading num.disco...+isPrime
+ test/repl-desugar/input view
@@ -0,0 +1,2 @@+import num+:desugar isPrime
+ test/repl-doc/doc.disco view
@@ -0,0 +1,8 @@+||| P is a type of stuff.+type P = N * N++||| f is a function.+||| Some more documentation.+!!! f(1) == 2+f : N -> N+f x = x + 1
+ test/repl-doc/expected view
@@ -0,0 +1,36 @@+Loading doc.disco...+Running tests...+ f: OK+Loaded.+type P = ℕ × ℕ++P is a type of stuff.++f : ℕ → ℕ++f is a function.+Some more documentation.++No documentation found for x.+~+~ : ℕ × ℕ → ℕ+precedence level 7, left associative++The sum of two numbers, types, or graphs.++https://disco-lang.readthedocs.io/en/latest/reference/addition.html++~! : ℕ → ℕ+precedence level 14++n! computes the factorial of n, that is, 1 * 2 * ... * n.++https://disco-lang.readthedocs.io/en/latest/reference/factorial.html++not~ : Bool → Bool+Alternative syntax: ¬~+precedence level 15++Logical negation: ¬true = false and ¬false = true. Also written 'not'.++https://disco-lang.readthedocs.io/en/latest/reference/not.html+
+ test/repl-doc/input view
@@ -0,0 +1,7 @@+:load test/repl-doc/doc.disco+:doc P+:doc f+:doc x+:doc ++:doc !+:doc not
+ test/repl-eval-tydef-import/a.disco view
@@ -0,0 +1,4 @@+import b++x : L+x = right(3, left(unit))
+ test/repl-eval-tydef-import/b.disco view
@@ -0,0 +1,1 @@+type L = Unit + N * L
+ test/repl-eval-tydef-import/expected view
@@ -0,0 +1,4 @@+Loading a.disco...+Loading b.disco...+Loaded.+right(3, left(■))
+ test/repl-eval-tydef-import/input view
@@ -0,0 +1,2 @@+:load test/repl-eval-tydef-import/a.disco+x
+ test/repl-help/expected view
@@ -0,0 +1,12 @@+Commands available from the prompt:++:defn <var> Show a variable's definition+:doc <term> Show documentation+<code> Evaluate a block of code+:help Show help+:load <filename> Load a file+:names Show all names in current scope+:reload Reloads the most recently loaded file+:test <property> Test a property using random examples++2
+ test/repl-help/input view
@@ -0,0 +1,2 @@+:help+1+1
+ test/repl-import/expected view
@@ -0,0 +1,3 @@+Loading num.disco...+false+[4, 5]
+ test/repl-import/input view
@@ -0,0 +1,3 @@+import num+isPrime 20+filter(\x. x > 3, [1 .. 5])
+ test/repl-names/expected view
@@ -0,0 +1,8 @@+Loading logic.disco...+Loading other.disco...+Loaded.+type Maybe(a) = Unit + a+exor : Bool → Bool → Bool+implication : Bool → Bool → Bool+lnot1 : Bool → Bool+lnot2 : Bool → Bool
+ test/repl-names/input view
@@ -0,0 +1,2 @@+:load test/repl-names/logic.disco+:names
+ test/repl-names/logic.disco view
@@ -0,0 +1,27 @@+-- copied from example/logic.disco as it might change, but this file shouldn't++import other++-- Basic logical operators++lnot1 : Bool -> Bool+lnot1 true = false+lnot1 false = true++lnot2 : Bool -> Bool+lnot2 x =+ {? false if x,+ true otherwise+ ?}++implication : Bool -> Bool -> Bool+implication x y =+ {? false if x and not y,+ true otherwise+ ?}++exor : Bool -> Bool -> Bool+exor x y = (x && not y) || (not x && y)++-- A custom type used to validate :names output+type Maybe(a) = Unit + a
+ test/repl-names/other.disco view
@@ -0,0 +1,9 @@+-- Another module with some definitions.+-- For now these should *not* be shown by the :names+-- command, since showing stuff from imports would also mean+-- showing thins from implicitly loaded standard library modules...++type Foo = Int++x : Int+x = 0
+ test/repl-proptest/expected view
@@ -0,0 +1,12 @@+ - Test passed: not false+ - Test passed: {1, 2} =!= {2, 1}+ - Test passed: ∃a, b. (a and b) =!= (a or b)+ Found example:+ a = false+ b = false+ - Test result mismatch for: ∀a, b. (a and b) =!= (a or b)+ - Left side: true+ - Right side: false+ Counterexample:+ a = false+ b = true
+ test/repl-proptest/input view
@@ -0,0 +1,4 @@+:test not false+:test {1, 2} =!= {2, 1}+:test exists a : Bool, b : Bool. (a and b) =!= (a or b)+:test forall a : Bool, b : Bool. (a and b) =!= (a or b)
+ test/solver-issue112/diag-iso-bad.disco view
@@ -0,0 +1,9 @@+diagIso' : ℕ → ℕ×ℕ+diagIso' n =+ let d = (sqrt(1 + 8n) - 1)//2 : N+ in let t = d*(d+1)//2+ in (n .- t, d .- (n .- t))++-- The above shouldn't typecheck, because of the use of normal+-- subtraction in the definition of d. However, it was erroneously+-- being accepted before fixing issue #112.
+ test/solver-issue112/expected view
@@ -0,0 +1,5 @@+Loading diag-iso-bad.disco...+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html
+ test/solver-issue112/input view
@@ -0,0 +1,2 @@+:load test/solver-issue112/diag-iso-bad.disco+:type (\x.x^(-2)) : Z -> Z
+ test/syntax-chain/expected view
@@ -0,0 +1,16 @@+Loading inRange.disco...+Loaded.+false+false+true+true+true+false+true+false+false+false+false+false+false+false
+ test/syntax-chain/inRange.disco view
@@ -0,0 +1,2 @@+inRange : N -> N -> (N -> Bool)+inRange a b = λn. a <= n <= b
+ test/syntax-chain/input view
@@ -0,0 +1,15 @@+:load test/syntax-chain/inRange.disco+inRange 2 5 0+inRange 2 5 8+inRange 2 5 2+inRange 2 5 3+inRange 2 5 5+inRange 4 4 3+inRange 4 4 4+inRange 4 4 5+inRange 3 1 0+inRange 3 1 1+inRange 3 1 2+inRange 3 1 3+inRange 3 1 4+2 < 1 < 1/0 -- Issue #67
+ test/syntax-clause/clauses.disco view
@@ -0,0 +1,17 @@+-- An example of a function defined by pattern-matching clauses, with+-- multiple clauses and multiple arguments.++zipWithN : (N -> N -> N) -> List(N) -> List(N) -> List(N)+zipWithN _ [] _ = []+zipWithN _ _ [] = []+zipWithN f (m :: ms) (n :: ns) = f m n :: zipWithN f ms ns++-- Another example of a function defined by pattern-matching+-- clauses. This one has the feature that it has the same name (ys) in+-- two of the clauses, which was causing problems for the way we+-- handle name resolution (the implementation of 'lunbinds' in+-- Desugar.desugarDefn was wrong).++appendC : List(ℕ) × List(ℕ) → List(ℕ)+appendC ([] , ys) = ys+appendC (x::xs', ys) = x :: appendC(xs',ys)
+ test/syntax-clause/expected view
@@ -0,0 +1,4 @@+Loading clauses.disco...+Loaded.+[5, 11, 19]+[1, 2, 3, 4, 5, 6]
+ test/syntax-clause/input view
@@ -0,0 +1,3 @@+:load test/syntax-clause/clauses.disco+zipWithN (\x, y. x*y + 1) [1,2,3] [4,5,6]+append ([1,2,3], [4,5,6])
+ test/syntax-comment/expected view
@@ -0,0 +1,5 @@+Loading fib.disco...+Running tests...+ fib: OK+Loaded.+610
+ test/syntax-comment/fib.disco view
@@ -0,0 +1,18 @@+||| A naive implementation of the fibonacci function.+!!! fib 0 == 0+!!! fib 1 == 1+!!! fib 2 == 1+!!! fib 5 == 5+!!! fib 12 == 144+fib : Nat -> Nat -- a top-level recursive function+fib n =+ {? n when+ n -- note how a single branch can be+ is 0 -- broken across multiple lines+ , n when n is 1 -- comment+ , fib (n .- 1) + fib (n .- 2) otherwise+ -- note we can't write+ -- fib (n-1) + fib (n-2) otherwise+ -- since that doesn't pass the type checker: it doesn't believe+ -- that (n-1) and (n-2) are natural numbers.+ ?}
+ test/syntax-comment/input view
@@ -0,0 +1,2 @@+:load test/syntax-comment/fib.disco+fib 15
+ test/syntax-containers/expected view
@@ -0,0 +1,3 @@+[1, 2, 3, 3]+⟅1, 2, 3 # 2⟆+{1, 2, 3}
+ test/syntax-containers/input view
@@ -0,0 +1,3 @@+[ 1, 2, 3, 3 ]+⟅ 1, 2, 3, 3 ⟆+{ 1, 2, 3, 3 }
+ test/syntax-decimals/expected view
@@ -0,0 +1,24 @@+2+2+2+2+3.[45]+3.46[45]+3.111[3]+3.111[3]+3.[3]+3/2+1.5+22.7+3.8[3]+true+0.[142857]+0.[052631578947368421]+0.[032258064516129]+0.[175257731958762886597938144329896907216494845360824742268041237113402061855670103092783505154639]+0.[001]+0.0010090817356205852674066599394550958627648839556004036326942482341069626639757820383451059535822401...+0.0000000000000000000000000000000000000000001145742637671319864267636924948858003603123762210263720996...+0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001+0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001+0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000...
+ test/syntax-decimals/input view
@@ -0,0 +1,24 @@+2.0+2.[0]+2.[00000]+2.000[00]+3.45[45]+3.46[45]+3.111[333]+3.111[3]+3.[3]+1 + 1/2+1 + 0.5+3.5 + 19.2+3.5 + 1/3+1 == 0.[9]+1/7.0+1/19.0+1/31.0+17/97.0+1/999.0+1/991.0+(1/3.0)^90+(1.0/10^99)+(1.0/10^100)+(1.0/10^101)
+ test/syntax-doc/expected view
@@ -0,0 +1,10 @@+Loading syntax-doc.disco...+Running tests...+ x: OK+Loaded.+x : ℕ++This is some documentation.++More documentation after a blank line.+
+ test/syntax-doc/input view
@@ -0,0 +1,2 @@+:load test/syntax-doc/syntax-doc.disco+:doc x
+ test/syntax-doc/syntax-doc.disco view
@@ -0,0 +1,10 @@+||| This is some documentation.+|||+||| More documentation after a blank line.++!!! x == 3 -- A property++||| Yet more documentation.++x : Nat+x = 3
+ test/syntax-exts/expected view
@@ -0,0 +1,2 @@+Loading syntax-exts.disco...+Loaded.
+ test/syntax-exts/input view
@@ -0,0 +1,1 @@+:load test/syntax-exts/syntax-exts.disco
+ test/syntax-exts/syntax-exts.disco view
@@ -0,0 +1,5 @@+using rAnDOMnesS+using Primitives++x : N+x = 5
+ test/syntax-juxt-app/expected view
@@ -0,0 +1,3 @@+Loading juxt-app.disco...+Loaded.+17
+ test/syntax-juxt-app/input view
@@ -0,0 +1,2 @@+:load test/syntax-juxt-app/juxt-app.disco+f (\x.x^2) (3,4) 2
+ test/syntax-juxt-app/juxt-app.disco view
@@ -0,0 +1,2 @@+f : (N -> N) -> N * N -> N -> Z+f g (x,y) z = x + g y - z -- here g y is function application
+ test/syntax-juxt-mul/expected view
@@ -0,0 +1,18 @@+Loading juxt-mul.disco...+Loaded.+200+200+Error: the expression+ x+must have both a function type and also the incompatible type+ ℕ.+https://disco-lang.readthedocs.io/en/latest/reference/notcon.html+24+14+600+75+75+225+36+26+192
+ test/syntax-juxt-mul/input view
@@ -0,0 +1,13 @@+:load test/syntax-juxt-mul/juxt-mul.disco+(x^2) (y^3)+x^2 y^3+2^x y^3+(x + 1)(y + 2)+(x + 1) y + 2+5x!+3x^2+3(x^2)+(3x)^2+f(x)^2+f(x^2)+(x+1)(y+2)(x+3)
+ test/syntax-juxt-mul/juxt-mul.disco view
@@ -0,0 +1,8 @@+x : Nat+x = 5++y : Nat+y = 2++f : Nat -> Nat+f x = x + 1
+ test/syntax-lambda-pat/expected view
@@ -0,0 +1,7 @@+3+3+3+10+((1, 3), 2, 4)+42+λf, (a, b). f(a)(b) : (a2 → a1 → a) → a2 × a1 → a
+ test/syntax-lambda-pat/input view
@@ -0,0 +1,7 @@+let f1 = \(x, y). x + y in f1 (1, 2)+let f2 = \(x:N, y:N). x + y in f2 (1, 2)+let f3 = \((x, y) : N*N). x + y in f3 (1, 2)+let h = \left (x, y). x in h (left (10, -10))+let fwop = \(x1, y1), (x2, y2). ((x1, x2), (y1, y2)) in fwop (1, 2) (3, 4)+let uncurry = \f, (a, b). f a b in uncurry (\a, b. a + b) (40, 2)+:type \f, (a, b). f a b
+ test/syntax-lambda/expected view
@@ -0,0 +1,14 @@+4+let f = λx : ℕ. x + 1 in f : ℕ → ℕ+4+let f = (λx. x + 1) : ℕ → ℕ in f : ℕ → ℕ+let f = λx. x + 1 : ℕ in f : ℕ → ℕ+let f = λx. x + 1 : ℕ in f : ℕ → ℕ+(9, 8)+5+λx : ℤ, y : ℕ. x * y : ℤ → ℕ → ℤ+[false, true, true]+let f = λg : ℤ → ℕ → Bool. [g(1)(1), g(1)(2), g(-1)(0)] in f(λx, y : ℤ. x + 1 == y) : List(Bool)+3+TAbs_ Lam () (<[PWild_ ()]> TNat_ () 3)+3
+ test/syntax-lambda/input view
@@ -0,0 +1,14 @@+let f = \(x:N). x + 1 in f 3+:type let f = \(x:N). x + 1 in f+let f = (λx. x + 1) : N -> N in f 3+:type let f = (λx. x + 1) : N -> N in f+:type let f = λx. (x + 1 : N) in f+:type let f = λx. x + 1 : N in f+let g = λx:N, b:Bool.{? x*x if b, x+2 otherwise ?} in (g 3 true, g 6 false)+let q = \ (f : (N -> N) -> N) . f (\(x:N) . x*x) in q (\g. g 1 + g 2)+:type (\ x:Z, y:N . x * y)+let f = \(g : Z -> N -> Bool).[g 1 1, g 1 2, g (-1) 0] in f (\x, y:Z. x + 1 == y)+:type let f = \(g : Z -> N -> Bool).[g 1 1, g 1 2, g (-1) 0] in f (\x, y:Z. x + 1 == y)+let f : N -> N -> N = \x.\y.x+y in f 1 2+:parse \_.3+(\_.3) "hello"
+ test/syntax-let/expected view
@@ -0,0 +1,6 @@+8+12+4399+let x = 2, y = x + 3, z = 2 * y + x + 1 in z ^ 3 * x + y : ℕ+16+let x : ℕ = 5 + 3 in x + x : ℕ
+ test/syntax-let/input view
@@ -0,0 +1,6 @@+let x = 3, y = 5 in x + y+let x = 3 in let y = x + 1 in x*y+let x = 2, y = x + 3, z = 2y + x + 1 in z^3*x + y+:type let x = 2, y = x + 3, z = 2y + x + 1 in z^3*x + y+let x : N = 5 + 3 in x + x+:type let x : N = 5 + 3 in x + x
+ test/syntax-many-args/expected view
@@ -0,0 +1,4 @@+Loading many-args.disco...+Loaded.+10260+0
+ test/syntax-many-args/input view
@@ -0,0 +1,3 @@+:load test/syntax-many-args/many-args.disco+f (0,1) (2,3) (4,5) (6,7) (8,9) (10,11) (12,13) (14,15) (16,17) (18,19) (20,21) (22,23) (24,25) (26,27) (28,29) (30,31) (32,33) (34,35) (36,37) (38,39) [1,2,3,4,5] [6,7,8] [9,10]+f (0,1) (2,3) (4,5) (6,7) (8,9) (10,11) (12,13) (14,15) (16,17) (18,19) (20,21) (22,23) (24,25) (26,27) (28,29) (30,31) (32,33) (34,35) (36,37) (38,39) [1,2,3,4,5] [6,7,8] [9,11]
+ test/syntax-many-args/many-args.disco view
@@ -0,0 +1,4 @@+-- A stress test: a function with a lot of pattern arguments+f : N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> N * N -> List(N) -> List(N) -> List(N) -> N+f (a0,b1) (a2,b3) (a4,b5) (a6,b7) (a8,b9) (a10,b11) (a12,b13) (a14,b15) (a16,b17) (a18,b19) (a20,b21) (a22,b23) (a24,b25) (a26,b27) (a28,b29) (a30,b31) (a32,b33) (a34,b35) (a36,b37) (a38,b39) [1,2,3,4,5] [6,7,8] [9,10] = a0 * b1 + a2 * b3 + a4 * b5 + a6 * b7 + a8 * b9 + a10 * b11 + a12 * b13 + a14 * b15 + a16 * b17 + a18 * b19 + a20 * b21 + a22 * b23 + a24 * b25 + a26 * b27 + a28 * b29 + a30 * b31 + a32 * b33 + a34 * b35 + a36 * b37 + a38 * b39+f _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ = 0
+ test/syntax-many-clauses/expected view
@@ -0,0 +1,9 @@+Loading many-clauses.disco...+Loaded.+0+1+144+15+5000+100+267
+ test/syntax-many-clauses/input view
@@ -0,0 +1,8 @@+:load test/syntax-many-clauses/many-clauses.disco+f 0+f 1+f 14+f 15+f 87+f 100+f 153
+ test/syntax-many-clauses/many-clauses.disco view
@@ -0,0 +1,104 @@+-- Another stress test: a function with many clauses+f : N -> N+f 0 = 0+f 1 = 1+f 2 = 2+f 3 = 3+f 4 = 4+f 5 = 5+f 6 = 6+f 7 = 7+f 8 = 8+f 9 = 9+f 10 = 10+f 11 = 11+f 12 = 12+f 13 = 13+f 14 = 144+f 15 = 15+f 16 = 16+f 17 = 17+f 18 = 18+f 19 = 19+f 20 = 20+f 21 = 21+f 22 = 22+f 23 = 23+f 24 = 24+f 25 = 25+f 26 = 26+f 27 = 27+f 28 = 28+f 29 = 29+f 30 = 30+f 31 = 31+f 32 = 32+f 33 = 33+f 34 = 34+f 35 = 35+f 36 = 36+f 37 = 37+f 38 = 38+f 39 = 39+f 40 = 40+f 41 = 41+f 42 = 42+f 43 = 43+f 44 = 44+f 45 = 45+f 46 = 46+f 47 = 47+f 48 = 48+f 49 = 49+f 50 = 50+f 51 = 51+f 52 = 52+f 53 = 53+f 54 = 54+f 55 = 55+f 56 = 56+f 57 = 57+f 58 = 58+f 59 = 59+f 60 = 60+f 61 = 61+f 62 = 62+f 63 = 63+f 64 = 64+f 65 = 65+f 66 = 66+f 67 = 67+f 68 = 68+f 69 = 69+f 70 = 70+f 71 = 71+f 72 = 72+f 73 = 73+f 74 = 74+f 75 = 75+f 76 = 76+f 77 = 77+f 78 = 78+f 79 = 79+f 80 = 80+f 81 = 81+f 82 = 82+f 83 = 83+f 84 = 84+f 85 = 85+f 86 = 86+f 87 = 5000+f 88 = 88+f 89 = 89+f 90 = 90+f 91 = 91+f 92 = 92+f 93 = 93+f 94 = 94+f 95 = 95+f 96 = 96+f 97 = 97+f 98 = 98+f 99 = 99+f 100 = 100+f _ = 267
+ test/syntax-patclause/expected view
@@ -0,0 +1,4 @@+Loading fact.disco...+Loaded.+3628800+3628800
+ test/syntax-patclause/fact.disco view
@@ -0,0 +1,13 @@+-- Here are two equivalent definitions of factorial using the two+-- different styles: one with a case expression, and one with two+-- pattern-matching clauses.++fact : N -> N+fact n =+ {? 1 when n is 0,+ n * fact m when n is m+1+ ?}++fact2 : N -> N+fact2 0 = 1+fact2 (m+1) = (m + 1) * fact2 m
+ test/syntax-patclause/input view
@@ -0,0 +1,3 @@+:load test/syntax-patclause/fact.disco+fact 10+fact2 10
+ test/syntax-prims/expected view
@@ -0,0 +1,4 @@+Loading syntax-prims.disco...+Loaded.+x : ℕ → ℕ+User crash: bad dog!
+ test/syntax-prims/input view
@@ -0,0 +1,3 @@+:load test/syntax-prims/syntax-prims.disco+:type x+x 4
+ test/syntax-prims/syntax-prims.disco view
@@ -0,0 +1,4 @@+using primitives++x : N -> N+x _ = $crash "bad dog!"
+ test/syntax-tuples/expected view
@@ -0,0 +1,10 @@+(1, 2)+1+(1, 2, 3, 4, 5)+(1, 2, 3, 4, 5)+((1, 2), 3, 4, 5)+(1, 2, -3, 4.0, -5.0) : ℕ × ℕ × ℤ × 𝔽 × ℚ+((1 / 2, 1), 2) : (𝔽 × ℕ) × ℕ+3+29+-50
+ test/syntax-tuples/input view
@@ -0,0 +1,10 @@+(1,2)+(1)+(1,2,3,4,5)+(1,2,(3,4,5))+((1,2),3,4,5)+:type (1,2,-3,4.0,-5.0)+:type ((1/2,1),2)+{? x when (3,4,5) is (x,y,z) ?}+{? x+y when (7,22,45) is (x,y,z) ?}+{? a*f when (10,15,-23,7,3,-5) is (a,b,c,d,e,f) ?}
+ test/syntax-types/expected view
@@ -0,0 +1,11 @@+[1, 1, 1, 1]+[-1, -1, -1, -1]+[1/2, 1/2]+[-1/2, -1/2, -1/2]+[true, true]+[■, ■]+[]+{}+{}+⟅⟆+⟅⟆
+ test/syntax-types/input view
@@ -0,0 +1,12 @@+[1 : N, 1 : Nat, 1 : Natural, 1 : ℕ]+[-1 : Z, -1 : Int, -1 : Integer, -1 : ℤ]+[1/2 : F, 1/2 : 𝔽]+[-1/2 : Q, -1/2 : Rational, -1/2 : ℚ]+[true : Bool, true : Boolean]+[unit : Unit, ■ : Unit]+[] : List(Void)+-- [3 : Fin 5, 3 : Z 5, 3 : Z5, 3 : ℤ 5, 3 : ℤ5]+{} : Set(Natural)+{} : Set(Set(Natural))+⟅ ⟆ : Bag(Rational)+⟅⟆ : Bag(Set(Integer))
+ test/types-192/expected view
@@ -0,0 +1,1 @@+λx, y, z. x + y / z : 𝔽 → 𝔽 → 𝔽 → 𝔽
+ test/types-192/input view
@@ -0,0 +1,1 @@+:type \x,y,z. x + y / z
+ test/types-bind/expected view
@@ -0,0 +1,1 @@+8
+ test/types-bind/input view
@@ -0,0 +1,1 @@+((let y = 3 in (\x. x + y)) : N -> N) 5
+ test/types-char-string/expected view
@@ -0,0 +1,34 @@+'a'+'a' : Char+'c'+'\n'+'\''+'"'+'#'+'\\'+'"'+true+1+2+3+(λx. {? 1 when x is 'a' + ?})("Disco")+1:3:+ |+1 | ' a'+ | ^+unexpected 'a'+expecting '''++' '+"Disco"+"Disco" : List(Char)+"Di sco"+"'"+"\n"+"'"+"a\na\a\a\""+"\\"+true+2+1
+ test/types-char-string/input view
@@ -0,0 +1,30 @@+-- Chars+'a'+:type 'a'+'c'+'\n'+'''+'"'+'#'+'\\'+'\"'+'a' < 'b'+(\x. {? 1 when x is 'a', 2 when x is 'z', 3 otherwise ?}) 'a'+(\x. {? 1 when x is 'a', 2 when x is 'z', 3 otherwise ?}) 'z'+(\x. {? 1 when x is 'a', 2 when x is 'z', 3 otherwise ?}) 'k'+:pretty (\x. {? 1 when x is 'a' ?}) "Disco"+' a'+' '++-- Strings+"Disco"+:type "Disco"+"Di sco"+"\'"+"\n"+"'"+"a\na\a\a\""+"\\"+"bed" < "boat"+(\x. {? 1 when x is "Disco", 2 otherwise ?}) "blahblah"+(\x. {? 1 when x is "Disco", 2 otherwise ?}) "Disco"
+ test/types-compare/expected view
@@ -0,0 +1,1 @@+true
+ test/types-compare/input view
@@ -0,0 +1,1 @@+([1,2,3] : List(Z)) == ([1,2,3] : List(N))
+ test/types-container/expected view
@@ -0,0 +1,12 @@+[]+{}+⟅⟆+[] : List(a)+{} : Set(a)+⟅⟆ : Bag(a)+[3] : List(ℕ)+{3} : Set(ℕ)+⟅3⟆ : Bag(ℕ)+list : List(a) → List(a)+bag : List(ℕ) → Bag(ℕ)+set : List(ℕ) → Set(ℕ)
+ test/types-container/input view
@@ -0,0 +1,13 @@+[]+{}+⟅⟆+:type []+:type {}+:type ⟅⟆+:type [3]+:type {3}+:type ⟅3⟆+:type list+:type bag+:type set+-- :type {1,2,3} union {4,5} : Set Z3
+ test/types-kinds/expected view
@@ -0,0 +1,10 @@+Error: too many arguments for the type 'List'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html+Error: too many arguments for the type 'List'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html+Error: the shape of two types does not match.+https://disco-lang.readthedocs.io/en/latest/reference/shape-mismatch.html+Error: too many arguments for the type 'List'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html+Error: too many arguments for the type 'List'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html
+ test/types-kinds/input view
@@ -0,0 +1,5 @@+3 : List(N,N)+3 : List(N,Z)+3 : List(List(N))+3 : (List(List,N))+:type \x:List(N,N).x == x
+ test/types-naked-ops/expected view
@@ -0,0 +1,5 @@+~max~ : ℕ × ℕ → ℕ+~! : ℕ → ℕ+~mod~ : ℤ × ℤ → ℤ+~+~ : ℕ × ℕ → ℕ+not~ : Bool → Bool
+ test/types-naked-ops/input view
@@ -0,0 +1,5 @@+:type max+:type !+:type %+:type ++:type ¬
+ test/types-numpats/expected view
@@ -0,0 +1,2 @@+Loading types-numpats.disco...+Loaded.
+ test/types-numpats/input view
@@ -0,0 +1,1 @@+:load test/types-numpats/types-numpats.disco
+ test/types-numpats/types-numpats.disco view
@@ -0,0 +1,9 @@+type X = N -> N++f : X+f(0) = 3+f(n) = n+1++g : N -> X+g(0)(2) = 1+g(_)(_) = 2
+ test/types-ops/expected view
@@ -0,0 +1,38 @@+Loading list.disco...+right(0)+right(1)+right(2)+right(16)+right(4)+right(1)+right(1)+left(■)+right(0)+right(0)+right(1)+right(0)+right(1)+[]+[<Bool → Bool>, <Bool → Bool>, <Bool → Bool>, <Bool → Bool>]+[left(false, false), left(false, true), left(true, false), left(true, true), right(false), right(true)]+[left(■), right(false), right(true)]+[]+[[]]+[[]]+[false, true]+[[], [■], [■, ■], [■, ■, ■], [■, ■, ■, ■]]+[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]+[(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (2, 0)]+[]+[]+[<Void → ℕ>]+[]+[<Void → Void>]+right(1)+left(■)+right(1)+right(2)+right(4)+right(1114112)+right(1)+right(0)
+ test/types-ops/input view
@@ -0,0 +1,50 @@+import list+count Void+count Unit+count Bool+count (Bool + Bool -> Bool)+count (Bool * Bool)+count (List(Void))+count (List (Void * Bool + Void))+count (List(Unit))+count (Void * Nat)+count (Nat * Void)+count (Void -> Nat)+count (Nat -> Void)+count (Void -> Void)+enumerate Void+enumerate (Bool -> Bool)+enumerate (Bool * Bool + Bool)+enumerate (Unit + Bool)+enumerate (Void * Unit)+enumerate (List(Void))+enumerate (List (Void * Bool + Void))+enumerate(Bool)+take(5, enumerate(List(Unit)))+take(10, enumerate(N))+take(6, enumerate(N * N))+enumerate (Void * Nat)+enumerate (Nat * Void)+enumerate (Void -> Nat)+enumerate (Nat -> Void)+enumerate (Void -> Void)+-- count Z5+-- count (Z5 * Z4)+-- count (Z3 + Z6)+-- count (Z3 -> Z2)+-- count (Z3 + Z4 -> Z2)+-- enumerate Z10+-- enumerate (Z2 * Z3)+-- enumerate (Z2 -> Z2)+-- enumerate (Z3 + Z4)+count (Bag(Void))+count (Bag(Unit))+count (Set(Void))+count (Set(Unit))+count (Set(Bool))+count Char+count ((N -> Void) -> N)+count (N -> (N -> Void))+-- each(\f. f unit, enumerate (Unit -> Unit)) == [unit]+-- each(\f. f unit, enumerate (Unit -> Bool)) == [false, true]+-- each(\f. f true, enumerate (Bool -> Bool)) == [false]
+ test/types-rational/expected view
@@ -0,0 +1,19 @@+3 / 5 : 𝔽+1 / 2 + 2 / 3 : 𝔽+1 / 2 * (2 / 3) : 𝔽+3 ^ (-2) : 𝔽+(1 / 2) ^ (-2) : 𝔽+(1 / 2) ^ 3 : 𝔽+[1, 2 / 3] : List(𝔽)+1 :: 2 / 3 :: [] : List(𝔽)+-3 / 5 : ℚ+3 / -5 : ℚ+-(3 / 5) : ℚ+1 / 2 - 2 / 3 : ℚ+1 / 2 * -3 : ℚ+-1 / 2 * 3 : ℚ+(-3) ^ (-2) : ℚ+(-2 / 3) ^ (-2) : ℚ+(-1 / 2) ^ 3 : ℚ+[1, -1, 2 / 3] : List(ℚ)+1 :: -1 :: 2 / 3 :: [] : List(ℚ)
+ test/types-rational/input view
@@ -0,0 +1,22 @@+-- Some things of type ℚ⁺+:type 3 / 5+:type 1/2 + 2/3+:type (1/2) * (2/3)+:type 3^(-2)+:type (1/2)^(-2)+:type (1/2)^3+:type [1,2/3]+:type (1 :: 2/3 :: [])++-- Some things of type ℚ+:type (-3) / 5+:type 3 / (-5)+:type -(3/5)+:type 1/2 - 2/3+:type 1/2 * (-3)+:type (-1/2) * 3+:type (-3)^(-2)+:type (-2/3)^(-2)+:type (-1/2)^3+:type [1,-1,2/3]+:type (1 :: -1 :: 2/3 :: [])
+ test/types-rec/expected view
@@ -0,0 +1,5 @@+Loading types-rec.disco...+Loading product.disco...+Loaded.+Error: typechecking failed.+https://disco-lang.readthedocs.io/en/latest/reference/typecheck-fail.html
+ test/types-rec/input view
@@ -0,0 +1,2 @@+:load test/types-rec/types-rec.disco+snd(e : T2)(e')
+ test/types-rec/types-rec.disco view
@@ -0,0 +1,24 @@+import product++type DoubleStream = N * N * DoubleStream++cons : N * DoubleStream -> DoubleStream+cons = \x.x++-- Example from section 24.3 of PFPL++type T1 = (T1 -> N) * (T1 -> Z)+type T2 = (T2 -> Z) * (T2 -> Z)++e : T1+e = (\x:T1. 4, \x:T1. sqrt(fst(x)(x)))++e' : T2+e' = (\x:T2. -4, \x:T2. 0)++-- If the system can (erroneously) derive T1 <: T2, then e : T2, and+-- snd(e : T2)(e') would be well-typed but would reduce to sqrt(-4).+-- The reason one might erroneously have T1 <: T2 is with a subtly+-- wrong subtyping rule, where we use the same type variable to stand+-- for recursive occurrences of both sides. Fortunately Disco does+-- not fall into this trap.
+ test/types-squash/expected view
@@ -0,0 +1,8 @@+floor(0) : ℕ+floor(1 / 2) : ℕ+floor(-1) : ℤ+floor(-1 / 2) : ℤ+abs(0) : ℕ+abs(1 / 2) : 𝔽+abs(-1) : ℕ+abs(-1 / 2) : 𝔽
+ test/types-squash/input view
@@ -0,0 +1,10 @@+:type floor 0+:type floor (1/2)+:type floor (-1)+:type floor (-1/2)+-- :type floor (2 : Z7)+:type abs 0+:type abs (1/2)+:type abs (-1)+:type abs (-1/2)+-- :type abs (2 : Z7)
+ test/types-standalone-ops/expected view
@@ -0,0 +1,4 @@+~and~ : Bool × Bool → Bool+false and true : Bool+λx. x and true : Bool → Bool+let f : (Bool × Bool → Bool) → Bool = λg. g(true, false) in f(~and~) : Bool
+ test/types-standalone-ops/input view
@@ -0,0 +1,4 @@+:type ~and~+:type ~and~ (False, True)+:type \x. ~and~ (x, True)+:type let f : (Bool * Bool -> Bool) -> Bool = \g. g (True, False) in f ~and~
+ test/types-toomanypats/expected view
@@ -0,0 +1,3 @@+Loading toomanypats.disco...+Error: number of arguments does not match.+https://disco-lang.readthedocs.io/en/latest/reference/num-args.html
+ test/types-toomanypats/input view
@@ -0,0 +1,1 @@+:load test/types-toomanypats/toomanypats.disco
+ test/types-toomanypats/toomanypats.disco view
@@ -0,0 +1,2 @@+f : N -> Bool+f x y = true
+ test/types-tydef-bad/expected view
@@ -0,0 +1,3 @@+Loading types-tydef-bad.disco...+Error: there is no built-in or user-defined type named 'Flerb'.+https://disco-lang.readthedocs.io/en/latest/reference/no-tydef.html
+ test/types-tydef-bad/input view
@@ -0,0 +1,1 @@+:load test/types-tydef-bad/types-tydef-bad.disco
+ test/types-tydef-bad/types-tydef-bad.disco view
@@ -0,0 +1,1 @@+type X = Int -> Flerb(Int)
+ test/types-tydef-kind/expected view
@@ -0,0 +1,3 @@+Loading types-tydef-kind.disco...+Error: not enough arguments for the type 'Foo'.+https://disco-lang.readthedocs.io/en/latest/reference/num-args-type.html
+ test/types-tydef-kind/input view
@@ -0,0 +1,2 @@+-- See issue #295. Make sure we kind-check type definitions.+:load test/types-tydef-kind/types-tydef-kind.disco
+ test/types-tydef-kind/types-tydef-kind.disco view
@@ -0,0 +1,5 @@+type Foo(a) = a + Foo++type Bar = Bar + Foo++type Zip = Unit + Graph
+ test/types-tydef-param/expected view
@@ -0,0 +1,9 @@+Loading types-tydef-param.disco...+Loaded.+right(5, right(2, left(■), left(■)), right(7, right(1, left(■), left(■)), left(■)))+15+right(3, right(true, right(5, right(false, right(7, left(■))))))+15+type Maybe(a) = Unit + a+type Tree(a) = Unit + a × Tree(a) × Tree(a)+type AltList(a, b) = Unit + a × AltList(b, a)
+ test/types-tydef-param/input view
@@ -0,0 +1,8 @@+:load test/types-tydef-param/types-tydef-param.disco+t+sumTree t+alt1+sumAltList alt1+:def Maybe+:def Tree+:def AltList
+ test/types-tydef-param/types-tydef-param.disco view
@@ -0,0 +1,37 @@+type Maybe(a) = Unit + a++maybe : b -> (a -> b) -> Maybe(a) -> b+maybe b _ (left(■)) = b+maybe _ f (right(a)) = f a++m1 : Maybe(N)+m1 = left(■)++m2 : Maybe(N)+m2 = right 3+++type Tree(a) = Unit + a * Tree(a) * Tree(a)++foldTree : r -> (a -> r -> r -> r) -> Tree(a) -> r+foldTree z f (left(■)) = z+foldTree z f (right (a,l,r)) = f a (foldTree z f l) (foldTree z f r)++sumTree : Tree(N) -> N+sumTree = foldTree 0 (\a, l, r. a+l+r)++t : Tree(N)+t = right (5, right (2, left(■), left(■)), right (7, right (1, left(■), left(■)), left(■)))+++type AltList(a,b) = Unit + a * AltList(b,a)++alt1 : AltList(N, Bool)+alt1 = right (3, right (true, right (5, right (false, right (7, left(■))))))++foldAltList : r -> (a -> r -> r) -> (b -> r -> r) -> AltList(a, b) -> r+foldAltList z _ _ (left(■)) = z+foldAltList z f g (right (a, l)) = f a (foldAltList z g f l)++sumAltList : AltList(N,Bool) -> N+sumAltList = foldAltList 0 (\x, y. x+y) (\b, r. r)
+ test/types-tydefs/expected view
@@ -0,0 +1,7 @@+Loading types-tydefs.disco...+Loaded.+incr : Wahoo → Wahoo+let f = λx : Wahoo. x + 1 in f(3) : ℕ+4+sumTripletList : List(Triplet) → ℕ+21
+ test/types-tydefs/input view
@@ -0,0 +1,6 @@+:l test/types-tydefs/types-tydefs.disco+:type incr+:type let f = \(x: Wahoo). x + 1 in f 3+let f = \(x: Wahoo). x + 1 in f 3+:type sumTripletList+let x = [(1,2,3), (4,5,6)] : List(Triplet) in sumTripletList x
+ test/types-tydefs/types-tydefs.disco view
@@ -0,0 +1,20 @@+type Wahoo = Cat++type Cat = Corn++type Corn = Rain++type Rain = Groot++type Groot = Bat++type Bat = Nat++incr : Wahoo -> Wahoo+incr w = w + 1++type Triplet = (Nat * Nat * Nat)++sumTripletList : List(Triplet) -> N+sumTripletList [] = 0+sumTripletList ((n1, n2, n3) :: rest) = (n1 + n2 + n3 + (sumTripletList rest))