├── .eslintrc.json ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── socket.ts └── stdio.ts ├── nix ├── idris1and2.nix ├── nixpkgs-unstable.nix ├── nixpkgs.nix ├── sources.json └── sources.nix ├── package-lock.json ├── package.json ├── src ├── client.ts ├── index.ts ├── parser │ ├── index.ts │ ├── lexer.ts │ ├── reply-parser.ts │ └── tokens.ts ├── reply.ts ├── request.ts └── s-exps.ts ├── test ├── client │ ├── unimplemented.ts │ ├── v1-client.test.ts │ ├── v1-expected.ts │ ├── v1-lidr.test.ts │ ├── v2-client.test.ts │ ├── v2-expected.ts │ ├── v2-lidr.test.ts │ └── v2-md.test.ts ├── parser │ ├── add-clause.test.ts │ ├── add-missing.test.ts │ ├── apropos.test.ts │ ├── browse-namespace.test.ts │ ├── calls-who.test.ts │ ├── case-split.test.ts │ ├── docs-for.test.ts │ ├── generate-def-next.test.ts │ ├── generate-def.test.ts │ ├── interpret.test.ts │ ├── load-file.test.ts │ ├── make-case.test.ts │ ├── make-lemma.test.ts │ ├── make-with.test.ts │ ├── metavariables.test.ts │ ├── print-definition.test.ts │ ├── proof-search-next.test.ts │ ├── proof-search.test.ts │ ├── repl-completions.test.ts │ ├── type-at.test.ts │ ├── type-of.test.ts │ ├── version.test.ts │ └── who-calls.test.ts ├── resources │ ├── test-v2.idr │ ├── test-v2.lidr │ ├── test-v2.md │ ├── test.idr │ └── test.lidr └── utils.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": ["@typescript-eslint"], 9 | "rules": { 10 | "@typescript-eslint/semi": ["error", "never"], 11 | "@typescript-eslint/no-unused-vars": [ 12 | "warn", 13 | { "argsIgnorePattern": "^_.*", "varsIgnorePattern": "^_.*" } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: cachix/install-nix-action@v18 15 | - uses: cachix/cachix-action@v12 16 | with: 17 | name: idris-ide-client 18 | signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" 19 | - name: Use Node.js 14.x 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: 14.x 23 | 24 | - run: npm ci 25 | 26 | - name: run tests 27 | run: nix-shell nix/idris1and2.nix --run "npm test" 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | test/resources/*.ibc 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "singleQuote": false 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | ### Added 3 | ### Changed 4 | ### Fixed 5 | 6 | ## v0.1.6 7 | ### Added 8 | ### Changed 9 | - Added workarounds to the reply-parser to handle Idris 2, version 0.6.0. 10 | ### Fixed 11 | 12 | ## v0.1.5 13 | ### Added 14 | - Added the new Idris2-specific commands :generate-def, :generate-def-next and :proof-search-next. 15 | - Added a typeAt method, which implements the Idris2 :type-of + location command. 16 | ### Changed 17 | - The client now keeps track of the protocol version, and some requests are serialised differently depending on the version. 18 | ### Fixed 19 | - Partially fixed a bug where the :version request didn't work with Idris2 because of a breaking change in the protocol. 20 | 21 | ## v0.1.4 22 | ### Added 23 | - Added a listening flag to the client, so it can stop listening before closing, to handle Idris2 printing invalid messages on exit. 24 | ### Changed 25 | - A few workarounds have been added to the reply-parser to handle Idris 2, version 0.2.1. 26 | ### Fixed 27 | - Cover the case where :add-clause returns an error. 28 | 29 | ## v0.1.3 30 | ### Added 31 | ### Changed 32 | - Breaking: `ok` in FinalReply is now a Boolean discriminator. 33 | 34 | ### Fixed 35 | - Handle a bug where the Idris reply length-header doesn’t match the actual message length on Windows. 36 | 37 | ## v0.1.2 38 | ### Added 39 | - Support for connecting to an Idris process over sockets. 40 | 41 | ### Changed 42 | - Breaking: IdrisClient constructor now accepts an input stream and an output stream, and no longer manages the Idris process. 43 | 44 | ### Fixed 45 | - Fixed a bug in the lexer that would cause it to crash if reading a slash before a quote, such as in the function name `"\\"`. 46 | - Fixed a bug in the lexer where it would return the escaped `\\` rather than the actual value `\` in strings. 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Raymond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # idris-ide-client 2 | A TypeScript library for communicating with an Idris IDE process. 3 | 4 | ## Usage 5 | ```typescript 6 | import { IdrisClient } from "idris-ide-client" 7 | 8 | // Create an Idris process 9 | const idrisProc = spawn("idris", ["--ide-mode"]) 10 | 11 | // Instantiate the client 12 | const client = new IdrisClient(idrisProc.stdin, idrisProc.stdout) 13 | 14 | // Load a file 15 | await client.loadFile("test/resources/test.idr") 16 | 17 | // Make a request 18 | const reply = await client.typeOf("n") 19 | 20 | // Do something with the reply 21 | if (reply.ok) { 22 | console.log(reply.typeOf) // => "n : Nat" 23 | } else { 24 | console.warn(reply.err) 25 | } 26 | 27 | // Close the process 28 | idrisProc.kill() 29 | ``` 30 | 31 | ## What’s it for? 32 | It’s primarily intended to serve as the foundation for my [VSCode extension](https://github.com/meraymond2/idris-vscode). By keeping it as a separate layer, it can easily be reused in case someone wants to make their own extension. Or for another editor that supports TypeScript. 33 | 34 | Types are great documentation, and I’ve typed all of the s-expressions that the IDE returns in TypeScript. As the IDE protocol documentation is a bit spare, hopefully this will be of help to anyone implementing something similar in the future. 35 | 36 | ## Status 37 | It works with Idris 1.3.X on all OSes. 38 | 39 | It is also compatible with the most recent release of Idris 2, currently v0.6.0. This will _not_ maintain backwards compatibility with older versions of Idris 2 until it stabilises. 40 | 41 | Not all IDE commands have been completely implemented in Idris2, see the issues for their current status. 42 | 43 | If you experience any problems on other versions or OSes, please raise an issue. Confirming compatability should be as simple as running the tests. 44 | 45 | A few request-types related to tt-terms have not been implemented yet, because I don’t understand what they’re supposed to do. 46 | 47 | ## Known Issues 48 | The handling for unexpected errors could use a bit of work. For example, in Idris 1.3.4, you can cause the IDE to crash by passing in a line number that doesn’t exist, and there’s nothing at the moment to restart the IDE if it dies unexpectedly. 49 | 50 | ## Upgrade idris for CI 51 | 52 | from the root: 53 | ```sh 54 | niv update nixpkgs -b nixos-22.05 55 | niv update nixpkgs-unstable -b nixpkgs-unstable 56 | ``` 57 | this will update to the newest nixos-unstable branch of nixpkgs. 58 | 59 | To test the idris versions in the nix shell you can then use 60 | ```sh 61 | nix-shell nix/idris1and2.nix 62 | ``` 63 | to get dropped into a shell with both `idris` and `idris2`. 64 | 65 | To directly run the tests in the nix shell use: 66 | ```sh 67 | nix-shell nix/idris1and2.nix --run "npm test" 68 | ``` 69 | -------------------------------------------------------------------------------- /examples/socket.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from "child_process" 2 | import { IdrisClient } from "idris-ide-client" 3 | import { Socket } from "net" 4 | 5 | /* Idris 1 */ 6 | { 7 | const idrisProc = spawn("idris", ["--ide-mode-socket"]) 8 | 9 | // When you start Idris in socket IDE mode, it prints the socket port. 10 | const portProm: Promise = new Promise((res) => 11 | idrisProc.stdout.once("data", (chunk: Buffer) => 12 | res(parseInt(chunk.toString("utf8"))) 13 | ) 14 | ) 15 | const socket = new Socket({ readable: true, writable: true }) 16 | 17 | const port = await portProm 18 | socket.connect({ port }) 19 | 20 | const client = new IdrisClient(socket, socket) 21 | client 22 | .interpret("2 + 2") 23 | .then(console.log) 24 | .then(() => socket.destroy()) 25 | } 26 | 27 | /* Idris 2 */ 28 | { 29 | const idrisProc = spawn("idris2", ["--ide-mode-socket", "--find-ipkg"]) 30 | 31 | // When you start Idris in socket IDE mode, it prints the socket port. 32 | const portProm: Promise = new Promise((res) => 33 | idrisProc.stdout.once("data", (chunk: Buffer) => 34 | res(parseInt(chunk.toString("utf8"))) 35 | ) 36 | ) 37 | const socket = new Socket({ readable: true, writable: true }) 38 | 39 | const port = await portProm 40 | socket.connect({ port }) 41 | 42 | const client = new IdrisClient(socket, socket) 43 | client 44 | .interpret("2 + 2") 45 | .then(console.log) 46 | .then(() => socket.destroy()) 47 | } 48 | -------------------------------------------------------------------------------- /examples/stdio.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from "child_process" 2 | import { IdrisClient } from "idris-ide-client" 3 | 4 | /* Idris 1 */ 5 | { 6 | const idrisProc = spawn("idris", ["--ide-mode"]) 7 | const client = new IdrisClient(idrisProc.stdin, idrisProc.stdout) 8 | 9 | client 10 | .interpret("2 + 2") 11 | .then(console.log) 12 | .then(() => idrisProc.kill()) 13 | } 14 | 15 | /* Idris 2 */ 16 | { 17 | const idrisProc = spawn("idris2", ["--ide-mode", "--find-ipkg"]) 18 | const client = new IdrisClient(idrisProc.stdin, idrisProc.stdout) 19 | 20 | client 21 | .iterpret("2 + 2") 22 | .then(console.log) 23 | .then(() => idrisProc.kill()) 24 | } 25 | -------------------------------------------------------------------------------- /nix/idris1and2.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = import ./nixpkgs.nix { }; 3 | nixpkgs-unstable = import ./nixpkgs-unstable.nix { }; 4 | in 5 | nixpkgs.mkShell { 6 | buildInputs = 7 | [ 8 | nixpkgs.idris 9 | nixpkgs-unstable.idris2 10 | ]; 11 | } 12 | -------------------------------------------------------------------------------- /nix/nixpkgs-unstable.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ./sources.nix }: # import the sources 2 | with 3 | { 4 | overlay = _: pkgs: 5 | { 6 | niv = import sources.niv { }; # use the sources :) 7 | }; 8 | }; 9 | import 10 | sources.nixpkgs-unstable # and use them again! 11 | { overlays = [ overlay ]; config = { }; } 12 | -------------------------------------------------------------------------------- /nix/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | { sources ? import ./sources.nix }: # import the sources 2 | with 3 | { 4 | overlay = _: pkgs: 5 | { 6 | niv = import sources.niv { }; # use the sources :) 7 | }; 8 | }; 9 | import 10 | sources.nixpkgs # and use them again! 11 | { overlays = [ overlay ]; config = { }; } 12 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "niv": { 3 | "branch": "master", 4 | "description": "Easy dependency management for Nix projects", 5 | "homepage": "https://github.com/nmattia/niv", 6 | "owner": "nmattia", 7 | "repo": "niv", 8 | "rev": "f73bf8d584148677b01859677a63191c31911eae", 9 | "sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs", 10 | "type": "tarball", 11 | "url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "nixos-22.05", 16 | "description": "Nix Packages collection", 17 | "homepage": "https://github.com/NixOS/nixpkgs", 18 | "owner": "nixos", 19 | "repo": "nixpkgs", 20 | "rev": "26eb67abc9a7370a51fcb86ece18eaf19ae9207f", 21 | "sha256": "091m0hhv4w24rpffngiczn3xcgrz5gyhz14zyi6hg1q8zjy8pwyg", 22 | "type": "tarball", 23 | "url": "https://github.com/nixos/nixpkgs/archive/26eb67abc9a7370a51fcb86ece18eaf19ae9207f.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "nixpkgs-unstable": { 27 | "branch": "nixpkgs-unstable", 28 | "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to", 29 | "homepage": "https://github.com/NixOS/nixpkgs", 30 | "owner": "NixOS", 31 | "repo": "nixpkgs", 32 | "rev": "7f9be6a505a31f88499c5d20d11f98accf5ae6ba", 33 | "sha256": "147wp515k1x08blm3nn2z994wbdnbl0vhp472ym1habfihfr7x65", 34 | "type": "tarball", 35 | "url": "https://github.com/NixOS/nixpkgs/archive/7f9be6a505a31f88499c5d20d11f98accf5ae6ba.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | if spec ? ref then spec.ref else 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 | submodules = if spec ? submodules then spec.submodules else false; 35 | submoduleArg = 36 | let 37 | nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; 38 | emptyArgWithWarning = 39 | if submodules == true 40 | then 41 | builtins.trace 42 | ( 43 | "The niv input \"${name}\" uses submodules " 44 | + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " 45 | + "does not support them" 46 | ) 47 | {} 48 | else {}; 49 | in 50 | if nixSupportsSubmodules 51 | then { inherit submodules; } 52 | else emptyArgWithWarning; 53 | in 54 | builtins.fetchGit 55 | ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); 56 | 57 | fetch_local = spec: spec.path; 58 | 59 | fetch_builtin-tarball = name: throw 60 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 61 | $ niv modify ${name} -a type=tarball -a builtin=true''; 62 | 63 | fetch_builtin-url = name: throw 64 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 65 | $ niv modify ${name} -a type=file -a builtin=true''; 66 | 67 | # 68 | # Various helpers 69 | # 70 | 71 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 72 | sanitizeName = name: 73 | ( 74 | concatMapStrings (s: if builtins.isList s then "-" else s) 75 | ( 76 | builtins.split "[^[:alnum:]+._?=-]+" 77 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 78 | ) 79 | ); 80 | 81 | # The set of packages used when specs are fetched using non-builtins. 82 | mkPkgs = sources: system: 83 | let 84 | sourcesNixpkgs = 85 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 86 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 87 | hasThisAsNixpkgsPath = == ./.; 88 | in 89 | if builtins.hasAttr "nixpkgs" sources 90 | then sourcesNixpkgs 91 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 92 | import {} 93 | else 94 | abort 95 | '' 96 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 97 | add a package called "nixpkgs" to your sources.json. 98 | ''; 99 | 100 | # The actual fetching function. 101 | fetch = pkgs: name: spec: 102 | 103 | if ! builtins.hasAttr "type" spec then 104 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 105 | else if spec.type == "file" then fetch_file pkgs name spec 106 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 107 | else if spec.type == "git" then fetch_git name spec 108 | else if spec.type == "local" then fetch_local spec 109 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 110 | else if spec.type == "builtin-url" then fetch_builtin-url name 111 | else 112 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 113 | 114 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 115 | # the path directly as opposed to the fetched source. 116 | replace = name: drv: 117 | let 118 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 119 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 120 | in 121 | if ersatz == "" then drv else 122 | # this turns the string into an actual Nix path (for both absolute and 123 | # relative paths) 124 | if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 125 | 126 | # Ports of functions for older nix versions 127 | 128 | # a Nix version of mapAttrs if the built-in doesn't exist 129 | mapAttrs = builtins.mapAttrs or ( 130 | f: set: with builtins; 131 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 132 | ); 133 | 134 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 135 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 136 | 137 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 138 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 139 | 140 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 141 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 142 | concatMapStrings = f: list: concatStrings (map f list); 143 | concatStrings = builtins.concatStringsSep ""; 144 | 145 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 146 | optionalAttrs = cond: as: if cond then as else {}; 147 | 148 | # fetchTarball version that is compatible between all the versions of Nix 149 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 150 | let 151 | inherit (builtins) lessThan nixVersion fetchTarball; 152 | in 153 | if lessThan nixVersion "1.12" then 154 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 155 | else 156 | fetchTarball attrs; 157 | 158 | # fetchurl version that is compatible between all the versions of Nix 159 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 160 | let 161 | inherit (builtins) lessThan nixVersion fetchurl; 162 | in 163 | if lessThan nixVersion "1.12" then 164 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 165 | else 166 | fetchurl attrs; 167 | 168 | # Create the final "sources" from the config 169 | mkSources = config: 170 | mapAttrs ( 171 | name: spec: 172 | if builtins.hasAttr "outPath" spec 173 | then abort 174 | "The values in sources.json should not have an 'outPath' attribute" 175 | else 176 | spec // { outPath = replace name (fetch config.pkgs name spec); } 177 | ) config.sources; 178 | 179 | # The "config" used by the fetchers 180 | mkConfig = 181 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 182 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 183 | , system ? builtins.currentSystem 184 | , pkgs ? mkPkgs sources system 185 | }: rec { 186 | # The sources, i.e. the attribute set of spec name to spec 187 | inherit sources; 188 | 189 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 190 | inherit pkgs; 191 | }; 192 | 193 | in 194 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "idris-ide-client", 3 | "version": "0.1.6", 4 | "description": "A library for talking to the Idris IDE.", 5 | "keywords": [ 6 | "idris" 7 | ], 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/meraymond2/idris-ide-client" 12 | }, 13 | "main": "./build/index.js", 14 | "types": "./build/index.d.ts", 15 | "files": [ 16 | "build" 17 | ], 18 | "scripts": { 19 | "build": "tsc -p .", 20 | "eslint": "eslint src --ext ts", 21 | "lint": "tsc --noEmit && prettier --check --write 'src/**/*.ts' && eslint src --ext .ts --fix", 22 | "prepublishOnly": "npm run build", 23 | "start": "npm run build && node build/index.js", 24 | "test": "npx mocha --recursive --require ts-node/register --extensions ts 'test/**/*.ts'", 25 | "watch": "tsc -watch -p ./" 26 | }, 27 | "devDependencies": { 28 | "@types/chai": "4.2.21", 29 | "@types/mocha": "8.2.3", 30 | "@types/node": "14.17.2", 31 | "@typescript-eslint/eslint-plugin": "4.28.2", 32 | "@typescript-eslint/parser": "4.28.2", 33 | "chai": "4.3.4", 34 | "eslint": "7.30.0", 35 | "mocha": "9.2.2", 36 | "prettier": "2.3.2", 37 | "ts-node": "10.1.0", 38 | "typescript": "4.3.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { IdrisClient } from "./client" 2 | export { 3 | InfoReply, 4 | FinalReply, 5 | HolePremise, 6 | Metavariable, 7 | MessageMetadata, 8 | OutputReply, 9 | Reply, 10 | SourceMetadata, 11 | } from "./reply" 12 | export { Request } from "./request" 13 | export { Decor } from "./s-exps" 14 | -------------------------------------------------------------------------------- /src/parser/index.ts: -------------------------------------------------------------------------------- 1 | import { parseReply } from "./reply-parser" 2 | import { TokenIter } from "./lexer" 3 | import { RootExpr, Expr, isRootExpr } from "../s-exps" 4 | 5 | export const parse = (ts: TokenIter): Expr => { 6 | const next = ts.next() 7 | if (next === null) return [] 8 | switch (next.type) { 9 | case "LEFT_PAREN": { 10 | let exprs = [] 11 | while (ts.peek()?.type !== "RIGHT_PAREN") { 12 | exprs.push(parse(ts)) 13 | } 14 | ts.next() // consume right paren 15 | return exprs 16 | } 17 | case "NAT": 18 | return next.nat 19 | case "STRING": 20 | return next.str 21 | case "SYMBOL": 22 | return next.sym 23 | case "RIGHT_PAREN": 24 | throw Error("Unexpected right paren encountered while parsing.") 25 | } 26 | } 27 | 28 | export const parseRootExpr = (plString: string): RootExpr => { 29 | const tokens = new TokenIter(plString) 30 | const expr: Expr = parse(tokens) 31 | if (!isRootExpr(expr)) throw "Reply not handled: " + JSON.stringify(plString) 32 | return expr 33 | } 34 | 35 | export { parseReply } 36 | -------------------------------------------------------------------------------- /src/parser/lexer.ts: -------------------------------------------------------------------------------- 1 | import { Token } from "./tokens" 2 | 3 | const whitespace = (char: string): boolean => 4 | char === " " || char === "\t" || char === "\n" || char === "\r" 5 | 6 | const isNumeric = (char: string): boolean => { 7 | const charCode = char.charCodeAt(0) 8 | return charCode >= 48 && charCode <= 57 9 | } 10 | 11 | // Is a character part of a keyword: alpha-numeric or a hyphen. 12 | const isKeywordChar = (char: string): boolean => { 13 | const charCode = char.charCodeAt(0) 14 | return ( 15 | (97 <= charCode && charCode <= 122) || 16 | char === "-" || 17 | (65 <= charCode && charCode <= 90) 18 | ) 19 | } 20 | 21 | /** 22 | * Parses the s-exp representation of a string and into a JS string. 23 | * Unfortunately, we can’t just reuse JSON.parse. It does not have exhaustive 24 | * error handling; it assumes strings are well-formed. 25 | */ 26 | const parseEscapes = (s: string): string => { 27 | let res = "" 28 | let pos = 1 // skip opening quotes 29 | const end = s.length - 1 // skip closing quotes 30 | while (pos < end) { 31 | if (s[pos] === "\\") pos += 1 32 | 33 | res += s[pos] 34 | pos += 1 35 | } 36 | return res 37 | } 38 | 39 | export class TokenIter { 40 | private text: string 41 | private pos: number 42 | 43 | constructor(msg: string) { 44 | this.pos = 0 45 | this.text = msg 46 | } 47 | 48 | advance = (n: number = 1): void => { 49 | this.pos += n 50 | } 51 | 52 | next = (): Token | null => { 53 | if (this.pos >= this.text.length) return null 54 | 55 | const char = this.text[this.pos] 56 | switch (char) { 57 | case "(": 58 | this.advance() 59 | return { type: "LEFT_PAREN" } 60 | case ")": 61 | this.advance() 62 | return { type: "RIGHT_PAREN" } 63 | case ":": { 64 | const start = this.pos 65 | this.advance() // advance over the colon 66 | while (isKeywordChar(this.text[this.pos])) { 67 | this.advance() 68 | } 69 | return { type: "SYMBOL", sym: this.text.slice(start, this.pos) } 70 | } 71 | case '"': { 72 | const start = this.pos 73 | this.advance() // consume the opening quotes 74 | let escapes = 0 75 | while (this.text[this.pos] !== '"' || escapes % 2 !== 0) { 76 | if (this.text[this.pos] === "\\") { 77 | escapes += 1 78 | } else { 79 | escapes = 0 80 | } 81 | this.advance() 82 | } 83 | this.advance() // consume the closing quotes 84 | const str = parseEscapes(this.text.slice(start, this.pos)) 85 | return { type: "STRING", str } 86 | } 87 | default: { 88 | if (isNumeric(char)) { 89 | const start = this.pos 90 | while (isNumeric(this.text[this.pos])) { 91 | this.advance() 92 | } 93 | return { 94 | type: "NAT", 95 | nat: parseInt(this.text.slice(start, this.pos)), 96 | } 97 | } else if (whitespace(char)) { 98 | this.advance() 99 | return this.next() 100 | } else { 101 | throw ( 102 | "Unhandled character: " + 103 | this.text[this.pos] + 104 | " at " + 105 | this.pos + 106 | " in " + 107 | this.text.slice(this.pos - 100, this.pos + 100) 108 | ) 109 | } 110 | } 111 | } 112 | } 113 | 114 | peek = (): Token | null => { 115 | const start = this.pos 116 | const token = this.next() 117 | this.pos = start 118 | return token 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/parser/tokens.ts: -------------------------------------------------------------------------------- 1 | type TokenType = "LEFT_PAREN" | "RIGHT_PAREN" | "NAT" | "STRING" | "SYMBOL" 2 | 3 | interface TokenBase { 4 | type: TokenType 5 | } 6 | 7 | export interface LeftParen extends TokenBase { 8 | type: "LEFT_PAREN" 9 | } 10 | 11 | export interface RightParen extends TokenBase { 12 | type: "RIGHT_PAREN" 13 | } 14 | 15 | export interface Nat extends TokenBase { 16 | nat: number 17 | type: "NAT" 18 | } 19 | 20 | export interface Str extends TokenBase { 21 | str: string 22 | type: "STRING" 23 | } 24 | 25 | export interface Sym extends TokenBase { 26 | sym: string 27 | type: "SYMBOL" 28 | } 29 | 30 | export type Token = LeftParen | RightParen | Nat | Str | Sym 31 | -------------------------------------------------------------------------------- /src/reply.ts: -------------------------------------------------------------------------------- 1 | import { Decor, S_ExpBool, ReplyType } from "./s-exps" 2 | 3 | interface BaseReply { 4 | id: number 5 | type: ReplyType 6 | } 7 | 8 | interface Position { 9 | line: number 10 | column: number 11 | } 12 | 13 | interface MetaDetails { 14 | decor?: Decor 15 | docOverview?: string 16 | key?: string 17 | implicit?: S_ExpBool 18 | name?: string 19 | namespace?: string 20 | sourceFile?: string 21 | textFormatting?: string 22 | ttTerm?: string 23 | type?: string 24 | } 25 | 26 | export interface MessageMetadata { 27 | start: number 28 | length: number 29 | metadata: MetaDetails 30 | } 31 | 32 | export interface SourceMetadata { 33 | filename: string 34 | start: Position 35 | end: Position 36 | metadata: MetaDetails 37 | } 38 | 39 | export interface FileWarning { 40 | end: Position 41 | filename: string 42 | metadata: Array 43 | start: Position 44 | warning: string 45 | } 46 | 47 | export interface Declaration { 48 | name: string 49 | metadata: Array 50 | } 51 | 52 | export interface Variable { 53 | name: string 54 | type: string 55 | metadata: Array 56 | } 57 | 58 | export interface HolePremise { 59 | name: string 60 | type: string 61 | metadata: Array 62 | } 63 | 64 | export interface Metavariable { 65 | metavariable: Variable 66 | premises: Array 67 | } 68 | 69 | export namespace InfoReply { 70 | export interface SetPrompt extends BaseReply { 71 | path: string 72 | id: number 73 | type: ":set-prompt" 74 | } 75 | 76 | export interface Version extends BaseReply { 77 | version: number 78 | id: number 79 | type: ":protocol-version" 80 | } 81 | 82 | export interface Warning extends BaseReply { 83 | err: FileWarning 84 | id: number 85 | type: ":warning" 86 | } 87 | 88 | export interface WriteString extends BaseReply { 89 | message: string 90 | id: number 91 | type: ":write-string" 92 | } 93 | } 94 | 95 | export interface OutputReply extends BaseReply { 96 | ok: Array 97 | id: number 98 | type: ":output" 99 | } 100 | 101 | // FinalReply instances don’t inherit, so that they can be ok/err unions. 102 | export namespace FinalReply { 103 | export const isFinalReply = (reply: Reply): boolean => 104 | reply.type === ":return" 105 | 106 | export type AddClauseOk = { 107 | initialClause: string 108 | id: number 109 | ok: true 110 | type: ":return" 111 | } 112 | type AddClauseErr = { err: string; id: number; ok: false; type: ":return" } 113 | 114 | export type AddClause = AddClauseOk | AddClauseErr 115 | 116 | export type AddMissing = { 117 | id: number 118 | ok: true 119 | missingClauses: string 120 | type: ":return" 121 | } 122 | 123 | type AproposOk = { 124 | docs: string 125 | id: number 126 | metadata: Array | undefined 127 | ok: true 128 | type: ":return" 129 | } 130 | 131 | type AproposErr = { err: string; id: number; ok: false; type: ":return" } 132 | 133 | export type Apropos = AproposOk | AproposErr 134 | 135 | type BrowseNamespaceOk = { 136 | declarations: Array 137 | id: number 138 | ok: true 139 | subModules: string[] 140 | type: ":return" 141 | } 142 | 143 | type BrowseNamespaceErr = { 144 | err: string 145 | id: number 146 | ok: false 147 | type: ":return" 148 | } 149 | 150 | export type BrowseNamespace = BrowseNamespaceOk | BrowseNamespaceErr 151 | 152 | /** 153 | * Caller is null only when it is not found. 154 | */ 155 | export type CallsWho = { 156 | caller: Declaration | null 157 | id: number 158 | ok: true 159 | references: Array 160 | type: ":return" 161 | } 162 | 163 | export type CaseSplitOk = { 164 | caseClause: string 165 | id: number 166 | ok: true 167 | type: ":return" 168 | } 169 | 170 | type CaseSplitErr = { err: string; id: number; ok: false; type: ":return" } 171 | 172 | export type CaseSplit = CaseSplitOk | CaseSplitErr 173 | 174 | type DocsForOk = { 175 | docs: string 176 | metadata: Array 177 | id: number 178 | ok: true 179 | type: ":return" 180 | } 181 | 182 | type DocsForErr = { err: string; id: number; ok: false; type: ":return" } 183 | 184 | export type DocsFor = DocsForOk | DocsForErr 185 | 186 | export type GenerateDefOk = { 187 | def: string 188 | id: number 189 | ok: true 190 | type: ":return" 191 | } 192 | 193 | type GenerateDefErr = { err: string; id: number; ok: false; type: ":return" } 194 | 195 | export type GenerateDef = GenerateDefOk | GenerateDefErr 196 | 197 | /** 198 | * If part of the input can be interpreted, it will be an error, but with metadata. 199 | */ 200 | type InterpretOk = { 201 | id: number 202 | ok: true 203 | metadata: Array 204 | result: string 205 | type: ":return" 206 | } 207 | 208 | type InterpretErr = { 209 | err: string 210 | metadata: MessageMetadata[] 211 | id: number 212 | ok: false 213 | type: ":return" 214 | } 215 | 216 | export type Interpret = InterpretOk | InterpretErr 217 | 218 | type LoadFileOk = { 219 | id: number 220 | ok: true 221 | type: ":return" 222 | } 223 | 224 | type LoadFileErr = { err: string; id: number; ok: false; type: ":return" } 225 | 226 | export type LoadFile = LoadFileOk | LoadFileErr 227 | 228 | export type MakeCase = { 229 | caseClause: string 230 | id: number 231 | ok: true 232 | type: ":return" 233 | } 234 | 235 | export type MakeLemmaOk = { 236 | declaration: string 237 | metavariable: string 238 | id: number 239 | ok: true 240 | type: ":return" 241 | } 242 | 243 | type MakeLemmaErr = { err: string; id: number; ok: false; type: ":return" } 244 | 245 | export type MakeLemma = MakeLemmaOk | MakeLemmaErr 246 | 247 | export type MakeWith = { 248 | id: number 249 | ok: true 250 | type: ":return" 251 | withClause: string 252 | } 253 | 254 | export type Metavariables = { 255 | id: number 256 | metavariables: Array 257 | ok: true 258 | type: ":return" 259 | } 260 | 261 | type PrintDefinitionOk = { 262 | definition: string 263 | id: number 264 | metadata: Array | undefined 265 | ok: true 266 | type: ":return" 267 | } 268 | 269 | type PrintDefinitionErr = { 270 | err: string 271 | id: number 272 | ok: false 273 | type: ":return" 274 | } 275 | 276 | export type PrintDefinition = PrintDefinitionOk | PrintDefinitionErr 277 | 278 | type ProofSearchOk = { 279 | id: number 280 | ok: true 281 | solution: string 282 | type: ":return" 283 | } 284 | 285 | type ProofSearchErr = { err: string; id: number; ok: false; type: ":return" } 286 | 287 | export type ProofSearch = ProofSearchOk | ProofSearchErr 288 | 289 | /** 290 | * This reply omits an additional element that seems to always be an empty string. 291 | */ 292 | export type ReplCompletions = { 293 | completions: Array 294 | id: number 295 | ok: true 296 | type: ":return" 297 | } 298 | 299 | type TypeAtOk = { 300 | id: number 301 | ok: true 302 | type: ":return" 303 | typeAt: string 304 | } 305 | 306 | type TypeAtErr = { err: string; id: number; ok: false; type: ":return" } 307 | 308 | export type TypeAt = TypeAtOk | TypeAtErr 309 | 310 | type TypeOfOk = { 311 | id: number 312 | ok: true 313 | metadata: Array 314 | type: ":return" 315 | typeOf: string 316 | } 317 | 318 | type TypeOfErr = { err: string; id: number; ok: false; type: ":return" } 319 | 320 | export type TypeOf = TypeOfOk | TypeOfErr 321 | 322 | export type Version = { 323 | id: number 324 | minor: number 325 | major: number 326 | ok: true 327 | patch: number 328 | tags: Array 329 | type: ":return" 330 | } 331 | 332 | export type WhoCalls = { 333 | callee: Declaration | null 334 | references: Array 335 | id: number 336 | ok: true 337 | type: ":return" 338 | } 339 | } 340 | 341 | export type Reply = 342 | | FinalReply.AddClause 343 | | FinalReply.AddMissing 344 | | FinalReply.Apropos 345 | | FinalReply.BrowseNamespace 346 | | FinalReply.CallsWho 347 | | FinalReply.CaseSplit 348 | | FinalReply.DocsFor 349 | | FinalReply.GenerateDef 350 | | FinalReply.Interpret 351 | | FinalReply.LoadFile 352 | | FinalReply.MakeCase 353 | | FinalReply.MakeLemma 354 | | FinalReply.MakeWith 355 | | FinalReply.Metavariables 356 | | FinalReply.PrintDefinition 357 | | FinalReply.ProofSearch 358 | | FinalReply.ReplCompletions 359 | | FinalReply.TypeOf 360 | | FinalReply.Version 361 | | FinalReply.WhoCalls 362 | | InfoReply.SetPrompt 363 | | InfoReply.Version 364 | | InfoReply.Warning 365 | | InfoReply.WriteString 366 | | OutputReply 367 | -------------------------------------------------------------------------------- /src/request.ts: -------------------------------------------------------------------------------- 1 | export type RequestType = 2 | | ":add-clause" 3 | | ":add-missing" 4 | // | ":add-proof-clause" // TODO: deprecated? 5 | | ":apropos" 6 | | ":browse-namespace" 7 | | ":calls-who" 8 | | ":case-split" 9 | | ":docs-for" 10 | // | ":elaborate-term" 11 | | ":generate-def" 12 | | ":generate-def-next" 13 | // | ":hide-term-implicits" 14 | | ":interpret" 15 | | ":load-file" 16 | | ":make-case" 17 | | ":make-lemma" 18 | | ":make-with" 19 | | ":metavariables" 20 | // | ":normalise-term" 21 | | ":print-definition" 22 | | ":proof-search" 23 | | ":proof-search-next" 24 | | ":repl-completions" 25 | // | ":show-term-implicits" 26 | | ":type-at" 27 | | ":type-of" 28 | | ":version" 29 | | ":who-calls" 30 | 31 | export type DocMode = ":overview" | ":full" 32 | 33 | export namespace Request { 34 | interface RequestBase { 35 | id: number 36 | type: RequestType 37 | } 38 | 39 | export interface AddClause extends RequestBase { 40 | id: number 41 | line: number 42 | name: string 43 | type: ":add-clause" 44 | } 45 | 46 | export interface AddMissing extends RequestBase { 47 | id: number 48 | line: number 49 | name: string 50 | type: ":add-missing" 51 | } 52 | 53 | export interface Apropos extends RequestBase { 54 | id: number 55 | string: string 56 | type: ":apropos" 57 | } 58 | 59 | export interface BrowseNamespace extends RequestBase { 60 | id: number 61 | namespace: string 62 | type: ":browse-namespace" 63 | } 64 | 65 | export interface CallsWho extends RequestBase { 66 | id: number 67 | name: string 68 | type: ":calls-who" 69 | } 70 | 71 | export interface CaseSplit extends RequestBase { 72 | id: number 73 | line: number 74 | name: string 75 | type: ":case-split" 76 | } 77 | 78 | export interface DocsFor extends RequestBase { 79 | id: number 80 | mode: DocMode 81 | name: string 82 | type: ":docs-for" 83 | } 84 | 85 | export interface GenerateDef extends RequestBase { 86 | id: number 87 | line: number 88 | name: string 89 | type: ":generate-def" 90 | } 91 | 92 | export interface GenerateDefNext extends RequestBase { 93 | id: number 94 | type: ":generate-def-next" 95 | } 96 | 97 | export interface Interpret extends RequestBase { 98 | expression: string 99 | id: number 100 | type: ":interpret" 101 | } 102 | 103 | export interface LoadFile extends RequestBase { 104 | id: number 105 | path: string 106 | type: ":load-file" 107 | } 108 | 109 | export interface MakeCase extends RequestBase { 110 | id: number 111 | line: number 112 | name: string 113 | type: ":make-case" 114 | } 115 | 116 | export interface MakeLemma extends RequestBase { 117 | id: number 118 | line: number 119 | name: string 120 | type: ":make-lemma" 121 | } 122 | 123 | export interface MakeWith extends RequestBase { 124 | id: number 125 | line: number 126 | name: string 127 | type: ":make-with" 128 | } 129 | 130 | export interface Metavariables extends RequestBase { 131 | id: number 132 | type: ":metavariables" 133 | width: number 134 | } 135 | 136 | export interface PrintDefinition extends RequestBase { 137 | id: number 138 | name: string 139 | type: ":print-definition" 140 | } 141 | 142 | /// Not sure how to use the hints. 143 | export interface ProofSearch extends RequestBase { 144 | id: number 145 | line: number 146 | name: string 147 | hints: string[] 148 | type: ":proof-search" 149 | } 150 | 151 | export interface ProofSearchNext extends RequestBase { 152 | id: number 153 | type: ":proof-search-next" 154 | } 155 | 156 | export interface ReplCompletions extends RequestBase { 157 | id: number 158 | name: string 159 | type: ":repl-completions" 160 | } 161 | 162 | export interface TypeAt extends RequestBase { 163 | column: number 164 | id: number 165 | line: number 166 | name: string 167 | type: ":type-at" 168 | } 169 | 170 | export interface TypeOf extends RequestBase { 171 | id: number 172 | name: string 173 | type: ":type-of" 174 | } 175 | 176 | export interface Version extends RequestBase { 177 | id: number 178 | type: ":version" 179 | } 180 | 181 | export interface WhoCalls extends RequestBase { 182 | id: number 183 | name: string 184 | type: ":who-calls" 185 | } 186 | } 187 | export type Request = 188 | | Request.AddClause 189 | | Request.AddMissing 190 | | Request.Apropos 191 | | Request.BrowseNamespace 192 | | Request.CallsWho 193 | | Request.CaseSplit 194 | | Request.DocsFor 195 | | Request.GenerateDef 196 | | Request.GenerateDefNext 197 | | Request.Interpret 198 | | Request.LoadFile 199 | | Request.MakeCase 200 | | Request.MakeLemma 201 | | Request.MakeWith 202 | | Request.Metavariables 203 | | Request.PrintDefinition 204 | | Request.ProofSearch 205 | | Request.ProofSearchNext 206 | | Request.ReplCompletions 207 | | Request.TypeAt 208 | | Request.TypeOf 209 | | Request.Version 210 | | Request.WhoCalls 211 | 212 | const pad = (hexNum: string): string => "0".repeat(6 - hexNum.length) + hexNum 213 | 214 | const prependLen = (message: string): string => 215 | pad(message.length.toString(16)) + message 216 | 217 | const serialiseArgs = (req: Request): string => { 218 | switch (req.type) { 219 | case ":add-clause": 220 | case ":add-missing": 221 | case ":case-split": 222 | case ":generate-def": 223 | case ":make-case": 224 | case ":make-lemma": 225 | case ":make-with": 226 | return `${req.type} ${req.line} "${req.name}"` 227 | case ":apropos": 228 | return `${req.type} "${req.string}"` 229 | case ":browse-namespace": 230 | return `${req.type} "${req.namespace}"` 231 | case ":calls-who": 232 | case ":print-definition": 233 | case ":repl-completions": 234 | case ":type-of": 235 | case ":who-calls": 236 | return `${req.type} "${req.name}"` 237 | case ":docs-for": 238 | return `${req.type} "${req.name}" ${req.mode}` 239 | case ":interpret": 240 | return `${req.type} "${req.expression}"` 241 | case ":load-file": 242 | return `${req.type} "${req.path}"` 243 | case ":metavariables": 244 | return `${req.type} ${req.width}` 245 | case ":proof-search": 246 | return `${req.type} ${req.line} "${req.name}" (${req.hints.join(" ")})` 247 | case ":type-at": 248 | return `:type-of "${req.name}" ${req.line} ${req.column}` 249 | case ":generate-def-next": 250 | case ":proof-search-next": 251 | case ":version": 252 | return req.type 253 | } 254 | } 255 | 256 | /** 257 | * Serialise request according to the Idris 1 IDE protocol. Arguments, including 258 | * the request type are always an s-expression list. 259 | */ 260 | export const serialiseRequest = (req: Request): string => 261 | prependLen(`((${serialiseArgs(req)}) ${req.id})\n`) 262 | 263 | /** 264 | * The Idris 2 IDE protocol introduces some new commands, and at least one 265 | * breaking change to the serialised request format. Specifically commands 266 | * without arguments are now simply atoms, rather than single-element lists. 267 | */ 268 | export const serialiseRequestV2 = (req: Request): string => { 269 | if ( 270 | req.type === ":generate-def-next" || 271 | req.type === ":proof-search-next" || 272 | req.type === ":version" 273 | ) { 274 | return prependLen(`(${serialiseArgs(req)} ${req.id})\n`) 275 | } else { 276 | return prependLen(`((${serialiseArgs(req)}) ${req.id})\n`) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/s-exps.ts: -------------------------------------------------------------------------------- 1 | export type ReplyType = 2 | | ":output" 3 | | ":protocol-version" 4 | | ":return" 5 | | ":set-prompt" 6 | | ":warning" 7 | | ":write-string" 8 | 9 | export const isReplyType = (s: string): s is ReplyType => 10 | s === ":protocol-version" || 11 | s === ":output" || 12 | s === ":return" || 13 | s === ":set-prompt" || 14 | s === ":warning" || 15 | s === ":write-string" 16 | 17 | export type Expr = string | number | Expr[] 18 | 19 | export type RootExpr = [ReplyType, Expr, number] 20 | 21 | export const isRootExpr = (expr: Expr): expr is RootExpr => 22 | Array.isArray(expr) && 23 | typeof expr[0] === "string" && 24 | isReplyType(expr[0]) && 25 | typeof expr[2] === "number" 26 | 27 | export type Decor = 28 | | ":bound" 29 | | ":data" 30 | | ":function" 31 | | ":keyword" 32 | | ":module" 33 | | ":metavar" 34 | | ":type" 35 | 36 | export type S_ExpBool = ":True" | ":False" 37 | 38 | export type Decl = [string, MsgMetadataExpr[]] 39 | 40 | type AttributePair = 41 | | [":decor", Decor] 42 | | [":doc-overview", string] 43 | | [":key", string] 44 | | [":implicit", S_ExpBool] 45 | | [":name", string] 46 | | [":namespace", string] 47 | | [":source-file", string] 48 | | [":tt-term", string] 49 | | [":type", string] 50 | 51 | type StartPos = number 52 | type Length = number 53 | export type MsgMetadataExpr = [StartPos, Length, AttributePair[]] 54 | 55 | type LineNum = number 56 | type ColNum = number 57 | type SourceMetaLoc = [ 58 | [":filename", string], 59 | [":start", LineNum, ColNum], 60 | [":end", LineNum, ColNum] 61 | ] 62 | export type SourceMetadataExpr = [SourceMetaLoc, AttributePair[]] 63 | 64 | export namespace S_Exp { 65 | /* Info Replies */ 66 | export type ProtocolVersion = number 67 | export type SetPrompt = string 68 | export type WriteString = string 69 | 70 | export type Warning = [ 71 | string, 72 | [LineNum, ColNum], 73 | [LineNum, ColNum], 74 | string, 75 | MsgMetadataExpr[] 76 | ] 77 | 78 | /* Output Replies */ 79 | export type Output = [":ok", [":highlight-source", SourceMetadataExpr[]]] 80 | 81 | /* Final Replies */ 82 | export type AddClauseOk = [":ok", string] 83 | export type AddClauseErr = [":error", string] 84 | export type AddClause = AddClauseOk | AddClauseErr 85 | export const isOkAddClause = (payload: AddClause): payload is AddClauseOk => 86 | payload[0] === ":ok" 87 | 88 | export type AddMissing = [":ok", string] 89 | 90 | export type AproposOk = [":ok", string, MsgMetadataExpr[]] 91 | export type AproposErr = [":error", "No results found", []] 92 | export type Apropos = AproposOk | AproposErr 93 | export const isOkApropos = (payload: Apropos): payload is AproposOk => 94 | payload[0] === ":ok" 95 | 96 | export type BrowseNamespaceOk = [":ok", [string[], Decl[]]] 97 | export type BrowseNamespaceErr = [":error", "Invalid or empty namespace"] 98 | export type BrowseNamespace = BrowseNamespaceOk | BrowseNamespaceErr 99 | export const isOkBrowseNamespace = ( 100 | payload: BrowseNamespace 101 | ): payload is BrowseNamespaceOk => payload[0] === ":ok" 102 | 103 | /** 104 | * Length of caller/references is 0 or 1. 105 | */ 106 | export type CallsWho = [":ok", [Decl, Decl[]][]] 107 | 108 | export type CaseSplitOk = [":ok", string] 109 | export type CaseSplitErr = [":error", string] 110 | export type CaseSplit = CaseSplitOk | CaseSplitErr 111 | export const isOkCaseSplit = (payload: CaseSplit): payload is CaseSplitOk => 112 | payload[0] === ":ok" 113 | 114 | export type DocsForOk = [":ok", string, MsgMetadataExpr[]] 115 | export type DocsForErr = [":error", string] 116 | export type DocsFor = DocsForOk | DocsForErr 117 | export const isOkDocsFor = (payload: DocsFor): payload is DocsForOk => 118 | payload[0] === ":ok" 119 | 120 | export type GenerateDefOk = [":ok", string] 121 | export type GenerateDefErr = [":error", string] 122 | export type GenerateDef = GenerateDefOk | GenerateDefErr 123 | export const isOkGenerateDef = ( 124 | payload: GenerateDef 125 | ): payload is GenerateDefOk => payload[0] === ":ok" 126 | 127 | export type InterpretOk = [":ok", string, MsgMetadataExpr[]] 128 | // If it can parse some of the input, it returns metadata. 129 | export type InterpretErr = 130 | | [":error", string] 131 | | [":error", string, MsgMetadataExpr[]] 132 | export type Interpret = InterpretOk | InterpretErr 133 | export const isOkInterpret = (payload: Interpret): payload is InterpretOk => 134 | payload[0] === ":ok" 135 | 136 | export type LoadFileOk = [":ok", []] 137 | export type LoadFileErr = [":error", string] 138 | export type LoadFile = LoadFileOk | LoadFileErr 139 | export const isOkLoadFile = (payload: LoadFile): payload is LoadFileOk => 140 | payload[0] === ":ok" 141 | 142 | export type MakeCase = [":ok", string] 143 | 144 | export type MakeLemmaOk = [ 145 | ":ok", 146 | [ 147 | ":metavariable-lemma", 148 | [":replace-metavariable", string], 149 | [":definition-type", string] 150 | ] 151 | ] 152 | export type MakeLemmaErr = [":error", string] 153 | export type MakeLemma = MakeLemmaOk | MakeLemmaErr 154 | export const isOkMakeLemma = (payload: MakeLemma): payload is MakeLemmaOk => 155 | payload[0] === ":ok" 156 | 157 | export type MakeWith = [":ok", string] 158 | 159 | type ScopeVar = [string, string, MsgMetadataExpr[]] 160 | type Metavar = [string, ScopeVar[], [string, MsgMetadataExpr[]]] 161 | export type Metavariables = [":ok", Metavar[]] 162 | 163 | export type PrintDefinitionOk = [":ok", string, MsgMetadataExpr[]] 164 | export type PrintDefinitionErr = [":error", string] 165 | export type PrintDefinition = PrintDefinitionOk | PrintDefinitionErr 166 | export const isOkPrintDefinition = ( 167 | payload: PrintDefinition 168 | ): payload is PrintDefinitionOk => payload[0] === ":ok" 169 | 170 | export type ProofSearchOk = [":ok", string] 171 | export type ProofSearchErr = [":error", string] 172 | export type ProofSearch = ProofSearchOk | ProofSearchErr 173 | export const isOkProofSearch = ( 174 | payload: ProofSearch 175 | ): payload is ProofSearchOk => payload[0] === ":ok" 176 | 177 | export type ReplCompletions = [":ok", [string[], ""]] 178 | 179 | export type TypeAtOk = [":ok", string] 180 | export type TypeAtErr = [":error", string] 181 | export type TypeAt = TypeAtOk | TypeAtErr 182 | export const isOkTypeAt = (payload: TypeAt): payload is TypeAtOk => 183 | payload[0] === ":ok" 184 | 185 | export type TypeOfOk = [":ok", string, MsgMetadataExpr[]] 186 | export type TypeOfErr = [":error", string] 187 | export type TypeOf = TypeOfOk | TypeOfErr 188 | export const isOkTypeOf = (payload: TypeOf): payload is TypeOfOk => 189 | payload[0] === ":ok" 190 | 191 | export type Version = [":ok", [[number, number, number], string[]]] 192 | 193 | /** 194 | * Length of callee/references will be 0 or 1. 195 | */ 196 | export type WhoCalls = [":ok", [Decl, Decl[]][]] 197 | } 198 | -------------------------------------------------------------------------------- /test/client/unimplemented.ts: -------------------------------------------------------------------------------- 1 | import { FinalReply } from "../../src/reply" 2 | 3 | export const addMissing: FinalReply.AddMissing = { 4 | id: 8, 5 | missingClauses: "", 6 | ok: true, 7 | type: ":return", 8 | } 9 | 10 | export const apropos: FinalReply.Apropos = { 11 | docs: "", 12 | metadata: [], 13 | id: 8, 14 | ok: true, 15 | type: ":return", 16 | } 17 | 18 | export const browseNamespace: FinalReply.BrowseNamespace = { 19 | subModules: [], 20 | declarations: [], 21 | id: 8, 22 | ok: true, 23 | type: ":return", 24 | } 25 | 26 | export const callsWho: FinalReply.CallsWho = { 27 | caller: null, 28 | references: [], 29 | id: 8, 30 | ok: true, 31 | type: ":return", 32 | } 33 | 34 | // Partially Implemented — kinda broken 35 | export const makeLemma: FinalReply.MakeLemmaOk = { 36 | declaration: "g_rhs : Bool -> Nat -> String", 37 | metavariable: "g_rhs b n", 38 | id: 8, 39 | ok: true, 40 | type: ":return", 41 | } 42 | 43 | export const metavariables: FinalReply.Metavariables = { 44 | metavariables: [ 45 | { 46 | metavariable: { 47 | metadata: [], 48 | name: `"Main.append"`, 49 | type: "Vect n a -> Vect m a -> Vect (plus n m) a", 50 | }, 51 | premises: [], 52 | }, 53 | { 54 | metavariable: { 55 | metadata: [], 56 | name: `"Main.f"`, 57 | type: "Cat -> String", 58 | }, 59 | premises: [], 60 | }, 61 | { 62 | metavariable: { 63 | metadata: [], 64 | name: `"Main.g_rhs"`, 65 | type: "String", 66 | }, 67 | premises: [ 68 | { 69 | metadata: [], 70 | name: " b", 71 | type: "Bool", 72 | }, 73 | { 74 | metadata: [], 75 | name: " n", 76 | type: "Nat", 77 | }, 78 | ], 79 | }, 80 | { 81 | metavariable: { 82 | metadata: [], 83 | name: `"Main.n_rhs"`, 84 | type: "Nat", 85 | }, 86 | premises: [], 87 | }, 88 | // This is additional to the V2 test, because I had to make it a 89 | // metavariable in order for case split to work. 90 | { 91 | metavariable: { 92 | metadata: [], 93 | name: `"Main.plusTwo_rhs"`, 94 | type: "Nat", 95 | }, 96 | premises: [ 97 | { 98 | metadata: [], 99 | name: " n", 100 | type: "Nat", 101 | }, 102 | ], 103 | }, 104 | ], 105 | id: 8, 106 | ok: true, 107 | type: ":return", 108 | } 109 | 110 | export const printDefinition: FinalReply.PrintDefinition = { 111 | definition: "Bool", 112 | metadata: [], 113 | id: 8, 114 | ok: true, 115 | type: ":return", 116 | } 117 | 118 | export const whoCalls: FinalReply.WhoCalls = { 119 | callee: null, 120 | references: [], 121 | id: 8, 122 | ok: true, 123 | type: ":return", 124 | } 125 | -------------------------------------------------------------------------------- /test/client/v1-client.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { spawn, ChildProcess } from "child_process" 3 | import * as expected from "./v1-expected" 4 | import { clean, omitKeys } from "../utils" 5 | import { IdrisClient } from "../../src/client" 6 | 7 | /** 8 | * Standardise dynamic return values to allow equality checking. 9 | * All messages ids are set to an arbitrary number, so the tests are (mostly) 10 | * order independent. And we remove the ttTerm key, which is random. 11 | */ 12 | const std = (reply: object): object => ({ 13 | ...omitKeys(reply, ["ttTerm"]), 14 | id: 8, 15 | }) 16 | 17 | describe("Running the v1 client commands on test.idr", () => { 18 | let ic: IdrisClient 19 | let proc: ChildProcess 20 | 21 | before(async () => { 22 | clean() 23 | proc = spawn("idris", ["--ide-mode"]) 24 | if (proc.stdin && proc.stdout) { 25 | ic = new IdrisClient(proc.stdin, proc.stdout) 26 | } 27 | }) 28 | 29 | // This test _does_ need to be first, because it sets up the internal state 30 | // of the Idris IDE process. 31 | it("returns the expected result for :load-file", async () => { 32 | const actual = await ic.loadFile("./test/resources/test.idr") 33 | assert.deepEqual(actual, expected.loadFile) 34 | }).timeout(10000) 35 | 36 | it("returns the expected result for :add-clause.", async () => { 37 | const actual = await ic.addClause("f", 5) 38 | assert.deepEqual(std(actual), expected.addClause) 39 | }) 40 | 41 | it("returns the expected result for :add-missing.", async () => { 42 | const actual = await ic.addMissing("getName", 7) 43 | assert.deepEqual(std(actual), expected.addMissing) 44 | }) 45 | 46 | it("returns the expected result for :apropos.", async () => { 47 | const actual = await ic.apropos("b8ToBinString") 48 | assert.deepEqual(std(actual), expected.apropos) 49 | }) 50 | 51 | it("returns the expected result for :browse-namespace.", async () => { 52 | const actual = await ic.browseNamespace("Language.Reflection") 53 | // The metadata for an actual namespace is too long to read if the test fails. 54 | if (actual.ok) actual.declarations = actual.declarations.slice(0, 1) 55 | assert.deepEqual(std(actual), expected.browseNamespace) 56 | }) 57 | 58 | it("returns the expected result for :calls-who.", async () => { 59 | const actual = await ic.callsWho("plusTwo") 60 | assert.deepEqual(std(actual), expected.callsWho) 61 | }) 62 | 63 | it("returns the expected result for :case-split", async () => { 64 | const actual = await ic.caseSplit("n", 12) 65 | assert.deepEqual(std(actual), expected.caseSplit) 66 | }) 67 | 68 | it("returns the expected result for :docs-for", async () => { 69 | const actual = await ic.docsFor("b8ToBinString", ":full") 70 | assert.deepEqual(std(actual), expected.docsFor) 71 | }) 72 | 73 | it("returns the expected result for :interpret", async () => { 74 | const actual = await ic.interpret("2 * 2") 75 | assert.deepEqual(std(actual), expected.interpret) 76 | }) 77 | 78 | it("returns the expected result for :make-case", async () => { 79 | const actual = await ic.makeCase("g_rhs", 15) 80 | assert.deepEqual(std(actual), expected.makeCase) 81 | }) 82 | 83 | it("returns the expected result for :make-lemma", async () => { 84 | const actual = await ic.makeLemma("g_rhs", 15) 85 | assert.deepEqual(std(actual), expected.makeLemma) 86 | }) 87 | 88 | it("returns the expected result for :make-with", async () => { 89 | const actual = await ic.makeWith("g_rhs", 15) 90 | assert.deepEqual(std(actual), expected.makeWith) 91 | }) 92 | 93 | it("returns the expected result for :metavariables", async () => { 94 | const actual = await ic.metavariables(80) 95 | assert.deepEqual(std(actual), expected.metavariables) 96 | }) 97 | 98 | it("returns the expected result for :print-definition", async () => { 99 | const actual = await ic.printDefinition("Bool") 100 | assert.deepEqual(std(actual), expected.printDefinition) 101 | }) 102 | 103 | it("returns the expected result for :proof-search", async () => { 104 | const actual = await ic.proofSearch("n_rhs", 18, []) 105 | assert.deepEqual(std(actual), expected.proofSearch) 106 | }) 107 | 108 | it("returns the expected result for :repl-completions", async () => { 109 | const actual = await ic.replCompletions("get") 110 | assert.deepEqual(std(actual), expected.replCompletions) 111 | }) 112 | 113 | it("returns the expected result for :type-of", async () => { 114 | const actual = await ic.typeOf("Cat") 115 | assert.deepEqual(std(actual), expected.typeOf) 116 | }) 117 | 118 | it("returns the expected result for :version", async () => { 119 | const actual = await ic.version() 120 | // We don’t want to tie the test to an actual version. 121 | assert.isTrue(actual.ok) 122 | }) 123 | 124 | it("returns the expected result for :who-calls", async () => { 125 | const actual = await ic.whoCalls("Cat") 126 | assert.deepEqual(std(actual), expected.whoCalls) 127 | }) 128 | 129 | after(async () => { 130 | proc.kill() 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /test/client/v1-lidr.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { spawn, ChildProcess } from "child_process" 3 | 4 | import * as expected from "./v1-expected" 5 | import * as unimplemented from "./unimplemented" 6 | import { clean, omitKeys } from "../utils" 7 | import { IdrisClient } from "../../src/client" 8 | 9 | /** 10 | * Check the IDE process responses for literate Idris files. 11 | * The expected behaviour is that the they are identical 12 | * to the normal V2 responses, except with the appropriate 13 | * prefix when returning new lines. 14 | * 15 | * From the Idris2 docs: 16 | * Bird notation is a classic literate mode found in Haskell, 17 | * (and Orwell) in which visible code lines begin with > and 18 | * hidden lines with <. Other lines are treated as documentation. 19 | */ 20 | 21 | const codePrefix = "> " 22 | 23 | /** 24 | * Standardise dynamic return values to allow equality checking. 25 | * All messages ids are set to an arbitrary number, so the tests are (mostly) 26 | * order independent. And we remove the ttTerm key, which is random. 27 | */ 28 | const std = (reply: object): object => ({ 29 | ...omitKeys(reply, ["ttTerm"]), 30 | id: 8, 31 | }) 32 | 33 | describe("Running the v1 client commands on test.lidr", () => { 34 | let ic: IdrisClient 35 | let proc: ChildProcess 36 | 37 | before(async () => { 38 | clean() 39 | proc = spawn("idris", ["--ide-mode"]) 40 | if (proc.stdin && proc.stdout) { 41 | ic = new IdrisClient(proc.stdin, proc.stdout) 42 | } 43 | }) 44 | 45 | // This test _does_ need to be first, because it sets up the internal state 46 | // of the Idris IDE process. 47 | it("returns the expected result for :load-file", async () => { 48 | const actual = await ic.loadFile("./test/resources/test.lidr") 49 | assert.deepEqual(actual, expected.loadFile) 50 | }).timeout(10000) 51 | 52 | it("returns the expected result for :add-clause.", async () => { 53 | const actual = await ic.addClause("f", 9) 54 | assert.deepEqual(std(actual), expected.addClause) 55 | }) 56 | 57 | it("returns the expected result for :add-missing.", async () => { 58 | const actual = await ic.addMissing("getName", 11) 59 | assert.deepEqual(std(actual), { 60 | ...expected.addMissing, 61 | missingClauses: codePrefix + expected.addMissing.missingClauses, 62 | }) 63 | }) 64 | 65 | it("returns the expected result for :apropos.", async () => { 66 | const actual = await ic.apropos("b8ToBinString") 67 | assert.deepEqual(std(actual), expected.apropos) 68 | }) 69 | 70 | it("returns the expected result for :browse-namespace.", async () => { 71 | const actual = await ic.browseNamespace("Language.Reflection") 72 | // The metadata for an actual namespace is too long to read if the test fails. 73 | if (actual.ok) actual.declarations = actual.declarations.slice(0, 1) 74 | assert.deepEqual(std(actual), expected.browseNamespace) 75 | }) 76 | 77 | it("returns the expected result for :calls-who.", async () => { 78 | const actual = await ic.callsWho("plusTwo") 79 | assert.deepEqual(std(actual), expected.callsWho) 80 | }) 81 | 82 | it("returns the expected result for :case-split", async () => { 83 | const actual = await ic.caseSplit("n", 18) 84 | const correctlyPrefixed = 85 | expected.caseSplit.caseClause 86 | .trim() 87 | .split("\n") 88 | .map((line) => codePrefix + line) 89 | .join("\n") + "\n" 90 | assert.deepEqual(std(actual), { 91 | ...expected.caseSplit, 92 | caseClause: correctlyPrefixed, 93 | }) 94 | }) 95 | 96 | it("returns the expected result for :docs-for", async () => { 97 | const actual = await ic.docsFor("b8ToBinString", ":full") 98 | assert.deepEqual(std(actual), expected.docsFor) 99 | }) 100 | 101 | it("returns the expected result for :interpret", async () => { 102 | const actual = await ic.interpret("2 * 2") 103 | assert.deepEqual(std(actual), expected.interpret) 104 | }) 105 | 106 | it("returns the expected result for :make-case", async () => { 107 | const actual = await ic.makeCase("g_rhs", 21) 108 | // Missing the code prefix on the second line. 109 | const incorrectlyPrefixed = 110 | expected.makeCase.caseClause 111 | .trim() 112 | .split("\n") 113 | .map((line, idx) => (idx === 0 ? codePrefix : " ") + line) 114 | .join("\n") + "\n" 115 | assert.deepEqual(std(actual), { 116 | ...expected.makeCase, 117 | caseClause: incorrectlyPrefixed, 118 | }) 119 | }) 120 | 121 | it("returns the expected result for :make-lemma", async () => { 122 | const actual = await ic.makeLemma("g_rhs", 21) 123 | // Also wrong, because it's not prefixed at all. 124 | assert.deepEqual(std(actual), expected.makeLemma) 125 | }) 126 | 127 | it("returns the expected result for :make-with", async () => { 128 | const actual = await ic.makeWith("g_rhs", 21) 129 | const correctlyPrefixed = 130 | expected.makeWith.withClause 131 | .trim() 132 | .split("\n") 133 | .map((line) => codePrefix + line) 134 | .join("\n") + "\n" 135 | assert.deepEqual(std(actual), { 136 | ...expected.makeWith, 137 | withClause: correctlyPrefixed, 138 | }) 139 | }) 140 | 141 | it("returns the expected result for :metavariables", async () => { 142 | const actual = await ic.metavariables(80) 143 | assert.deepEqual(std(actual), expected.metavariables) 144 | }) 145 | 146 | it("returns the expected result for :print-definition", async () => { 147 | const actual = await ic.printDefinition("Bool") 148 | assert.deepEqual(std(actual), expected.printDefinition) 149 | }) 150 | 151 | it("returns the expected result for :proof-search", async () => { 152 | const actual = await ic.proofSearch("n_rhs", 24, []) 153 | assert.deepEqual(std(actual), expected.proofSearch) 154 | }) 155 | 156 | it("returns the expected result for :repl-completions", async () => { 157 | const actual = await ic.replCompletions("get") 158 | assert.deepEqual(std(actual), expected.replCompletions) 159 | }) 160 | 161 | it("returns the expected result for :type-of", async () => { 162 | const actual = await ic.typeOf("Cat") 163 | assert.deepEqual(std(actual), expected.typeOf) 164 | }) 165 | 166 | it("returns the expected result for :version", async () => { 167 | const actual = await ic.version() 168 | // We don’t want to tie the test to an actual version. 169 | assert.isTrue(actual.ok) 170 | }) 171 | 172 | it("returns the expected result for :who-calls", async () => { 173 | const actual = await ic.whoCalls("Cat") 174 | assert.deepEqual(std(actual), expected.whoCalls) 175 | }) 176 | 177 | after(async () => { 178 | proc.kill() 179 | }) 180 | }) 181 | -------------------------------------------------------------------------------- /test/client/v2-client.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { spawn, ChildProcess } from "child_process" 3 | import * as expected from "./v2-expected" 4 | import * as unimplemented from "./unimplemented" 5 | import { clean, omitKeys } from "../utils" 6 | import { IdrisClient } from "../../src/client" 7 | 8 | /** 9 | * Standardise dynamic return values to allow equality checking. 10 | * All messages ids are set to an arbitrary number, so the tests are (mostly) 11 | * order independent. And we remove the ttTerm key, which is random. 12 | */ 13 | const std = (reply: object): object => ({ 14 | ...omitKeys(reply, ["ttTerm"]), 15 | id: 8, 16 | }) 17 | 18 | describe("Running the v2 client commands on test.idr", () => { 19 | let ic: IdrisClient 20 | let proc: ChildProcess 21 | 22 | before(async () => { 23 | clean() 24 | proc = spawn("idris2", ["--ide-mode", "--no-color"]) 25 | if (proc.stdin && proc.stdout) { 26 | ic = new IdrisClient(proc.stdin, proc.stdout) 27 | } 28 | }) 29 | 30 | // This test _does_ need to be first, because it sets up the internal state 31 | // of the Idris IDE process. 32 | it("returns the expected result for :load-file", async () => { 33 | const actual = await ic.loadFile("./test/resources/test-v2.idr") 34 | assert.deepEqual(actual, expected.loadFile) 35 | }).timeout(10000) 36 | 37 | it("returns the expected result for :add-clause.", async () => { 38 | const actual = await ic.addClause("f", 7) 39 | assert.deepEqual(std(actual), expected.addClause) 40 | }) 41 | 42 | // Unimplemented 43 | // (:write-string "add-missing: command not yet implemented. Hopefully soon!" 3) 44 | // Also, how will this work if it won’t load the file with missing cases? 45 | it("returns the expected result for :add-missing.", async () => { 46 | const actual = await ic.addMissing("getName", 9) 47 | assert.deepEqual(std(actual), unimplemented.addMissing) 48 | }) 49 | 50 | // Unimplemented 51 | // (:write-string "apropros: command not yet implemented. Hopefully soon!" 4) 52 | it("returns the expected result for :apropos.", async () => { 53 | const actual = await ic.apropos("plus") 54 | assert.deepEqual(std(actual), unimplemented.apropos) 55 | }) 56 | 57 | // Unimplemented 58 | // (:write-string "browse-namespace: command not yet implemented. Hopefully soon!" 3) 59 | it("returns the expected result for :browse-namespace.", async () => { 60 | const actual = await ic.browseNamespace("Language.Reflection") 61 | assert.deepEqual(std(actual), unimplemented.browseNamespace) 62 | }) 63 | 64 | // Unimplemented 65 | // (:write-string "calls-who: command not yet implemented. Hopefully soon!" 6) 66 | it("returns the expected result for :calls-who.", async () => { 67 | const actual = await ic.callsWho("plusTwo") 68 | assert.deepEqual(std(actual), unimplemented.callsWho) 69 | }) 70 | 71 | it("returns the expected result for :case-split", async () => { 72 | const actual = await ic.caseSplit("n", 15) 73 | assert.deepEqual(std(actual), expected.caseSplit) 74 | }) 75 | 76 | it("returns the expected result for :docs-for", async () => { 77 | const actual = await ic.docsFor("putStrLn", ":full") 78 | assert.deepEqual(std(actual), expected.docsFor) 79 | }) 80 | 81 | // No longer includes type in response, possibly intentional. 82 | it("returns the expected result for :interpret", async () => { 83 | const actual = await ic.interpret("2 * 2") 84 | assert.deepEqual(std(actual), expected.interpret) 85 | }) 86 | 87 | it("returns the expected result for :make-case", async () => { 88 | const actual = await ic.makeCase("g_rhs", 18) 89 | assert.deepEqual(std(actual), expected.makeCase) 90 | }) 91 | 92 | // Partially implemented — Broken 93 | // (:return (:ok "g_rhs : Bool -> Nat -> String\ng_rhs b n") 11) 94 | it("returns the expected result for :make-lemma", async () => { 95 | const actual = await ic.makeLemma("g_rhs", 18) 96 | assert.deepEqual(std(actual), unimplemented.makeLemma) 97 | }) 98 | 99 | it("returns the expected result for :make-with", async () => { 100 | const actual = await ic.makeWith("g_rhs", 18) 101 | assert.deepEqual(std(actual), expected.makeWith) 102 | }) 103 | 104 | // Partially implemented — no metadata 105 | it("returns the expected result for :metavariables", async () => { 106 | const actual = await ic.metavariables(80) 107 | assert.deepEqual(std(actual), unimplemented.metavariables) 108 | }) 109 | 110 | // Unimplemented 111 | // (:write-string "print-definition: command not yet implemented. Hopefully soon!" 14) 112 | it("returns the expected result for :print-definition", async () => { 113 | const actual = await ic.printDefinition("Bool") 114 | assert.deepEqual(std(actual), unimplemented.printDefinition) 115 | }) 116 | 117 | it("returns the expected result for :proof-search", async () => { 118 | const actual = await ic.proofSearch("n_rhs", 21, []) 119 | assert.deepEqual(std(actual), expected.proofSearch) 120 | }) 121 | 122 | // Unimplemented 123 | // (:write-string "repl-completions: command not yet implemented. Hopefully soon!" 16) 124 | // (:return (:ok ()) 3) 125 | it("returns the expected result for :repl-completions", async () => { 126 | const actual = await ic.replCompletions("get") 127 | assert.deepEqual(std(actual), expected.replCompletions) 128 | }) 129 | 130 | it("returns the expected result for :type-of", async () => { 131 | const actual = await ic.typeOf("Cat") 132 | assert.deepEqual(std(actual), expected.typeOf) 133 | }) 134 | 135 | it("returns the expected result for :version", async () => { 136 | const actual = await ic.version() 137 | // We don’t want to tie the test to an actual version. 138 | assert.isTrue(actual.ok) 139 | }) 140 | 141 | // Unimplemented 142 | // (:write-string "who-calls: command not yet implemented. Hopefully soon!" 18) 143 | it("returns the expected result for :who-calls", async () => { 144 | const actual = await ic.whoCalls("Cat") 145 | assert.deepEqual(std(actual), unimplemented.whoCalls) 146 | }) 147 | 148 | // New V2 commands 149 | 150 | it("returns the expected result for :generate-def", async () => { 151 | const actual = await ic.generateDef("append", 23) 152 | assert.deepEqual(std(actual), expected.generateDef) 153 | }) 154 | 155 | it("returns the expected result for :generate-def-next", async () => { 156 | const actual = await ic.generateDefNext() 157 | assert.deepEqual(std(actual), expected.generateDefNext) 158 | }) 159 | 160 | it("returns the expected result for :proof-search-next", async () => { 161 | const actual = await ic.proofSearchNext() 162 | assert.deepEqual(std(actual), expected.proofSearchNext) 163 | }) 164 | 165 | it("returns the expected result for :type-at", async () => { 166 | const actual = await ic.typeAt("b", 18, 5) 167 | assert.deepEqual(std(actual), expected.typeAt) 168 | }) 169 | 170 | after(() => { 171 | proc.kill() 172 | }) 173 | }) 174 | -------------------------------------------------------------------------------- /test/client/v2-expected.ts: -------------------------------------------------------------------------------- 1 | import * as v1 from "./v1-expected" 2 | import { FinalReply } from "../../src/reply" 3 | 4 | /** 5 | * The expected replies are extracted into a separate file because I want to reuse 6 | * them across the V1 and V2 tests. That makes it easier to determine where V2 7 | * is not backwards compatible, or has bugs. 8 | * 9 | * The ttTerm key has been omitted, since it’s random, and we can’t check it for equality. 10 | */ 11 | 12 | export const loadFile: FinalReply.LoadFile = v1.loadFile 13 | 14 | export const addClause: FinalReply.AddClauseOk = v1.addClause 15 | 16 | export const caseSplit: FinalReply.CaseSplitOk = { 17 | caseClause: "plusTwo 0 = ?plusTwo_rhs_0\nplusTwo (S k) = ?plusTwo_rhs_1", 18 | id: 8, 19 | ok: true, 20 | type: ":return", 21 | } 22 | 23 | export const docsFor: FinalReply.DocsFor = { 24 | docs: "Prelude.putStrLn : HasIO io => String -> io ()\n Output a string to stdout with a trailing newline.\n Totality: total\n Visibility: export", 25 | metadata: [ 26 | { 27 | length: 16, 28 | metadata: { 29 | decor: ":function", 30 | }, 31 | start: 0, 32 | }, 33 | { 34 | length: 5, 35 | metadata: { 36 | decor: ":type", 37 | }, 38 | start: 19, 39 | }, 40 | { 41 | length: 2, 42 | metadata: { 43 | decor: ":bound", 44 | }, 45 | start: 25, 46 | }, 47 | { 48 | length: 2, 49 | metadata: { 50 | decor: ":keyword", 51 | }, 52 | start: 28, 53 | }, 54 | { 55 | length: 6, 56 | metadata: { 57 | decor: ":type", 58 | }, 59 | start: 31, 60 | }, 61 | { 62 | length: 2, 63 | metadata: { 64 | decor: ":keyword", 65 | }, 66 | start: 38, 67 | }, 68 | { 69 | length: 2, 70 | metadata: { 71 | decor: ":bound", 72 | }, 73 | start: 41, 74 | }, 75 | { 76 | length: 8, 77 | metadata: { 78 | textFormatting: ":underline", 79 | }, 80 | start: 102, 81 | }, 82 | { 83 | length: 5, 84 | metadata: { 85 | decor: ":keyword", 86 | }, 87 | start: 112, 88 | }, 89 | { 90 | length: 10, 91 | metadata: { 92 | textFormatting: ":underline", 93 | }, 94 | start: 120, 95 | }, 96 | { 97 | length: 6, 98 | metadata: { 99 | decor: ":keyword", 100 | }, 101 | start: 132, 102 | }, 103 | ], 104 | id: 8, 105 | ok: true, 106 | type: ":return", 107 | } 108 | 109 | export const interpret: FinalReply.Interpret = { 110 | result: "4", 111 | metadata: [{ length: 1, metadata: { decor: ":data" }, start: 0 }], 112 | id: 8, 113 | ok: true, 114 | type: ":return", 115 | } 116 | 117 | export const makeCase: FinalReply.MakeCase = { 118 | caseClause: "g n b = case _ of\n case_val => ?g_rhs", 119 | id: 8, 120 | ok: true, 121 | type: ":return", 122 | } 123 | 124 | export const makeWith: FinalReply.MakeWith = { 125 | id: 8, 126 | ok: true, 127 | type: ":return", 128 | withClause: "g n b with (_)\n g n b | with_pat = ?g_rhs_rhs", 129 | } 130 | 131 | export const proofSearch: FinalReply.ProofSearch = v1.proofSearch 132 | 133 | export const replCompletions: FinalReply.ReplCompletions = { 134 | completions: [ 135 | "getName", 136 | "getAt", 137 | "getLine", 138 | "getChar", 139 | "getRight", 140 | "getLeft", 141 | ], 142 | id: 8, 143 | ok: true, 144 | type: ":return", 145 | } 146 | 147 | export const typeOf: FinalReply.TypeOf = { 148 | typeOf: "Main.Cat : Type", 149 | metadata: [ 150 | { 151 | length: 8, 152 | metadata: { 153 | decor: ":type", 154 | }, 155 | start: 0, 156 | }, 157 | { 158 | length: 4, 159 | metadata: { 160 | decor: ":type", 161 | }, 162 | start: 11, 163 | }, 164 | ], 165 | id: 8, 166 | ok: true, 167 | type: ":return", 168 | } 169 | 170 | // Idris 2 only. 171 | export const generateDef: FinalReply.GenerateDefOk = { 172 | def: "append [] ys = ys\nappend (x :: xs) ys = x :: append xs ys", 173 | id: 8, 174 | ok: true, 175 | type: ":return", 176 | } 177 | 178 | export const generateDefNext: FinalReply.GenerateDefOk = { 179 | def: "append [] ys = ys\nappend (x :: xs) [] = x :: append xs []\nappend (x :: xs) (y :: ys) = x :: append xs (y :: ys)", 180 | id: 8, 181 | ok: true, 182 | type: ":return", 183 | } 184 | 185 | export const proofSearchNext: FinalReply.ProofSearch = { 186 | id: 8, 187 | ok: true, 188 | solution: "1", 189 | type: ":return", 190 | } 191 | 192 | export const typeAt: FinalReply.TypeAt = { 193 | id: 8, 194 | ok: true, 195 | type: ":return", 196 | typeAt: "b : Bool", 197 | } 198 | -------------------------------------------------------------------------------- /test/client/v2-lidr.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { spawn, ChildProcess } from "child_process" 3 | import * as expected from "./v2-expected" 4 | import * as unimplemented from "./unimplemented" 5 | import { clean, omitKeys } from "../utils" 6 | import { IdrisClient } from "../../src/client" 7 | 8 | /** 9 | * Check the IDE process responses for literate Idris files. 10 | * The expected behaviour is that the they are identical 11 | * to the normal V2 responses, except with the appropriate 12 | * prefix when returning new lines. 13 | * 14 | * From the Idris2 docs: 15 | * Bird notation is a classic literate mode found in Haskell, 16 | * (and Orwell) in which visible code lines begin with > and 17 | * hidden lines with <. Other lines are treated as documentation. 18 | */ 19 | 20 | const codePrefix = "> " 21 | 22 | /** 23 | * Standardise dynamic return values to allow equality checking. 24 | * All messages ids are set to an arbitrary number, so the tests are (mostly) 25 | * order independent. And we remove the ttTerm key, which is random. 26 | */ 27 | const std = (reply: object): object => ({ 28 | ...omitKeys(reply, ["ttTerm"]), 29 | id: 8, 30 | }) 31 | 32 | describe("Running the v2 client commands on test.lidr", () => { 33 | let ic: IdrisClient 34 | let proc: ChildProcess 35 | 36 | before(async () => { 37 | clean() 38 | proc = spawn("idris2", ["--ide-mode", "--no-color"]) 39 | if (proc.stdin && proc.stdout) { 40 | ic = new IdrisClient(proc.stdin, proc.stdout) 41 | } 42 | }) 43 | 44 | // This test _does_ need to be first, because it sets up the internal state 45 | // of the Idris IDE process. 46 | it("returns the expected result for :load-file", async () => { 47 | const actual = await ic.loadFile("./test/resources/test-v2.lidr") 48 | assert.deepEqual(actual, expected.loadFile) 49 | }).timeout(10000) 50 | 51 | it("returns the expected result for :add-clause.", async () => { 52 | const actual = await ic.addClause("f", 11) 53 | const correctlyPrefixed = codePrefix + expected.addClause.initialClause 54 | assert.deepEqual(std(actual), { 55 | ...expected.addClause, 56 | initialClause: correctlyPrefixed, 57 | }) 58 | }) 59 | 60 | // Unimplemented 61 | // (:write-string "add-missing: command not yet implemented. Hopefully soon!" 3) 62 | // Also, how will this work if it won’t load the file with missing cases? 63 | it("returns the expected result for :add-missing.", async () => { 64 | const actual = await ic.addMissing("getName", 9) 65 | assert.deepEqual(std(actual), unimplemented.addMissing) 66 | }) 67 | 68 | // Unimplemented 69 | // (:write-string "apropros: command not yet implemented. Hopefully soon!" 4) 70 | it("returns the expected result for :apropos.", async () => { 71 | const actual = await ic.apropos("plus") 72 | assert.deepEqual(std(actual), unimplemented.apropos) 73 | }) 74 | 75 | // Unimplemented 76 | // (:write-string "browse-namespace: command not yet implemented. Hopefully soon!" 3) 77 | it("returns the expected result for :browse-namespace.", async () => { 78 | const actual = await ic.browseNamespace("Language.Reflection") 79 | assert.deepEqual(std(actual), unimplemented.browseNamespace) 80 | }) 81 | 82 | // Unimplemented 83 | // (:write-string "calls-who: command not yet implemented. Hopefully soon!" 6) 84 | it("returns the expected result for :calls-who.", async () => { 85 | const actual = await ic.callsWho("plusTwo") 86 | assert.deepEqual(std(actual), unimplemented.callsWho) 87 | }) 88 | 89 | it("returns the expected result for :case-split", async () => { 90 | const actual = await ic.caseSplit("n", 21) 91 | const correctlyPrefixed = expected.caseSplit.caseClause 92 | .split("\n") 93 | .map((line) => codePrefix + line) 94 | .join("\n") 95 | assert.deepEqual(std(actual), { 96 | ...expected.caseSplit, 97 | caseClause: correctlyPrefixed, 98 | }) 99 | }) 100 | 101 | // Partially Implemented — no metadata 102 | it("returns the expected result for :docs-for", async () => { 103 | const actual = await ic.docsFor("putStrLn", ":full") 104 | assert.deepEqual(std(actual), expected.docsFor) 105 | }) 106 | 107 | // Partially Implemented — no metadata. 108 | // (:return (:ok "4" ()) 9) 109 | // Also, no longer includes type in response, possibly intentional. 110 | it("returns the expected result for :interpret", async () => { 111 | const actual = await ic.interpret("2 * 2") 112 | assert.deepEqual(std(actual), expected.interpret) 113 | }) 114 | 115 | it("returns the expected result for :make-case", async () => { 116 | const actual = await ic.makeCase("g_rhs", 24) 117 | // I do not think this is the intended behaviour. The first line 118 | // is given an additional prefix. the second line is correctly 119 | // prefixed though, and indented correctly. 120 | // > > g n b = case _ of 121 | // > case_val => ?g_rhs 122 | const incorrectlyPrefixed = expected.makeCase.caseClause 123 | .split("\n") 124 | .map((line, idx) => (idx === 0 ? "> > " + line : "> " + line)) 125 | .join("\n") 126 | assert.deepEqual(std(actual), { 127 | ...expected.makeCase, 128 | caseClause: incorrectlyPrefixed, 129 | }) 130 | }) 131 | 132 | // Partially implemented — Broken 133 | // (:return (:ok "g_rhs : Bool -> Nat -> String\ng_rhs b n") 11) 134 | it("returns the expected result for :make-lemma", async () => { 135 | const actual = await ic.makeLemma("g_rhs", 24) 136 | // The metavariable is not prefixed, which is correct, because 137 | // it goes inline. 138 | const correctlyPrefixed = codePrefix + unimplemented.makeLemma.declaration 139 | assert.deepEqual(std(actual), { 140 | ...unimplemented.makeLemma, 141 | declaration: correctlyPrefixed, 142 | }) 143 | }) 144 | 145 | it("returns the expected result for :make-with", async () => { 146 | const actual = await ic.makeWith("g_rhs", 24) 147 | // I'm not entirely sure what's happened here. 148 | // from: 149 | // g n b with (_) 150 | // g n b | with_pat = ?g_rhs_rhs 151 | // to: 152 | // > > > g n b with (_) 153 | // > > > g n b | with_pat = ?g_rhs_rhs 154 | const incorrectlyPrefixed = expected.makeWith.withClause 155 | .split("\n") 156 | .map((line, idx) => 157 | idx === 0 ? "> > > " + line : "> > > " + line.trim() 158 | ) 159 | .join("\n") 160 | assert.deepEqual(std(actual), { 161 | ...expected.makeWith, 162 | withClause: incorrectlyPrefixed, 163 | }) 164 | }) 165 | 166 | // Partially implemented — no metadata 167 | it("returns the expected result for :metavariables", async () => { 168 | const actual = await ic.metavariables(80) 169 | assert.deepEqual(std(actual), unimplemented.metavariables) 170 | }) 171 | 172 | // Unimplemented 173 | // (:write-string "print-definition: command not yet implemented. Hopefully soon!" 14) 174 | it("returns the expected result for :print-definition", async () => { 175 | const actual = await ic.printDefinition("Bool") 176 | assert.deepEqual(std(actual), unimplemented.printDefinition) 177 | }) 178 | 179 | it("returns the expected result for :proof-search", async () => { 180 | const actual = await ic.proofSearch("n_rhs", 27, []) 181 | assert.deepEqual(std(actual), expected.proofSearch) 182 | }) 183 | 184 | // Unimplemented 185 | // (:write-string "repl-completions: command not yet implemented. Hopefully soon!" 16) 186 | // (:return (:ok ()) 3) 187 | it("returns the expected result for :repl-completions", async () => { 188 | const actual = await ic.replCompletions("get") 189 | assert.deepEqual(std(actual), expected.replCompletions) 190 | }) 191 | 192 | // Partially Implemented — no metadata 193 | // (:return (:ok "Main.Cat : Type" ()) 17) 194 | it("returns the expected result for :type-of", async () => { 195 | const actual = await ic.typeOf("Cat") 196 | assert.deepEqual(std(actual), expected.typeOf) 197 | }) 198 | 199 | it("returns the expected result for :version", async () => { 200 | const actual = await ic.version() 201 | // We don’t want to tie the test to an actual version. 202 | assert.isTrue(actual.ok) 203 | }) 204 | 205 | // Unimplemented 206 | // (:write-string "who-calls: command not yet implemented. Hopefully soon!" 18) 207 | it("returns the expected result for :who-calls", async () => { 208 | const actual = await ic.whoCalls("Cat") 209 | assert.deepEqual(std(actual), unimplemented.whoCalls) 210 | }) 211 | 212 | // New V2 commands 213 | it("returns the expected result for :generate-def", async () => { 214 | const actual = await ic.generateDef("append", 29) 215 | const correctlyPrefixed = expected.generateDef.def 216 | .split("\n") 217 | .map((line) => codePrefix + line) 218 | .join("\n") 219 | assert.deepEqual(std(actual), { 220 | ...expected.generateDef, 221 | def: correctlyPrefixed, 222 | }) 223 | }) 224 | 225 | it("returns the expected result for :generate-def-next", async () => { 226 | const actual = await ic.generateDefNext() 227 | const correctlyPrefixed = expected.generateDefNext.def 228 | .split("\n") 229 | .map((line) => codePrefix + line) 230 | .join("\n") 231 | assert.deepEqual(std(actual), { 232 | ...expected.generateDefNext, 233 | def: correctlyPrefixed, 234 | }) 235 | }) 236 | 237 | it("returns the expected result for :proof-search-next", async () => { 238 | const actual = await ic.proofSearchNext() 239 | assert.deepEqual(std(actual), expected.proofSearchNext) 240 | }) 241 | 242 | it("returns the expected result for :type-at", async () => { 243 | const actual = await ic.typeAt("b", 24, 5) 244 | assert.deepEqual(std(actual), expected.typeAt) 245 | }) 246 | 247 | after(() => { 248 | proc.kill() 249 | }) 250 | }) 251 | -------------------------------------------------------------------------------- /test/client/v2-md.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { spawn, ChildProcess } from "child_process" 3 | import * as expected from "./v2-expected" 4 | import * as unimplemented from "./unimplemented" 5 | import { clean, omitKeys } from "../utils" 6 | import { IdrisClient } from "../../src/client" 7 | 8 | /** 9 | * Standardise dynamic return values to allow equality checking. 10 | * All messages ids are set to an arbitrary number, so the tests are (mostly) 11 | * order independent. And we remove the ttTerm key, which is random. 12 | */ 13 | const std = (reply: object): object => ({ 14 | ...omitKeys(reply, ["ttTerm"]), 15 | id: 8, 16 | }) 17 | 18 | describe("Running the v2 client commands on test.md", () => { 19 | let ic: IdrisClient 20 | let proc: ChildProcess 21 | 22 | before(async () => { 23 | clean() 24 | proc = spawn("idris2", ["--ide-mode", "--no-color"]) 25 | if (proc.stdin && proc.stdout) { 26 | ic = new IdrisClient(proc.stdin, proc.stdout) 27 | } 28 | }) 29 | 30 | // This test _does_ need to be first, because it sets up the internal state 31 | // of the Idris IDE process. 32 | it("returns the expected result for :load-file", async () => { 33 | const actual = await ic.loadFile("./test/resources/test-v2.md") 34 | assert.deepEqual(actual, expected.loadFile) 35 | }).timeout(10000) 36 | 37 | it("returns the expected result for :add-clause.", async () => { 38 | const actual = await ic.addClause("f", 14) 39 | assert.deepEqual(std(actual), expected.addClause) 40 | }) 41 | 42 | // Unimplemented 43 | // (:write-string "add-missing: command not yet implemented. Hopefully soon!" 3) 44 | // Also, how will this work if it won’t load the file with missing cases? 45 | it("returns the expected result for :add-missing.", async () => { 46 | const actual = await ic.addMissing("getName", 16) 47 | assert.deepEqual(std(actual), unimplemented.addMissing) 48 | }) 49 | 50 | // Unimplemented 51 | // (:write-string "apropros: command not yet implemented. Hopefully soon!" 4) 52 | it("returns the expected result for :apropos.", async () => { 53 | const actual = await ic.apropos("plus") 54 | assert.deepEqual(std(actual), unimplemented.apropos) 55 | }) 56 | 57 | // Unimplemented 58 | // (:write-string "browse-namespace: command not yet implemented. Hopefully soon!" 3) 59 | it("returns the expected result for :browse-namespace.", async () => { 60 | const actual = await ic.browseNamespace("Language.Reflection") 61 | assert.deepEqual(std(actual), unimplemented.browseNamespace) 62 | }) 63 | 64 | // Unimplemented 65 | // (:write-string "calls-who: command not yet implemented. Hopefully soon!" 6) 66 | it("returns the expected result for :calls-who.", async () => { 67 | const actual = await ic.callsWho("plusTwo") 68 | assert.deepEqual(std(actual), unimplemented.callsWho) 69 | }) 70 | 71 | it("returns the expected result for :case-split", async () => { 72 | const actual = await ic.caseSplit("n", 26) 73 | assert.deepEqual(std(actual), expected.caseSplit) 74 | }) 75 | 76 | // Partially Implemented — no metadata 77 | it("returns the expected result for :docs-for", async () => { 78 | const actual = await ic.docsFor("putStrLn", ":full") 79 | assert.deepEqual(std(actual), expected.docsFor) 80 | }) 81 | 82 | it("returns the expected result for :make-case", async () => { 83 | const actual = await ic.makeCase("g_rhs", 29) 84 | assert.deepEqual(std(actual), expected.makeCase) 85 | }) 86 | 87 | // Partially implemented — Broken 88 | // (:return (:ok "g_rhs : Bool -> Nat -> String\ng_rhs b n") 11) 89 | it("returns the expected result for :make-lemma", async () => { 90 | const actual = await ic.makeLemma("g_rhs", 29) 91 | assert.deepEqual(std(actual), unimplemented.makeLemma) 92 | }) 93 | 94 | it("returns the expected result for :make-with", async () => { 95 | const actual = await ic.makeWith("g_rhs", 29) 96 | assert.deepEqual(std(actual), expected.makeWith) 97 | }) 98 | 99 | // Partially implemented — no metadata 100 | it("returns the expected result for :metavariables", async () => { 101 | const actual = await ic.metavariables(80) 102 | assert.deepEqual(std(actual), unimplemented.metavariables) 103 | }) 104 | 105 | // Unimplemented 106 | // (:write-string "print-definition: command not yet implemented. Hopefully soon!" 14) 107 | it("returns the expected result for :print-definition", async () => { 108 | const actual = await ic.printDefinition("Bool") 109 | assert.deepEqual(std(actual), unimplemented.printDefinition) 110 | }) 111 | 112 | it("returns the expected result for :proof-search", async () => { 113 | const actual = await ic.proofSearch("n_rhs", 32, []) 114 | assert.deepEqual(std(actual), expected.proofSearch) 115 | }) 116 | 117 | // Unimplemented 118 | // (:write-string "repl-completions: command not yet implemented. Hopefully soon!" 16) 119 | // (:return (:ok ()) 3) 120 | it("returns the expected result for :repl-completions", async () => { 121 | const actual = await ic.replCompletions("get") 122 | assert.deepEqual(std(actual), expected.replCompletions) 123 | }) 124 | 125 | it("returns the expected result for :version", async () => { 126 | const actual = await ic.version() 127 | // We don’t want to tie the test to an actual version. 128 | assert.isTrue(actual.ok) 129 | }) 130 | 131 | // Unimplemented 132 | // (:write-string "who-calls: command not yet implemented. Hopefully soon!" 18) 133 | it("returns the expected result for :who-calls", async () => { 134 | const actual = await ic.whoCalls("Cat") 135 | assert.deepEqual(std(actual), unimplemented.whoCalls) 136 | }) 137 | 138 | // New V2 commands 139 | 140 | it("returns the expected result for :generate-def", async () => { 141 | const actual = await ic.generateDef("append", 34) 142 | assert.deepEqual(std(actual), expected.generateDef) 143 | }) 144 | 145 | it("returns the expected result for :generate-def-next", async () => { 146 | const actual = await ic.generateDefNext() 147 | assert.deepEqual(std(actual), expected.generateDefNext) 148 | }) 149 | 150 | it("returns the expected result for :proof-search-next", async () => { 151 | const actual = await ic.proofSearchNext() 152 | assert.deepEqual(std(actual), expected.proofSearchNext) 153 | }) 154 | 155 | it("returns the expected result for :type-at", async () => { 156 | const actual = await ic.typeAt("b", 29, 5) 157 | assert.deepEqual(std(actual), expected.typeAt) 158 | }) 159 | 160 | after(() => { 161 | proc.kill() 162 | }) 163 | }) 164 | -------------------------------------------------------------------------------- /test/parser/add-clause.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { S_Exp, RootExpr } from "../../src/s-exps" 6 | 7 | describe("Parsing :add-clause reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "f cat = ?f_rhs") 2)` 10 | const payload: S_Exp.AddClause = [":ok", "f cat = ?f_rhs"] 11 | const rootExpr: RootExpr = [":return", payload, 2] 12 | const expected: FinalReply.AddClause = { 13 | id: 2, 14 | initialClause: "f cat = ?f_rhs", 15 | ok: true, 16 | type: ":return", 17 | } 18 | 19 | const tokens = new TokenIter(sexp) 20 | const exprs = parse(tokens) as RootExpr 21 | assert.deepEqual(exprs, rootExpr) 22 | 23 | const parsed = parseReply(rootExpr, ":add-clause") 24 | assert.deepEqual(parsed, expected) 25 | }) 26 | 27 | it("can parse a failure sexp.", () => { 28 | const sexp = `(:return (:error "f not defined here") 5)` 29 | const payload: S_Exp.AddClause = [":error", "f not defined here"] 30 | const rootExpr: RootExpr = [":return", payload, 5] 31 | const expected: FinalReply.AddClause = { 32 | err: "f not defined here", 33 | id: 5, 34 | ok: false, 35 | type: ":return", 36 | } 37 | 38 | const tokens = new TokenIter(sexp) 39 | const exprs = parse(tokens) as RootExpr 40 | assert.deepEqual(exprs, rootExpr) 41 | 42 | const parsed = parseReply(rootExpr, ":add-clause") 43 | assert.deepEqual(parsed, expected) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /test/parser/add-missing.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { S_Exp, RootExpr } from "../../src/s-exps" 6 | 7 | describe("Parsing :add-missing reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "getName Sherlock = ?getName_rhs_1") 2)` 10 | const payload: S_Exp.AddMissing = [ 11 | ":ok", 12 | "getName Sherlock = ?getName_rhs_1", 13 | ] 14 | const rootExpr: RootExpr = [":return", payload, 2] 15 | const expected: FinalReply.AddMissing = { 16 | id: 2, 17 | missingClauses: "getName Sherlock = ?getName_rhs_1", 18 | ok: true, 19 | type: ":return", 20 | } 21 | 22 | const tokens = new TokenIter(sexp) 23 | const exprs = parse(tokens) as RootExpr 24 | assert.deepEqual(exprs, rootExpr) 25 | 26 | const parsed = parseReply(rootExpr, ":add-missing") 27 | assert.deepEqual(parsed, expected) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/parser/apropos.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :apropos reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "\nPrelude.Bits.b8ToBinString : Bits8 -> String\nEncode Bits8 as an 8-character binary string.\n" ((1 26 ((:name "Prelude.Bits.b8ToBinString") (:implicit :False) (:key "AQAAAAAAAAAADWI4VG9CaW5TdHJpbmcAAAAAAAAAAgAAAAAAAAAEQml0cwAAAAAAAAAHUHJlbHVkZQ==") (:decor :function) (:doc-overview "Encode Bits8 as an 8-character binary string.") (:type "Bits8 -> String") (:namespace "Prelude.Bits"))) (30 5 ((:decor :type) (:type "Type") (:doc-overview "Eight bits (unsigned)") (:name "Bits8"))) (39 6 ((:decor :type) (:type "Type") (:doc-overview "Strings in some unspecified encoding") (:name "String"))))) 2)` 10 | const payload: S_Exp.AproposOk = [ 11 | ":ok", 12 | "\nPrelude.Bits.b8ToBinString : Bits8 -> String\nEncode Bits8 as an 8-character binary string.\n", 13 | [ 14 | [ 15 | 1, 16 | 26, 17 | [ 18 | [":name", "Prelude.Bits.b8ToBinString"], 19 | [":implicit", ":False"], 20 | [ 21 | ":key", 22 | "AQAAAAAAAAAADWI4VG9CaW5TdHJpbmcAAAAAAAAAAgAAAAAAAAAEQml0cwAAAAAAAAAHUHJlbHVkZQ==", 23 | ], 24 | [":decor", ":function"], 25 | [":doc-overview", "Encode Bits8 as an 8-character binary string."], 26 | [":type", "Bits8 -> String"], 27 | [":namespace", "Prelude.Bits"], 28 | ], 29 | ], 30 | [ 31 | 30, 32 | 5, 33 | [ 34 | [":decor", ":type"], 35 | [":type", "Type"], 36 | [":doc-overview", "Eight bits (unsigned)"], 37 | [":name", "Bits8"], 38 | ], 39 | ], 40 | [ 41 | 39, 42 | 6, 43 | [ 44 | [":decor", ":type"], 45 | [":type", "Type"], 46 | [":doc-overview", "Strings in some unspecified encoding"], 47 | [":name", "String"], 48 | ], 49 | ], 50 | ], 51 | ] 52 | const rootExpr: RootExpr = [":return", payload, 2] 53 | const expected: FinalReply.Apropos = { 54 | docs: 55 | "\nPrelude.Bits.b8ToBinString : Bits8 -> String\nEncode Bits8 as an 8-character binary string.\n", 56 | metadata: [ 57 | { 58 | length: 26, 59 | metadata: { 60 | decor: ":function", 61 | docOverview: "Encode Bits8 as an 8-character binary string.", 62 | implicit: ":False", 63 | key: 64 | "AQAAAAAAAAAADWI4VG9CaW5TdHJpbmcAAAAAAAAAAgAAAAAAAAAEQml0cwAAAAAAAAAHUHJlbHVkZQ==", 65 | name: "Prelude.Bits.b8ToBinString", 66 | namespace: "Prelude.Bits", 67 | type: "Bits8 -> String", 68 | }, 69 | start: 1, 70 | }, 71 | { 72 | length: 5, 73 | metadata: { 74 | decor: ":type", 75 | docOverview: "Eight bits (unsigned)", 76 | name: "Bits8", 77 | type: "Type", 78 | }, 79 | start: 30, 80 | }, 81 | { 82 | length: 6, 83 | metadata: { 84 | decor: ":type", 85 | docOverview: "Strings in some unspecified encoding", 86 | name: "String", 87 | type: "Type", 88 | }, 89 | start: 39, 90 | }, 91 | ], 92 | id: 2, 93 | ok: true, 94 | type: ":return", 95 | } 96 | 97 | const tokens = new TokenIter(sexp) 98 | const exprs = parse(tokens) as RootExpr 99 | assert.deepEqual(exprs, rootExpr) 100 | 101 | const parsed = parseReply(rootExpr, ":apropos") 102 | assert.deepEqual(parsed, expected) 103 | }) 104 | 105 | it("can parse a failure sexp.", () => { 106 | const sexp = `(:return (:error "No results found" ()) 2)` 107 | const payload: S_Exp.AproposErr = [":error", "No results found", []] 108 | const rootExpr: RootExpr = [":return", payload, 2] 109 | const expected: FinalReply.Apropos = { 110 | err: "No results found", 111 | id: 2, 112 | ok: false, 113 | type: ":return", 114 | } 115 | 116 | const tokens = new TokenIter(sexp) 117 | const exprs = parse(tokens) as RootExpr 118 | assert.deepEqual(exprs, rootExpr) 119 | 120 | const parsed = parseReply(rootExpr, ":apropos") 121 | assert.deepEqual(parsed, expected) 122 | }) 123 | }) 124 | -------------------------------------------------------------------------------- /test/parser/browse-namespace.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :browse-namespace reply", () => { 8 | it("can parse a success sexp with submodules.", () => { 9 | // Abbreviated result from ":browseNamespace "Language.Reflection" 10 | const sexp = `(:return (:ok (("Language.Reflection.Errors" "Language.Reflection.Elab" "Language.Reflection.Elab.Tactics" "Language.Reflection.Elab.ConstructorDefn" "Language.Reflection.Elab.TyDecl" "Language.Reflection.Elab.FunDefn" "Language.Reflection.Elab.DataDefn" "Language.Reflection.Elab.Datatype" "Language.Reflection.SourceLocation" "Language.Reflection.Elab.FunArg") (("ATDouble : ArithTy" ((0 8 ((:name "Language.Reflection.ATDouble") (:implicit :False) (:key "AQAAAAAAAAAACEFURG91YmxlAAAAAAAAAAIAAAAAAAAAClJlZmxlY3Rpb24AAAAAAAAACExhbmd1YWdl") (:decor :data) (:doc-overview "") (:type "ArithTy") (:namespace "Language.Reflection"))) (11 7 ((:name "Language.Reflection.ArithTy") (:implicit :False) (:key "AQAAAAAAAAAAB0FyaXRoVHkAAAAAAAAAAgAAAAAAAAAKUmVmbGVjdGlvbgAAAAAAAAAITGFuZ3VhZ2U=") (:decor :type) (:doc-overview "") (:type "Type") (:namespace "Language.Reflection"))) (11 7 ((:tt-term "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAHQXJpdGhUeQAAAAAAAAACAAAAAAAAAApSZWZsZWN0aW9uAAAAAAAAAAhMYW5ndWFnZQ==")))))))) 2)` 11 | const payload: S_Exp.BrowseNamespaceOk = [ 12 | ":ok", 13 | [ 14 | [ 15 | "Language.Reflection.Errors", 16 | "Language.Reflection.Elab", 17 | "Language.Reflection.Elab.Tactics", 18 | "Language.Reflection.Elab.ConstructorDefn", 19 | "Language.Reflection.Elab.TyDecl", 20 | "Language.Reflection.Elab.FunDefn", 21 | "Language.Reflection.Elab.DataDefn", 22 | "Language.Reflection.Elab.Datatype", 23 | "Language.Reflection.SourceLocation", 24 | "Language.Reflection.Elab.FunArg", 25 | ], 26 | [ 27 | [ 28 | "ATDouble : ArithTy", 29 | [ 30 | [ 31 | 0, 32 | 8, 33 | [ 34 | [":name", "Language.Reflection.ATDouble"], 35 | [":implicit", ":False"], 36 | [ 37 | ":key", 38 | "AQAAAAAAAAAACEFURG91YmxlAAAAAAAAAAIAAAAAAAAAClJlZmxlY3Rpb24AAAAAAAAACExhbmd1YWdl", 39 | ], 40 | [":decor", ":data"], 41 | [":doc-overview", ""], 42 | [":type", "ArithTy"], 43 | [":namespace", "Language.Reflection"], 44 | ], 45 | ], 46 | [ 47 | 11, 48 | 7, 49 | [ 50 | [":name", "Language.Reflection.ArithTy"], 51 | [":implicit", ":False"], 52 | [ 53 | ":key", 54 | "AQAAAAAAAAAAB0FyaXRoVHkAAAAAAAAAAgAAAAAAAAAKUmVmbGVjdGlvbgAAAAAAAAAITGFuZ3VhZ2U=", 55 | ], 56 | [":decor", ":type"], 57 | [":doc-overview", ""], 58 | [":type", "Type"], 59 | [":namespace", "Language.Reflection"], 60 | ], 61 | ], 62 | [ 63 | 11, 64 | 7, 65 | [ 66 | [ 67 | ":tt-term", 68 | "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAHQXJpdGhUeQAAAAAAAAACAAAAAAAAAApSZWZsZWN0aW9uAAAAAAAAAAhMYW5ndWFnZQ==", 69 | ], 70 | ], 71 | ], 72 | ], 73 | ], 74 | ], 75 | ], 76 | ] 77 | 78 | const rootExpr: RootExpr = [":return", payload, 2] 79 | const expected: FinalReply.BrowseNamespace = { 80 | subModules: [ 81 | "Language.Reflection.Errors", 82 | "Language.Reflection.Elab", 83 | "Language.Reflection.Elab.Tactics", 84 | "Language.Reflection.Elab.ConstructorDefn", 85 | "Language.Reflection.Elab.TyDecl", 86 | "Language.Reflection.Elab.FunDefn", 87 | "Language.Reflection.Elab.DataDefn", 88 | "Language.Reflection.Elab.Datatype", 89 | "Language.Reflection.SourceLocation", 90 | "Language.Reflection.Elab.FunArg", 91 | ], 92 | declarations: [ 93 | { 94 | name: "ATDouble : ArithTy", 95 | metadata: [ 96 | { 97 | start: 0, 98 | length: 8, 99 | metadata: { 100 | name: "Language.Reflection.ATDouble", 101 | implicit: ":False", 102 | key: 103 | "AQAAAAAAAAAACEFURG91YmxlAAAAAAAAAAIAAAAAAAAAClJlZmxlY3Rpb24AAAAAAAAACExhbmd1YWdl", 104 | decor: ":data", 105 | docOverview: "", 106 | type: "ArithTy", 107 | namespace: "Language.Reflection", 108 | }, 109 | }, 110 | { 111 | start: 11, 112 | length: 7, 113 | metadata: { 114 | name: "Language.Reflection.ArithTy", 115 | implicit: ":False", 116 | key: 117 | "AQAAAAAAAAAAB0FyaXRoVHkAAAAAAAAAAgAAAAAAAAAKUmVmbGVjdGlvbgAAAAAAAAAITGFuZ3VhZ2U=", 118 | decor: ":type", 119 | docOverview: "", 120 | type: "Type", 121 | namespace: "Language.Reflection", 122 | ttTerm: "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAHQXJpdGhUeQAAAAAAAAACAAAAAAAAAApSZWZsZWN0aW9uAAAAAAAAAAhMYW5ndWFnZQ==", 123 | }, 124 | }, 125 | ], 126 | }, 127 | ], 128 | id: 2, 129 | ok: true, 130 | type: ":return", 131 | } 132 | 133 | const tokens = new TokenIter(sexp) 134 | const exprs = parse(tokens) as RootExpr 135 | assert.deepEqual(exprs, rootExpr) 136 | 137 | const parsed = parseReply(rootExpr, ":browse-namespace") 138 | assert.deepEqual(parsed, expected) 139 | }) 140 | 141 | it("can parse a success sexp with no submodules.", () => { 142 | // Abbreviated result from ":browseNamespace "Prelude.Bool" 143 | const sexp = `(:return (:ok (() (("Bool : Type" ((0 4 ((:name "Prelude.Bool.Bool") (:implicit :False) (:key "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==") (:decor :type) (:doc-overview "Boolean Data Type") (:type "Type") (:namespace "Prelude.Bool"))) (7 4 ((:decor :type) (:type "Type") (:doc-overview "The type of types") (:name "Type"))) (7 4 ((:tt-term "AAAAAAAAAAAHAAAAAAAAAAASLi9QcmVsdWRlL0Jvb2wuaWRyAAAAAAAAABQ=")))))))) 2)` 144 | const payload: S_Exp.BrowseNamespaceOk = [ 145 | ":ok", 146 | [ 147 | [], 148 | [ 149 | [ 150 | "Bool : Type", 151 | [ 152 | [ 153 | 0, 154 | 4, 155 | [ 156 | [":name", "Prelude.Bool.Bool"], 157 | [":implicit", ":False"], 158 | [ 159 | ":key", 160 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 161 | ], 162 | [":decor", ":type"], 163 | [":doc-overview", "Boolean Data Type"], 164 | [":type", "Type"], 165 | [":namespace", "Prelude.Bool"], 166 | ], 167 | ], 168 | [ 169 | 7, 170 | 4, 171 | [ 172 | [":decor", ":type"], 173 | [":type", "Type"], 174 | [":doc-overview", "The type of types"], 175 | [":name", "Type"], 176 | ], 177 | ], 178 | [ 179 | 7, 180 | 4, 181 | [ 182 | [ 183 | ":tt-term", 184 | "AAAAAAAAAAAHAAAAAAAAAAASLi9QcmVsdWRlL0Jvb2wuaWRyAAAAAAAAABQ=", 185 | ], 186 | ], 187 | ], 188 | ], 189 | ], 190 | ], 191 | ], 192 | ] 193 | 194 | const rootExpr: RootExpr = [":return", payload, 2] 195 | const expected: FinalReply.BrowseNamespace = { 196 | subModules: [], 197 | declarations: [ 198 | { 199 | name: "Bool : Type", 200 | metadata: [ 201 | { 202 | length: 4, 203 | metadata: { 204 | decor: ":type", 205 | docOverview: "Boolean Data Type", 206 | implicit: ":False", 207 | key: 208 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 209 | name: "Prelude.Bool.Bool", 210 | namespace: "Prelude.Bool", 211 | type: "Type", 212 | }, 213 | start: 0, 214 | }, 215 | { 216 | length: 4, 217 | metadata: { 218 | decor: ":type", 219 | docOverview: "The type of types", 220 | name: "Type", 221 | ttTerm: "AAAAAAAAAAAHAAAAAAAAAAASLi9QcmVsdWRlL0Jvb2wuaWRyAAAAAAAAABQ=", 222 | type: "Type", 223 | }, 224 | start: 7, 225 | }, 226 | ], 227 | }, 228 | ], 229 | id: 2, 230 | ok: true, 231 | type: ":return", 232 | } 233 | 234 | const tokens = new TokenIter(sexp) 235 | const exprs = parse(tokens) as RootExpr 236 | assert.deepEqual(exprs, rootExpr) 237 | 238 | const parsed = parseReply(rootExpr, ":browse-namespace") 239 | assert.deepEqual(parsed, expected) 240 | }) 241 | 242 | it("can parse a failure sexp.", () => { 243 | const sexp = `(:return (:error "Invalid or empty namespace") 2)` 244 | const payload: S_Exp.BrowseNamespaceErr = [ 245 | ":error", 246 | "Invalid or empty namespace", 247 | ] 248 | const rootExpr: RootExpr = [":return", payload, 2] 249 | const expected: FinalReply.BrowseNamespace = { 250 | err: "Invalid or empty namespace", 251 | id: 2, 252 | ok: false, 253 | type: ":return", 254 | } 255 | 256 | const tokens = new TokenIter(sexp) 257 | const exprs = parse(tokens) as RootExpr 258 | assert.deepEqual(exprs, rootExpr) 259 | 260 | const parsed = parseReply(rootExpr, ":browse-namespace") 261 | assert.deepEqual(parsed, expected) 262 | }) 263 | }) 264 | -------------------------------------------------------------------------------- /test/parser/calls-who.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :calls-who reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok ((("Example.plusTwo" ((0 15 ((:name "Example.plusTwo") (:implicit :False) (:key "AQAAAAAAAAAAB3BsdXNUd28AAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==") (:decor :function) (:doc-overview "") (:type "Nat -> Nat") (:namespace "Example"))))) (("Prelude.Nat.Nat" ((0 15 ((:name "Prelude.Nat.Nat") (:implicit :False) (:key "AQAAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=") (:decor :type) (:doc-overview "Natural numbers: unbounded, unsigned integers\nwhich can be pattern matched.") (:type "Type") (:namespace "Prelude.Nat"))))) ("Prelude.Nat.plus" ((0 16 ((:name "Prelude.Nat.plus") (:implicit :False) (:key "AQAAAAAAAAAABHBsdXMAAAAAAAAAAgAAAAAAAAADTmF0AAAAAAAAAAdQcmVsdWRl") (:decor :function) (:doc-overview "Add two natural numbers.") (:type "Nat -> Nat -> Nat") (:namespace "Prelude.Nat"))))) ("Prelude.Interfaces.fromInteger" ((0 30 ((:name "Prelude.Interfaces.fromInteger") (:implicit :False) (:key "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=") (:decor :function) (:doc-overview "Conversion from Integer.") (:type "Num ty => Integer -> ty") (:namespace "Prelude.Interfaces"))))) ("Prelude.Nat.Nat implementation of Prelude.Interfaces.Num" ((0 56 ((:name "Prelude.Nat.Nat implementation of Prelude.Interfaces.Num") (:implicit :False) (:key "AQMCAQAAAAAAAAAAA051bQAAAAAAAAACAAAAAAAAAApJbnRlcmZhY2VzAAAAAAAAAAdQcmVsdWRlAAAAAAAAAAEAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=") (:decor :function) (:doc-overview "") (:type "Num Nat") (:namespace "Prelude.Nat"))))))))) 2)` 10 | const payload: S_Exp.CallsWho = [ 11 | ":ok", 12 | [ 13 | [ 14 | [ 15 | "Example.plusTwo", 16 | [ 17 | [ 18 | 0, 19 | 15, 20 | [ 21 | [":name", "Example.plusTwo"], 22 | [":implicit", ":False"], 23 | [ 24 | ":key", 25 | "AQAAAAAAAAAAB3BsdXNUd28AAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 26 | ], 27 | [":decor", ":function"], 28 | [":doc-overview", ""], 29 | [":type", "Nat -> Nat"], 30 | [":namespace", "Example"], 31 | ], 32 | ], 33 | ], 34 | ], 35 | [ 36 | [ 37 | "Prelude.Nat.Nat", 38 | [ 39 | [ 40 | 0, 41 | 15, 42 | [ 43 | [":name", "Prelude.Nat.Nat"], 44 | [":implicit", ":False"], 45 | [ 46 | ":key", 47 | "AQAAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=", 48 | ], 49 | [":decor", ":type"], 50 | [ 51 | ":doc-overview", 52 | "Natural numbers: unbounded, unsigned integers\nwhich can be pattern matched.", 53 | ], 54 | [":type", "Type"], 55 | [":namespace", "Prelude.Nat"], 56 | ], 57 | ], 58 | ], 59 | ], 60 | [ 61 | "Prelude.Nat.plus", 62 | [ 63 | [ 64 | 0, 65 | 16, 66 | [ 67 | [":name", "Prelude.Nat.plus"], 68 | [":implicit", ":False"], 69 | [ 70 | ":key", 71 | "AQAAAAAAAAAABHBsdXMAAAAAAAAAAgAAAAAAAAADTmF0AAAAAAAAAAdQcmVsdWRl", 72 | ], 73 | [":decor", ":function"], 74 | [":doc-overview", "Add two natural numbers."], 75 | [":type", "Nat -> Nat -> Nat"], 76 | [":namespace", "Prelude.Nat"], 77 | ], 78 | ], 79 | ], 80 | ], 81 | [ 82 | "Prelude.Interfaces.fromInteger", 83 | [ 84 | [ 85 | 0, 86 | 30, 87 | [ 88 | [":name", "Prelude.Interfaces.fromInteger"], 89 | [":implicit", ":False"], 90 | [ 91 | ":key", 92 | "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=", 93 | ], 94 | [":decor", ":function"], 95 | [":doc-overview", "Conversion from Integer."], 96 | [":type", "Num ty => Integer -> ty"], 97 | [":namespace", "Prelude.Interfaces"], 98 | ], 99 | ], 100 | ], 101 | ], 102 | [ 103 | "Prelude.Nat.Nat implementation of Prelude.Interfaces.Num", 104 | [ 105 | [ 106 | 0, 107 | 56, 108 | [ 109 | [ 110 | ":name", 111 | "Prelude.Nat.Nat implementation of Prelude.Interfaces.Num", 112 | ], 113 | [":implicit", ":False"], 114 | [ 115 | ":key", 116 | "AQMCAQAAAAAAAAAAA051bQAAAAAAAAACAAAAAAAAAApJbnRlcmZhY2VzAAAAAAAAAAdQcmVsdWRlAAAAAAAAAAEAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=", 117 | ], 118 | [":decor", ":function"], 119 | [":doc-overview", ""], 120 | [":type", "Num Nat"], 121 | [":namespace", "Prelude.Nat"], 122 | ], 123 | ], 124 | ], 125 | ], 126 | ], 127 | ], 128 | ], 129 | ] 130 | const rootExpr: RootExpr = [":return", payload, 2] 131 | const expected: FinalReply.CallsWho = { 132 | caller: { 133 | name: "Example.plusTwo", 134 | metadata: [ 135 | { 136 | start: 0, 137 | length: 15, 138 | metadata: { 139 | name: "Example.plusTwo", 140 | implicit: ":False", 141 | key: "AQAAAAAAAAAAB3BsdXNUd28AAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 142 | decor: ":function", 143 | docOverview: "", 144 | type: "Nat -> Nat", 145 | namespace: "Example", 146 | }, 147 | }, 148 | ], 149 | }, 150 | references: [ 151 | { 152 | name: "Prelude.Nat.Nat", 153 | metadata: [ 154 | { 155 | start: 0, 156 | length: 15, 157 | metadata: { 158 | name: "Prelude.Nat.Nat", 159 | implicit: ":False", 160 | key: 161 | "AQAAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=", 162 | decor: ":type", 163 | docOverview: 164 | "Natural numbers: unbounded, unsigned integers\nwhich can be pattern matched.", 165 | type: "Type", 166 | namespace: "Prelude.Nat", 167 | }, 168 | }, 169 | ], 170 | }, 171 | { 172 | name: "Prelude.Nat.plus", 173 | metadata: [ 174 | { 175 | start: 0, 176 | length: 16, 177 | metadata: { 178 | name: "Prelude.Nat.plus", 179 | implicit: ":False", 180 | key: 181 | "AQAAAAAAAAAABHBsdXMAAAAAAAAAAgAAAAAAAAADTmF0AAAAAAAAAAdQcmVsdWRl", 182 | decor: ":function", 183 | docOverview: "Add two natural numbers.", 184 | type: "Nat -> Nat -> Nat", 185 | namespace: "Prelude.Nat", 186 | }, 187 | }, 188 | ], 189 | }, 190 | { 191 | name: "Prelude.Interfaces.fromInteger", 192 | metadata: [ 193 | { 194 | start: 0, 195 | length: 30, 196 | metadata: { 197 | name: "Prelude.Interfaces.fromInteger", 198 | implicit: ":False", 199 | key: 200 | "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=", 201 | decor: ":function", 202 | docOverview: "Conversion from Integer.", 203 | type: "Num ty => Integer -> ty", 204 | namespace: "Prelude.Interfaces", 205 | }, 206 | }, 207 | ], 208 | }, 209 | { 210 | name: "Prelude.Nat.Nat implementation of Prelude.Interfaces.Num", 211 | metadata: [ 212 | { 213 | start: 0, 214 | length: 56, 215 | metadata: { 216 | name: 217 | "Prelude.Nat.Nat implementation of Prelude.Interfaces.Num", 218 | implicit: ":False", 219 | key: 220 | "AQMCAQAAAAAAAAAAA051bQAAAAAAAAACAAAAAAAAAApJbnRlcmZhY2VzAAAAAAAAAAdQcmVsdWRlAAAAAAAAAAEAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=", 221 | decor: ":function", 222 | docOverview: "", 223 | type: "Num Nat", 224 | namespace: "Prelude.Nat", 225 | }, 226 | }, 227 | ], 228 | }, 229 | ], 230 | id: 2, 231 | ok: true, 232 | type: ":return", 233 | } 234 | 235 | const tokens = new TokenIter(sexp) 236 | const exprs = parse(tokens) as RootExpr 237 | assert.deepEqual(exprs, rootExpr) 238 | 239 | const parsed = parseReply(rootExpr, ":calls-who") 240 | assert.deepEqual(parsed, expected) 241 | }) 242 | 243 | it("can parse an empty sexp.", () => { 244 | const sexp = `(:return (:ok ()) 2)` 245 | const payload: S_Exp.CallsWho = [":ok", []] 246 | const rootExpr: RootExpr = [":return", payload, 2] 247 | const expected: FinalReply.CallsWho = { 248 | caller: null, 249 | references: [], 250 | id: 2, 251 | ok: true, 252 | type: ":return", 253 | } 254 | 255 | const tokens = new TokenIter(sexp) 256 | const exprs = parse(tokens) as RootExpr 257 | assert.deepEqual(exprs, rootExpr) 258 | 259 | const parsed = parseReply(rootExpr, ":calls-who") 260 | assert.deepEqual(parsed, expected) 261 | }) 262 | }) 263 | -------------------------------------------------------------------------------- /test/parser/case-split.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :case-split reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "plusTwo Z = plus 2 n\nplusTwo (S k) = plus 2 n\n") 2)` 10 | const payload: S_Exp.CaseSplitOk = [ 11 | ":ok", 12 | "plusTwo Z = plus 2 n\nplusTwo (S k) = plus 2 n\n", 13 | ] 14 | const rootExpr: RootExpr = [":return", payload, 2] 15 | const expected: FinalReply.CaseSplit = { 16 | caseClause: "plusTwo Z = plus 2 n\nplusTwo (S k) = plus 2 n\n", 17 | id: 2, 18 | ok: true, 19 | type: ":return", 20 | } 21 | 22 | const tokens = new TokenIter(sexp) 23 | const exprs = parse(tokens) as RootExpr 24 | assert.deepEqual(exprs, rootExpr) 25 | 26 | const parsed = parseReply(rootExpr, ":case-split") 27 | assert.deepEqual(parsed, expected) 28 | }) 29 | 30 | it("can parse a failure sexp.", () => { 31 | const sexp = `(:return (:error "Elaborating {__infer_0} arg {ival_0}: Internal error: \\"Unelaboratable syntactic form (Example.plusTwo : (n : Nat) ->\nNat)\\"") 2)` 32 | const payload: S_Exp.CaseSplitErr = [ 33 | ":error", 34 | `Elaborating {__infer_0} arg {ival_0}: Internal error: "Unelaboratable syntactic form (Example.plusTwo : (n : Nat) ->\nNat)"`, 35 | ] 36 | const rootExpr: RootExpr = [":return", payload, 2] 37 | const expected: FinalReply.CaseSplit = { 38 | err: `Elaborating {__infer_0} arg {ival_0}: Internal error: "Unelaboratable syntactic form (Example.plusTwo : (n : Nat) ->\nNat)"`, 39 | id: 2, 40 | ok: false, 41 | type: ":return", 42 | } 43 | 44 | const tokens = new TokenIter(sexp) 45 | const exprs = parse(tokens) as RootExpr 46 | assert.deepEqual(exprs, rootExpr) 47 | 48 | const parsed = parseReply(rootExpr, ":case-split") 49 | assert.deepEqual(parsed, expected) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/parser/docs-for.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { TokenIter } from "../../src/parser/lexer" 4 | import { RootExpr, S_Exp } from "../../src/s-exps" 5 | import { FinalReply } from "../../src/reply" 6 | 7 | describe("Parsing :case-split reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "Prelude.Bits.b8ToBinString : Bits8 -> String\n Encode Bits8 as an 8-character binary string.\n \n The function is: Total & public export" ((0 26 ((:name "Prelude.Bits.b8ToBinString") (:implicit :False) (:key "AQAAAAAAAAAADWI4VG9CaW5TdHJpbmcAAAAAAAAAAgAAAAAAAAAEQml0cwAAAAAAAAAHUHJlbHVkZQ==") (:decor :function) (:doc-overview "Encode Bits8 as an 8-character binary string.") (:type "Bits8 -> String") (:namespace "Prelude.Bits"))) (29 5 ((:decor :type) (:type "Type") (:doc-overview "Eight bits (unsigned)") (:name "Bits8"))) (38 6 ((:decor :type) (:type "Type") (:doc-overview "Strings in some unspecified encoding") (:name "String"))))) 2)` 10 | const payload: S_Exp.DocsForOk = [ 11 | ":ok", 12 | "Prelude.Bits.b8ToBinString : Bits8 -> String\n Encode Bits8 as an 8-character binary string.\n \n The function is: Total & public export", 13 | [ 14 | [ 15 | 0, 16 | 26, 17 | [ 18 | [":name", "Prelude.Bits.b8ToBinString"], 19 | [":implicit", ":False"], 20 | [ 21 | ":key", 22 | "AQAAAAAAAAAADWI4VG9CaW5TdHJpbmcAAAAAAAAAAgAAAAAAAAAEQml0cwAAAAAAAAAHUHJlbHVkZQ==", 23 | ], 24 | [":decor", ":function"], 25 | [":doc-overview", "Encode Bits8 as an 8-character binary string."], 26 | [":type", "Bits8 -> String"], 27 | [":namespace", "Prelude.Bits"], 28 | ], 29 | ], 30 | [ 31 | 29, 32 | 5, 33 | [ 34 | [":decor", ":type"], 35 | [":type", "Type"], 36 | [":doc-overview", "Eight bits (unsigned)"], 37 | [":name", "Bits8"], 38 | ], 39 | ], 40 | [ 41 | 38, 42 | 6, 43 | [ 44 | [":decor", ":type"], 45 | [":type", "Type"], 46 | [":doc-overview", "Strings in some unspecified encoding"], 47 | [":name", "String"], 48 | ], 49 | ], 50 | ], 51 | ] 52 | const rootExpr: RootExpr = [":return", payload, 2] 53 | const expected: FinalReply.DocsFor = { 54 | docs: 55 | "Prelude.Bits.b8ToBinString : Bits8 -> String\n Encode Bits8 as an 8-character binary string.\n \n The function is: Total & public export", 56 | metadata: [ 57 | { 58 | start: 0, 59 | length: 26, 60 | metadata: { 61 | name: "Prelude.Bits.b8ToBinString", 62 | implicit: ":False", 63 | key: 64 | "AQAAAAAAAAAADWI4VG9CaW5TdHJpbmcAAAAAAAAAAgAAAAAAAAAEQml0cwAAAAAAAAAHUHJlbHVkZQ==", 65 | decor: ":function", 66 | docOverview: "Encode Bits8 as an 8-character binary string.", 67 | type: "Bits8 -> String", 68 | namespace: "Prelude.Bits", 69 | }, 70 | }, 71 | { 72 | start: 29, 73 | length: 5, 74 | metadata: { 75 | decor: ":type", 76 | type: "Type", 77 | docOverview: "Eight bits (unsigned)", 78 | name: "Bits8", 79 | }, 80 | }, 81 | { 82 | start: 38, 83 | length: 6, 84 | metadata: { 85 | decor: ":type", 86 | type: "Type", 87 | docOverview: "Strings in some unspecified encoding", 88 | name: "String", 89 | }, 90 | }, 91 | ], 92 | id: 2, 93 | ok: true, 94 | type: ":return", 95 | } 96 | 97 | const tokens = new TokenIter(sexp) 98 | const exprs = parse(tokens) as RootExpr 99 | assert.deepEqual(exprs, rootExpr) 100 | 101 | const parsed = parseReply(rootExpr, ":docs-for") 102 | assert.deepEqual(parsed, expected) 103 | }) 104 | 105 | it("can parse a failure sexp.", () => { 106 | const sexp = `(:return (:error "No documentation for Blue Notebook #10") 2)` 107 | const payload: S_Exp.DocsForErr = [ 108 | ":error", 109 | "No documentation for Blue Notebook #10", 110 | ] 111 | const rootExpr: RootExpr = [":return", payload, 2] 112 | const expected: FinalReply.DocsFor = { 113 | err: "No documentation for Blue Notebook #10", 114 | id: 2, 115 | ok: false, 116 | type: ":return", 117 | } 118 | 119 | const tokens = new TokenIter(sexp) 120 | const exprs = parse(tokens) as RootExpr 121 | assert.deepEqual(exprs, rootExpr) 122 | 123 | const parsed = parseReply(rootExpr, ":docs-for") 124 | assert.deepEqual(parsed, expected) 125 | }) 126 | }) 127 | -------------------------------------------------------------------------------- /test/parser/generate-def-next.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { S_Exp, RootExpr } from "../../src/s-exps" 6 | 7 | // Idris 2 only. 8 | describe("Parsing :generate-def reply", () => { 9 | it("can parse a success sexp.", () => { 10 | const sexp = `(:return (:ok "append [] ys = ys\nappend (x :: xs) [] = x :: append xs []\nappend (x :: xs) (y :: ys) = x :: append xs (y :: ys)") 3)` 11 | const payload: S_Exp.GenerateDef = [ 12 | ":ok", 13 | "append [] ys = ys\nappend (x :: xs) [] = x :: append xs []\nappend (x :: xs) (y :: ys) = x :: append xs (y :: ys)", 14 | ] 15 | const rootExpr: RootExpr = [":return", payload, 3] 16 | const expected: FinalReply.GenerateDef = { 17 | def: 18 | "append [] ys = ys\nappend (x :: xs) [] = x :: append xs []\nappend (x :: xs) (y :: ys) = x :: append xs (y :: ys)", 19 | id: 3, 20 | ok: true, 21 | type: ":return", 22 | } 23 | 24 | const tokens = new TokenIter(sexp) 25 | const exprs = parse(tokens) as RootExpr 26 | assert.deepEqual(exprs, rootExpr) 27 | 28 | const parsed = parseReply(rootExpr, ":generate-def-next") 29 | assert.deepEqual(parsed, expected) 30 | }) 31 | 32 | it("can parse a failure sexp.", () => { 33 | const sexp = `(:return (:error "No more results") 16)` 34 | const payload: S_Exp.GenerateDef = [":error", "No more results"] 35 | const rootExpr: RootExpr = [":return", payload, 16] 36 | const expected: FinalReply.GenerateDef = { 37 | err: "No more results", 38 | id: 16, 39 | ok: false, 40 | type: ":return", 41 | } 42 | 43 | const tokens = new TokenIter(sexp) 44 | const exprs = parse(tokens) as RootExpr 45 | assert.deepEqual(exprs, rootExpr) 46 | 47 | const parsed = parseReply(rootExpr, ":generate-def-next") 48 | assert.deepEqual(parsed, expected) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /test/parser/generate-def.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { S_Exp, RootExpr } from "../../src/s-exps" 6 | 7 | // Idris 2 only. 8 | describe("Parsing :generate-def reply", () => { 9 | it("can parse a success sexp.", () => { 10 | const sexp = `(:return (:ok "append [] ys = ys\nappend (x :: xs) ys = x :: append xs ys") 2)` 11 | const payload: S_Exp.GenerateDef = [ 12 | ":ok", 13 | "append [] ys = ys\nappend (x :: xs) ys = x :: append xs ys", 14 | ] 15 | const rootExpr: RootExpr = [":return", payload, 2] 16 | const expected: FinalReply.GenerateDef = { 17 | def: "append [] ys = ys\nappend (x :: xs) ys = x :: append xs ys", 18 | id: 2, 19 | ok: true, 20 | type: ":return", 21 | } 22 | 23 | const tokens = new TokenIter(sexp) 24 | const exprs = parse(tokens) as RootExpr 25 | assert.deepEqual(exprs, rootExpr) 26 | 27 | const parsed = parseReply(rootExpr, ":generate-def") 28 | assert.deepEqual(parsed, expected) 29 | }) 30 | 31 | it("can parse a not-found failure sexp.", () => { 32 | const sexp = `(:return (:error "Can't find declaration for append on line 5") 2)` 33 | const payload: S_Exp.GenerateDefErr = [ 34 | ":error", 35 | "Can't find declaration for append on line 5", 36 | ] 37 | const rootExpr: RootExpr = [":return", payload, 2] 38 | const expected: FinalReply.GenerateDef = { 39 | err: "Can't find declaration for append on line 5", 40 | id: 2, 41 | ok: false, 42 | type: ":return", 43 | } 44 | 45 | const tokens = new TokenIter(sexp) 46 | const exprs = parse(tokens) as RootExpr 47 | assert.deepEqual(exprs, rootExpr) 48 | 49 | const parsed = parseReply(rootExpr, ":generate-def") 50 | assert.deepEqual(parsed, expected) 51 | }) 52 | 53 | it("can parse a already-defined failure sexp.", () => { 54 | const sexp = `(:return (:error "Already defined") 2)` 55 | const payload: S_Exp.GenerateDefErr = [":error", "Already defined"] 56 | const rootExpr: RootExpr = [":return", payload, 2] 57 | const expected: FinalReply.GenerateDef = { 58 | err: "Already defined", 59 | id: 2, 60 | ok: false, 61 | type: ":return", 62 | } 63 | 64 | const tokens = new TokenIter(sexp) 65 | const exprs = parse(tokens) as RootExpr 66 | assert.deepEqual(exprs, rootExpr) 67 | 68 | const parsed = parseReply(rootExpr, ":generate-def") 69 | assert.deepEqual(parsed, expected) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /test/parser/interpret.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { TokenIter } from "../../src/parser/lexer" 4 | import { RootExpr, S_Exp } from "../../src/s-exps" 5 | import { FinalReply, OutputReply } from "../../src/reply" 6 | 7 | describe("Parsing :interpret reply", () => { 8 | it("can parse the interpret source output of `2 * 2`", () => { 9 | const sexp = `(:output (:ok (:highlight-source ((((:filename "(input)") (:start 1 1) (:end 1 1)) ((:name "Prelude.Interfaces.fromInteger") (:implicit :False) (:key "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=") (:decor :function) (:doc-overview "Conversion from Integer.") (:type "Num ty => Integer -> ty") (:namespace "Prelude.Interfaces"))) (((:filename "(input)") (:start 1 3) (:end 1 3)) ((:name "Prelude.Interfaces.*") (:implicit :False) (:key "AQAAAAAAAAAAASoAAAAAAAAAAgAAAAAAAAAKSW50ZXJmYWNlcwAAAAAAAAAHUHJlbHVkZQ==") (:decor :function) (:doc-overview "") (:type "Num ty => ty -> ty -> ty") (:namespace "Prelude.Interfaces"))) (((:filename "(input)") (:start 1 5) (:end 1 5)) ((:name "Prelude.Interfaces.fromInteger") (:implicit :False) (:key "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=") (:decor :function) (:doc-overview "Conversion from Integer.") (:type "Num ty => Integer -> ty") (:namespace "Prelude.Interfaces")))))) 2)` 10 | const payload: S_Exp.Output = [ 11 | ":ok", 12 | [ 13 | ":highlight-source", 14 | [ 15 | [ 16 | [ 17 | [":filename", "(input)"], 18 | [":start", 1, 1], 19 | [":end", 1, 1], 20 | ], 21 | [ 22 | [":name", "Prelude.Interfaces.fromInteger"], 23 | [":implicit", ":False"], 24 | [ 25 | ":key", 26 | "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=", 27 | ], 28 | [":decor", ":function"], 29 | [":doc-overview", "Conversion from Integer."], 30 | [":type", "Num ty => Integer -> ty"], 31 | [":namespace", "Prelude.Interfaces"], 32 | ], 33 | ], 34 | [ 35 | [ 36 | [":filename", "(input)"], 37 | [":start", 1, 3], 38 | [":end", 1, 3], 39 | ], 40 | [ 41 | [":name", "Prelude.Interfaces.*"], 42 | [":implicit", ":False"], 43 | [ 44 | ":key", 45 | "AQAAAAAAAAAAASoAAAAAAAAAAgAAAAAAAAAKSW50ZXJmYWNlcwAAAAAAAAAHUHJlbHVkZQ==", 46 | ], 47 | [":decor", ":function"], 48 | [":doc-overview", ""], 49 | [":type", "Num ty => ty -> ty -> ty"], 50 | [":namespace", "Prelude.Interfaces"], 51 | ], 52 | ], 53 | [ 54 | [ 55 | [":filename", "(input)"], 56 | [":start", 1, 5], 57 | [":end", 1, 5], 58 | ], 59 | [ 60 | [":name", "Prelude.Interfaces.fromInteger"], 61 | [":implicit", ":False"], 62 | [ 63 | ":key", 64 | "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=", 65 | ], 66 | [":decor", ":function"], 67 | [":doc-overview", "Conversion from Integer."], 68 | [":type", "Num ty => Integer -> ty"], 69 | [":namespace", "Prelude.Interfaces"], 70 | ], 71 | ], 72 | ], 73 | ], 74 | ] 75 | const rootExpr: RootExpr = [":output", payload, 2] 76 | const expected: OutputReply = { 77 | ok: [ 78 | { 79 | end: { 80 | column: 1, 81 | line: 1, 82 | }, 83 | filename: "(input)", 84 | metadata: { 85 | decor: ":function", 86 | docOverview: "Conversion from Integer.", 87 | implicit: ":False", 88 | key: 89 | "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=", 90 | name: "Prelude.Interfaces.fromInteger", 91 | namespace: "Prelude.Interfaces", 92 | type: "Num ty => Integer -> ty", 93 | }, 94 | start: { 95 | column: 1, 96 | line: 1, 97 | }, 98 | }, 99 | { 100 | end: { 101 | column: 3, 102 | line: 1, 103 | }, 104 | filename: "(input)", 105 | metadata: { 106 | decor: ":function", 107 | docOverview: "", 108 | implicit: ":False", 109 | key: 110 | "AQAAAAAAAAAAASoAAAAAAAAAAgAAAAAAAAAKSW50ZXJmYWNlcwAAAAAAAAAHUHJlbHVkZQ==", 111 | name: "Prelude.Interfaces.*", 112 | namespace: "Prelude.Interfaces", 113 | type: "Num ty => ty -> ty -> ty", 114 | }, 115 | start: { 116 | column: 3, 117 | line: 1, 118 | }, 119 | }, 120 | { 121 | end: { 122 | column: 5, 123 | line: 1, 124 | }, 125 | filename: "(input)", 126 | metadata: { 127 | decor: ":function", 128 | docOverview: "Conversion from Integer.", 129 | implicit: ":False", 130 | key: 131 | "AQAAAAAAAAAAC2Zyb21JbnRlZ2VyAAAAAAAAAAIAAAAAAAAACkludGVyZmFjZXMAAAAAAAAAB1ByZWx1ZGU=", 132 | name: "Prelude.Interfaces.fromInteger", 133 | namespace: "Prelude.Interfaces", 134 | type: "Num ty => Integer -> ty", 135 | }, 136 | start: { 137 | column: 5, 138 | line: 1, 139 | }, 140 | }, 141 | ], 142 | id: 2, 143 | type: ":output", 144 | } 145 | 146 | const tokens = new TokenIter(sexp) 147 | const exprs = parse(tokens) as RootExpr 148 | assert.deepEqual(exprs, rootExpr) 149 | 150 | const parsed = parseReply(rootExpr, ":interpret") 151 | assert.deepEqual(parsed, expected) 152 | }) 153 | 154 | it("can parse a success sexp.", () => { 155 | const sexp = `(:return (:ok "4 : Integer" ((0 1 ((:decor :data) (:type "Integer") (:doc-overview "An arbitrary-precision integer") (:name "4"))) (0 1 ((:tt-term "AAAAAAAAAAAEAQAAAAAE"))) (4 7 ((:decor :type) (:type "Type") (:doc-overview "Arbitrary-precision integers") (:name "Integer"))) (4 7 ((:tt-term "AAAAAAAAAAAECg=="))))) 2)` 156 | const payload: S_Exp.InterpretOk = [ 157 | ":ok", 158 | "4 : Integer", 159 | [ 160 | [ 161 | 0, 162 | 1, 163 | [ 164 | [":decor", ":data"], 165 | [":type", "Integer"], 166 | [":doc-overview", "An arbitrary-precision integer"], 167 | [":name", "4"], 168 | ], 169 | ], 170 | [0, 1, [[":tt-term", "AAAAAAAAAAAEAQAAAAAE"]]], 171 | [ 172 | 4, 173 | 7, 174 | [ 175 | [":decor", ":type"], 176 | [":type", "Type"], 177 | [":doc-overview", "Arbitrary-precision integers"], 178 | [":name", "Integer"], 179 | ], 180 | ], 181 | [4, 7, [[":tt-term", "AAAAAAAAAAAECg=="]]], 182 | ], 183 | ] 184 | const rootExpr: RootExpr = [":return", payload, 2] 185 | const expected: FinalReply.Interpret = { 186 | result: "4 : Integer", 187 | metadata: [ 188 | { 189 | length: 1, 190 | metadata: { 191 | decor: ":data", 192 | docOverview: "An arbitrary-precision integer", 193 | name: "4", 194 | ttTerm: "AAAAAAAAAAAEAQAAAAAE", 195 | type: "Integer", 196 | }, 197 | start: 0, 198 | }, 199 | { 200 | length: 7, 201 | metadata: { 202 | decor: ":type", 203 | docOverview: "Arbitrary-precision integers", 204 | name: "Integer", 205 | ttTerm: "AAAAAAAAAAAECg==", 206 | type: "Type", 207 | }, 208 | start: 4, 209 | }, 210 | ], 211 | id: 2, 212 | ok: true, 213 | type: ":return", 214 | } 215 | 216 | const tokens = new TokenIter(sexp) 217 | const exprs = parse(tokens) as RootExpr 218 | assert.deepEqual(exprs, rootExpr) 219 | 220 | const parsed = parseReply(rootExpr, ":interpret") 221 | assert.deepEqual(parsed, expected) 222 | }) 223 | 224 | it("can parse a failure sexp with metadata.", () => { 225 | const sexp = `(:return (:error "When checking an application of function Prelude.Interfaces.*:\n No such variable cas" ((41 20 ((:name "Prelude.Interfaces.*") (:implicit :False) (:key "AQAAAAAAAAAAASoAAAAAAAAAAgAAAAAAAAAKSW50ZXJmYWNlcwAAAAAAAAAHUHJlbHVkZQ==") (:decor :function) (:doc-overview "") (:type "Num ty => ty -> ty -> ty") (:namespace "Prelude.Interfaces"))) (88 3 ((:name "cas") (:implicit :False) (:key "AAAAAAAAAAADY2Fz"))))) 2)` 226 | const payload: S_Exp.InterpretErr = [ 227 | ":error", 228 | "When checking an application of function Prelude.Interfaces.*:\n No such variable cas", 229 | [ 230 | [ 231 | 41, 232 | 20, 233 | [ 234 | [":name", "Prelude.Interfaces.*"], 235 | [":implicit", ":False"], 236 | [ 237 | ":key", 238 | "AQAAAAAAAAAAASoAAAAAAAAAAgAAAAAAAAAKSW50ZXJmYWNlcwAAAAAAAAAHUHJlbHVkZQ==", 239 | ], 240 | [":decor", ":function"], 241 | [":doc-overview", ""], 242 | [":type", "Num ty => ty -> ty -> ty"], 243 | [":namespace", "Prelude.Interfaces"], 244 | ], 245 | ], 246 | [ 247 | 88, 248 | 3, 249 | [ 250 | [":name", "cas"], 251 | [":implicit", ":False"], 252 | [":key", "AAAAAAAAAAADY2Fz"], 253 | ], 254 | ], 255 | ], 256 | ] 257 | const rootExpr: RootExpr = [":return", payload, 2] 258 | const expected: FinalReply.Interpret = { 259 | err: 260 | "When checking an application of function Prelude.Interfaces.*:\n No such variable cas", 261 | metadata: [ 262 | { 263 | length: 20, 264 | metadata: { 265 | decor: ":function", 266 | docOverview: "", 267 | implicit: ":False", 268 | key: 269 | "AQAAAAAAAAAAASoAAAAAAAAAAgAAAAAAAAAKSW50ZXJmYWNlcwAAAAAAAAAHUHJlbHVkZQ==", 270 | name: "Prelude.Interfaces.*", 271 | namespace: "Prelude.Interfaces", 272 | type: "Num ty => ty -> ty -> ty", 273 | }, 274 | start: 41, 275 | }, 276 | { 277 | length: 3, 278 | metadata: { 279 | implicit: ":False", 280 | key: "AAAAAAAAAAADY2Fz", 281 | name: "cas", 282 | }, 283 | start: 88, 284 | }, 285 | ], 286 | id: 2, 287 | ok: false, 288 | type: ":return", 289 | } 290 | 291 | const tokens = new TokenIter(sexp) 292 | const exprs = parse(tokens) as RootExpr 293 | assert.deepEqual(exprs, rootExpr) 294 | 295 | const parsed = parseReply(rootExpr, ":interpret") 296 | assert.deepEqual(parsed, expected) 297 | }) 298 | 299 | it("can parse a failure sexp without metadata.", () => { 300 | const sexp = `(:return (:error "(input):1:1:\n |\n1 | -----\n | ^^^^^\nunexpected \\"-----\\"\nexpecting ':', dependent type signature, or end of input\n") 3)` 301 | const payload: S_Exp.InterpretErr = [ 302 | ":error", 303 | `(input):1:1:\n |\n1 | -----\n | ^^^^^\nunexpected "-----"\nexpecting ':', dependent type signature, or end of input\n`, 304 | ] 305 | const rootExpr: RootExpr = [":return", payload, 3] 306 | const expected: FinalReply.Interpret = { 307 | err: `(input):1:1:\n |\n1 | -----\n | ^^^^^\nunexpected "-----"\nexpecting ':', dependent type signature, or end of input\n`, 308 | metadata: [], 309 | id: 3, 310 | ok: false, 311 | type: ":return", 312 | } 313 | 314 | const tokens = new TokenIter(sexp) 315 | const exprs = parse(tokens) as RootExpr 316 | assert.deepEqual(exprs, rootExpr) 317 | 318 | const parsed = parseReply(rootExpr, ":interpret") 319 | assert.deepEqual(parsed, expected) 320 | }) 321 | }) 322 | -------------------------------------------------------------------------------- /test/parser/load-file.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { TokenIter } from "../../src/parser/lexer" 4 | import { RootExpr, S_Exp } from "../../src/s-exps" 5 | import { FinalReply, InfoReply, OutputReply } from "../../src/reply" 6 | 7 | describe("Parsing :load-file reply", () => { 8 | it("can parse the load-file source highlighting", () => { 9 | // client.loadFile("./test/resources/test.idr") it prepends the extra ./ 10 | const sexp = `(:output (:ok (:highlight-source ((((:filename "././test/resources/test.idr") (:start 1 8) (:end 1 14)) ((:namespace "Example") (:decor :module) (:source-file "/home/michael/dev/idris-client/test/resources/test.idr")))))) 1)` 11 | const payload: S_Exp.Output = [ 12 | ":ok", 13 | [ 14 | ":highlight-source", 15 | [ 16 | [ 17 | [ 18 | [":filename", "././test/resources/test.idr"], 19 | [":start", 1, 8], 20 | [":end", 1, 14], 21 | ], 22 | [ 23 | [":namespace", "Example"], 24 | [":decor", ":module"], 25 | [ 26 | ":source-file", 27 | "/home/michael/dev/idris-client/test/resources/test.idr", 28 | ], 29 | ], 30 | ], 31 | ], 32 | ], 33 | ] 34 | const rootExpr: RootExpr = [":output", payload, 1] 35 | const expected: OutputReply = { 36 | ok: [ 37 | { 38 | end: { 39 | column: 14, 40 | line: 1, 41 | }, 42 | filename: "././test/resources/test.idr", 43 | metadata: { 44 | decor: ":module", 45 | namespace: "Example", 46 | sourceFile: 47 | "/home/michael/dev/idris-client/test/resources/test.idr", 48 | }, 49 | start: { 50 | column: 8, 51 | line: 1, 52 | }, 53 | }, 54 | ], 55 | id: 1, 56 | type: ":output", 57 | } 58 | 59 | const tokens = new TokenIter(sexp) 60 | const exprs = parse(tokens) as RootExpr 61 | assert.deepEqual(exprs, rootExpr) 62 | 63 | const parsed = parseReply(rootExpr, ":load-file") 64 | assert.deepEqual(parsed, expected) 65 | }) 66 | 67 | it("can parse the load-file compilation/type-checking log", () => { 68 | const sexp = `(:write-string "Type checking ././test/resources/test.idr" 1)` 69 | const payload: S_Exp.WriteString = 70 | "Type checking ././test/resources/test.idr" 71 | const rootExpr: RootExpr = [":write-string", payload, 1] 72 | const expected: InfoReply.WriteString = { 73 | message: "Type checking ././test/resources/test.idr", 74 | id: 1, 75 | type: ":write-string", 76 | } 77 | 78 | const tokens = new TokenIter(sexp) 79 | const exprs = parse(tokens) as RootExpr 80 | assert.deepEqual(exprs, rootExpr) 81 | 82 | const parsed = parseReply(rootExpr, ":load-file") 83 | assert.deepEqual(parsed, expected) 84 | }) 85 | 86 | it("can parse the load-file set-prompt log", () => { 87 | const sexp = `(:set-prompt "*./test/resources/test" 1)` 88 | const payload: S_Exp.SetPrompt = "*./test/resources/test" 89 | const rootExpr: RootExpr = [":set-prompt", payload, 1] 90 | const expected: InfoReply.SetPrompt = { 91 | path: "*./test/resources/test", 92 | id: 1, 93 | type: ":set-prompt", 94 | } 95 | 96 | const tokens = new TokenIter(sexp) 97 | const exprs = parse(tokens) as RootExpr 98 | assert.deepEqual(exprs, rootExpr) 99 | 100 | const parsed = parseReply(rootExpr, ":load-file") 101 | assert.deepEqual(parsed, expected) 102 | }) 103 | 104 | it("can parse the load-file success reply", () => { 105 | const sexp = `(:return (:ok ()) 1)` 106 | const payload: S_Exp.LoadFileOk = [":ok", []] 107 | const rootExpr: RootExpr = [":return", payload, 1] 108 | const expected: FinalReply.LoadFile = { 109 | ok: true, 110 | id: 1, 111 | type: ":return", 112 | } 113 | 114 | const tokens = new TokenIter(sexp) 115 | const exprs = parse(tokens) as RootExpr 116 | assert.deepEqual(exprs, rootExpr) 117 | 118 | const parsed = parseReply(rootExpr, ":load-file") 119 | assert.deepEqual(parsed, expected) 120 | }) 121 | 122 | it("can parse the load-file failed compilation warning", () => { 123 | const sexp = `(:warning ("././test/resources/test.idr" (10 1) (10 13) "When checking left hand side of getName:\n When checking argument cat to Example.getName:\n Tiger is not a valid name for a pattern variable" ((32 7 ((:name "Example.getName") (:implicit :False) (:key "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==") (:decor :metavar) (:doc-overview "") (:type "Cat -> String") (:namespace "Example"))) (64 3 ((:name "cat") (:decor :bound) (:implicit :False))) (71 15 ((:name "Example.getName") (:implicit :False) (:key "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==") (:decor :metavar) (:doc-overview "") (:type "Cat -> String") (:namespace "Example"))))) 1)` 124 | // const payload: S_Exp.Warning = [":ok", []] 125 | const payload: S_Exp.Warning = [ 126 | "././test/resources/test.idr", 127 | [10, 1], 128 | [10, 13], 129 | "When checking left hand side of getName:\n When checking argument cat to Example.getName:\n Tiger is not a valid name for a pattern variable", 130 | [ 131 | [ 132 | 32, 133 | 7, 134 | [ 135 | [":name", "Example.getName"], 136 | [":implicit", ":False"], 137 | [ 138 | ":key", 139 | "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 140 | ], 141 | [":decor", ":metavar"], 142 | [":doc-overview", ""], 143 | [":type", "Cat -> String"], 144 | [":namespace", "Example"], 145 | ], 146 | ], 147 | [ 148 | 64, 149 | 3, 150 | [ 151 | [":name", "cat"], 152 | [":decor", ":bound"], 153 | [":implicit", ":False"], 154 | ], 155 | ], 156 | [ 157 | 71, 158 | 15, 159 | [ 160 | [":name", "Example.getName"], 161 | [":implicit", ":False"], 162 | [ 163 | ":key", 164 | "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 165 | ], 166 | [":decor", ":metavar"], 167 | [":doc-overview", ""], 168 | [":type", "Cat -> String"], 169 | [":namespace", "Example"], 170 | ], 171 | ], 172 | ], 173 | ] 174 | const rootExpr: RootExpr = [":warning", payload, 1] 175 | const expected: InfoReply.Warning = { 176 | err: { 177 | end: { 178 | column: 13, 179 | line: 10, 180 | }, 181 | filename: "././test/resources/test.idr", 182 | metadata: [ 183 | { 184 | length: 7, 185 | metadata: { 186 | decor: ":metavar", 187 | docOverview: "", 188 | implicit: ":False", 189 | key: "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 190 | name: "Example.getName", 191 | namespace: "Example", 192 | type: "Cat -> String", 193 | }, 194 | start: 32, 195 | }, 196 | { 197 | length: 3, 198 | metadata: { 199 | decor: ":bound", 200 | implicit: ":False", 201 | name: "cat", 202 | }, 203 | start: 64, 204 | }, 205 | { 206 | length: 15, 207 | metadata: { 208 | decor: ":metavar", 209 | docOverview: "", 210 | implicit: ":False", 211 | key: "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 212 | name: "Example.getName", 213 | namespace: "Example", 214 | type: "Cat -> String", 215 | }, 216 | start: 71, 217 | }, 218 | ], 219 | start: { 220 | column: 1, 221 | line: 10, 222 | }, 223 | warning: 224 | "When checking left hand side of getName:\n When checking argument cat to Example.getName:\n Tiger is not a valid name for a pattern variable", 225 | }, 226 | id: 1, 227 | type: ":warning", 228 | } 229 | 230 | const tokens = new TokenIter(sexp) 231 | const exprs = parse(tokens) as RootExpr 232 | assert.deepEqual(exprs, rootExpr) 233 | 234 | const parsed = parseReply(rootExpr, ":load-file") 235 | assert.deepEqual(parsed, expected) 236 | }) 237 | 238 | it("can parse the load-file failure", () => { 239 | const sexp = `(:return (:error "didn't load ./test/resources/test.idr") 1)` 240 | const payload: S_Exp.LoadFileErr = [ 241 | ":error", 242 | "didn't load ./test/resources/test.idr", 243 | ] 244 | const rootExpr: RootExpr = [":return", payload, 1] 245 | const expected: FinalReply.LoadFile = { 246 | err: "didn't load ./test/resources/test.idr", 247 | id: 1, 248 | ok: false, 249 | type: ":return", 250 | } 251 | 252 | const tokens = new TokenIter(sexp) 253 | const exprs = parse(tokens) as RootExpr 254 | assert.deepEqual(exprs, rootExpr) 255 | 256 | const parsed = parseReply(rootExpr, ":load-file") 257 | assert.deepEqual(parsed, expected) 258 | }) 259 | }) 260 | -------------------------------------------------------------------------------- /test/parser/make-case.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :make-case reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "g n b = case _ of\n case_val => ?g_rhs\n") 2)` 10 | const payload: S_Exp.MakeCase = [ 11 | ":ok", 12 | "g n b = case _ of\n case_val => ?g_rhs\n", 13 | ] 14 | const rootExpr: RootExpr = [":return", payload, 2] 15 | const expected: FinalReply.MakeCase = { 16 | caseClause: "g n b = case _ of\n case_val => ?g_rhs\n", 17 | id: 2, 18 | ok: true, 19 | type: ":return", 20 | } 21 | 22 | const tokens = new TokenIter(sexp) 23 | const exprs = parse(tokens) as RootExpr 24 | assert.deepEqual(exprs, rootExpr) 25 | 26 | const parsed = parseReply(rootExpr, ":make-case") 27 | assert.deepEqual(parsed, expected) 28 | }) 29 | 30 | it("can parse a failure sexp.", () => { 31 | // When it can’t add a case, it still returns a new line. 32 | const sexp = `(:return (:ok "\n") 2)` 33 | const payload: S_Exp.MakeCase = [":ok", "\n"] 34 | const rootExpr: RootExpr = [":return", payload, 2] 35 | const expected: FinalReply.MakeCase = { 36 | caseClause: "\n", 37 | id: 2, 38 | ok: true, 39 | type: ":return", 40 | } 41 | 42 | const tokens = new TokenIter(sexp) 43 | const exprs = parse(tokens) as RootExpr 44 | assert.deepEqual(exprs, rootExpr) 45 | 46 | const parsed = parseReply(rootExpr, ":make-case") 47 | assert.deepEqual(parsed, expected) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /test/parser/make-lemma.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :make-lemma reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok (:metavariable-lemma (:replace-metavariable "g_rhs n b") (:definition-type "g_rhs : (n : Nat) -> (b : Bool) -> String"))) 2)` 10 | const payload: S_Exp.MakeLemmaOk = [ 11 | ":ok", 12 | [ 13 | ":metavariable-lemma", 14 | [":replace-metavariable", "g_rhs n b"], 15 | [":definition-type", "g_rhs : (n : Nat) -> (b : Bool) -> String"], 16 | ], 17 | ] 18 | const rootExpr: RootExpr = [":return", payload, 2] 19 | const expected: FinalReply.MakeLemma = { 20 | declaration: "g_rhs : (n : Nat) -> (b : Bool) -> String", 21 | id: 2, 22 | metavariable: "g_rhs n b", 23 | ok: true, 24 | type: ":return", 25 | } 26 | 27 | const tokens = new TokenIter(sexp) 28 | const exprs = parse(tokens) as RootExpr 29 | assert.deepEqual(exprs, rootExpr) 30 | 31 | const parsed = parseReply(rootExpr, ":make-lemma") 32 | assert.deepEqual(parsed, expected) 33 | }) 34 | 35 | it("can parse a failure sexp.", () => { 36 | const sexp = `(:return (:error "NoSuchVariable nix") 2)` 37 | const payload: S_Exp.MakeLemmaErr = [":error", "NoSuchVariable nix"] 38 | const rootExpr: RootExpr = [":return", payload, 2] 39 | const expected: FinalReply.MakeLemma = { 40 | err: "NoSuchVariable nix", 41 | id: 2, 42 | ok: false, 43 | type: ":return", 44 | } 45 | 46 | const tokens = new TokenIter(sexp) 47 | const exprs = parse(tokens) as RootExpr 48 | assert.deepEqual(exprs, rootExpr) 49 | 50 | const parsed = parseReply(rootExpr, ":make-lemma") 51 | assert.deepEqual(parsed, expected) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /test/parser/make-with.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :make-with reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "g n b with (_)\n g n b | with_pat = ?g_rhs_rhs\n") 2)` 10 | const payload: S_Exp.MakeWith = [ 11 | ":ok", 12 | "g n b with (_)\n g n b | with_pat = ?g_rhs_rhs\n", 13 | ] 14 | const rootExpr: RootExpr = [":return", payload, 2] 15 | const expected: FinalReply.MakeWith = { 16 | id: 2, 17 | ok: true, 18 | type: ":return", 19 | withClause: "g n b with (_)\n g n b | with_pat = ?g_rhs_rhs\n", 20 | } 21 | 22 | const tokens = new TokenIter(sexp) 23 | const exprs = parse(tokens) as RootExpr 24 | assert.deepEqual(exprs, rootExpr) 25 | 26 | const parsed = parseReply(rootExpr, ":make-with") 27 | assert.deepEqual(parsed, expected) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/parser/metavariables.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :metavariables reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok (("Example.f" () ("Cat -> String" ((0 3 ((:name "Example.Cat") (:implicit :False) (:key "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl") (:decor :type) (:doc-overview "") (:type "Type") (:namespace "Example"))) (7 6 ((:decor :type) (:type "Type") (:doc-overview "Strings in some unspecified encoding") (:name "String"))) (0 13 ((:tt-term "AAAAAAAAAAACAAAAAAAAAAADY2F0AQIAAAMAAAAAAAAACAAAAAAAAAAAAQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxlBwAAAAAAAAAAGy4vLi90ZXN0L3Jlc291cmNlcy90ZXN0LmlkcgAAAAAAAAAXBA0=")))))) ("Example.g_rhs" (("n" "Nat" ((0 3 ((:name "Prelude.Nat.Nat") (:implicit :False) (:key "AQAAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=") (:decor :type) (:doc-overview "Natural numbers: unbounded, unsigned integers\nwhich can be pattern matched.") (:type "Type") (:namespace "Prelude.Nat"))) (0 3 ((:tt-term "AAAAAAAAAAAAAwAAAAAAAAAIAAAAAAAAAAABAAAAAAAAAAADTmF0AAAAAAAAAAIAAAAAAAAAA05hdAAAAAAAAAAHUHJlbHVkZQ=="))))) ("b" "Bool" ((0 4 ((:name "Prelude.Bool.Bool") (:implicit :False) (:key "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==") (:decor :type) (:doc-overview "Boolean Data Type") (:type "Type") (:namespace "Prelude.Bool"))) (0 4 ((:tt-term "AAAAAAAAAAEAAAAAAAAAAAFuAAADAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAAAARCb29sAAAAAAAAAAIAAAAAAAAABEJvb2wAAAAAAAAAB1ByZWx1ZGU=")))))) ("String" ((0 6 ((:decor :type) (:type "Type") (:doc-overview "Strings in some unspecified encoding") (:name "String"))) (0 6 ((:tt-term "AAAAAAAAAAIAAAAAAAAAAAFuAAAAAAAAAAAAAWIABA0=")))))))) 2)` 10 | const payload: S_Exp.Metavariables = [ 11 | ":ok", 12 | [ 13 | [ 14 | "Example.f", 15 | [], 16 | [ 17 | "Cat -> String", 18 | [ 19 | [ 20 | 0, 21 | 3, 22 | [ 23 | [":name", "Example.Cat"], 24 | [":implicit", ":False"], 25 | [":key", "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl"], 26 | [":decor", ":type"], 27 | [":doc-overview", ""], 28 | [":type", "Type"], 29 | [":namespace", "Example"], 30 | ], 31 | ], 32 | [ 33 | 7, 34 | 6, 35 | [ 36 | [":decor", ":type"], 37 | [":type", "Type"], 38 | [":doc-overview", "Strings in some unspecified encoding"], 39 | [":name", "String"], 40 | ], 41 | ], 42 | [ 43 | 0, 44 | 13, 45 | [ 46 | [ 47 | ":tt-term", 48 | "AAAAAAAAAAACAAAAAAAAAAADY2F0AQIAAAMAAAAAAAAACAAAAAAAAAAAAQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxlBwAAAAAAAAAAGy4vLi90ZXN0L3Jlc291cmNlcy90ZXN0LmlkcgAAAAAAAAAXBA0=", 49 | ], 50 | ], 51 | ], 52 | ], 53 | ], 54 | ], 55 | [ 56 | "Example.g_rhs", 57 | [ 58 | [ 59 | "n", 60 | "Nat", 61 | [ 62 | [ 63 | 0, 64 | 3, 65 | [ 66 | [":name", "Prelude.Nat.Nat"], 67 | [":implicit", ":False"], 68 | [ 69 | ":key", 70 | "AQAAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=", 71 | ], 72 | [":decor", ":type"], 73 | [ 74 | ":doc-overview", 75 | "Natural numbers: unbounded, unsigned integers\nwhich can be pattern matched.", 76 | ], 77 | [":type", "Type"], 78 | [":namespace", "Prelude.Nat"], 79 | ], 80 | ], 81 | [ 82 | 0, 83 | 3, 84 | [ 85 | [ 86 | ":tt-term", 87 | "AAAAAAAAAAAAAwAAAAAAAAAIAAAAAAAAAAABAAAAAAAAAAADTmF0AAAAAAAAAAIAAAAAAAAAA05hdAAAAAAAAAAHUHJlbHVkZQ==", 88 | ], 89 | ], 90 | ], 91 | ], 92 | ], 93 | [ 94 | "b", 95 | "Bool", 96 | [ 97 | [ 98 | 0, 99 | 4, 100 | [ 101 | [":name", "Prelude.Bool.Bool"], 102 | [":implicit", ":False"], 103 | [ 104 | ":key", 105 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 106 | ], 107 | [":decor", ":type"], 108 | [":doc-overview", "Boolean Data Type"], 109 | [":type", "Type"], 110 | [":namespace", "Prelude.Bool"], 111 | ], 112 | ], 113 | [ 114 | 0, 115 | 4, 116 | [ 117 | [ 118 | ":tt-term", 119 | "AAAAAAAAAAEAAAAAAAAAAAFuAAADAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAAAARCb29sAAAAAAAAAAIAAAAAAAAABEJvb2wAAAAAAAAAB1ByZWx1ZGU=", 120 | ], 121 | ], 122 | ], 123 | ], 124 | ], 125 | ], 126 | [ 127 | "String", 128 | [ 129 | [ 130 | 0, 131 | 6, 132 | [ 133 | [":decor", ":type"], 134 | [":type", "Type"], 135 | [":doc-overview", "Strings in some unspecified encoding"], 136 | [":name", "String"], 137 | ], 138 | ], 139 | [ 140 | 0, 141 | 6, 142 | [[":tt-term", "AAAAAAAAAAIAAAAAAAAAAAFuAAAAAAAAAAAAAWIABA0="]], 143 | ], 144 | ], 145 | ], 146 | ], 147 | ], 148 | ] 149 | const rootExpr: RootExpr = [":return", payload, 2] 150 | const expected: FinalReply.Metavariables = { 151 | metavariables: [ 152 | { 153 | metavariable: { 154 | metadata: [ 155 | { 156 | length: 3, 157 | metadata: { 158 | decor: ":type", 159 | docOverview: "", 160 | implicit: ":False", 161 | key: "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl", 162 | name: "Example.Cat", 163 | namespace: "Example", 164 | type: "Type", 165 | }, 166 | start: 0, 167 | }, 168 | { 169 | length: 6, 170 | metadata: { 171 | decor: ":type", 172 | docOverview: "Strings in some unspecified encoding", 173 | name: "String", 174 | type: "Type", 175 | }, 176 | start: 7, 177 | }, 178 | { 179 | length: 13, 180 | metadata: { 181 | ttTerm: 182 | "AAAAAAAAAAACAAAAAAAAAAADY2F0AQIAAAMAAAAAAAAACAAAAAAAAAAAAQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxlBwAAAAAAAAAAGy4vLi90ZXN0L3Jlc291cmNlcy90ZXN0LmlkcgAAAAAAAAAXBA0=", 183 | }, 184 | start: 0, 185 | }, 186 | ], 187 | name: "Example.f", 188 | type: "Cat -> String", 189 | }, 190 | premises: [], 191 | }, 192 | { 193 | metavariable: { 194 | metadata: [ 195 | { 196 | length: 6, 197 | metadata: { 198 | decor: ":type", 199 | docOverview: "Strings in some unspecified encoding", 200 | name: "String", 201 | ttTerm: "AAAAAAAAAAIAAAAAAAAAAAFuAAAAAAAAAAAAAWIABA0=", 202 | type: "Type", 203 | }, 204 | start: 0, 205 | }, 206 | ], 207 | name: "Example.g_rhs", 208 | type: "String", 209 | }, 210 | premises: [ 211 | { 212 | metadata: [ 213 | { 214 | length: 3, 215 | metadata: { 216 | decor: ":type", 217 | docOverview: 218 | "Natural numbers: unbounded, unsigned integers\nwhich can be pattern matched.", 219 | implicit: ":False", 220 | key: "AQAAAAAAAAAAA05hdAAAAAAAAAACAAAAAAAAAANOYXQAAAAAAAAAB1ByZWx1ZGU=", 221 | name: "Prelude.Nat.Nat", 222 | namespace: "Prelude.Nat", 223 | ttTerm: 224 | "AAAAAAAAAAAAAwAAAAAAAAAIAAAAAAAAAAABAAAAAAAAAAADTmF0AAAAAAAAAAIAAAAAAAAAA05hdAAAAAAAAAAHUHJlbHVkZQ==", 225 | type: "Type", 226 | }, 227 | start: 0, 228 | }, 229 | ], 230 | name: "n", 231 | type: "Nat", 232 | }, 233 | { 234 | metadata: [ 235 | { 236 | length: 4, 237 | metadata: { 238 | decor: ":type", 239 | docOverview: "Boolean Data Type", 240 | implicit: ":False", 241 | key: "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 242 | name: "Prelude.Bool.Bool", 243 | namespace: "Prelude.Bool", 244 | ttTerm: 245 | "AAAAAAAAAAEAAAAAAAAAAAFuAAADAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAAAARCb29sAAAAAAAAAAIAAAAAAAAABEJvb2wAAAAAAAAAB1ByZWx1ZGU=", 246 | type: "Type", 247 | }, 248 | start: 0, 249 | }, 250 | ], 251 | name: "b", 252 | type: "Bool", 253 | }, 254 | ], 255 | }, 256 | ], 257 | id: 2, 258 | ok: true, 259 | type: ":return", 260 | } 261 | 262 | const tokens = new TokenIter(sexp) 263 | const exprs = parse(tokens) as RootExpr 264 | assert.deepEqual(exprs, rootExpr) 265 | 266 | const parsed = parseReply(rootExpr, ":metavariables") 267 | assert.deepEqual(parsed, expected) 268 | }) 269 | }) 270 | -------------------------------------------------------------------------------- /test/parser/print-definition.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :print-definition reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "data Bool : Type where\n False : Bool\n True : Bool" ((0 4 ((:decor :keyword))) (5 4 ((:name "Prelude.Bool.Bool") (:implicit :False) (:key "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==") (:decor :type) (:doc-overview "Boolean Data Type") (:type "Type") (:namespace "Prelude.Bool"))) (12 4 ((:decor :type) (:type "Type") (:doc-overview "The type of types") (:name "Type"))) (12 4 ((:tt-term "AAAAAAAAAAAHAAAAAAAAAAASLi9QcmVsdWRlL0Jvb2wuaWRyAAAAAAAAABQ="))) (17 5 ((:decor :keyword))) (25 5 ((:name "Prelude.Bool.False") (:implicit :False) (:key "AQAAAAAAAAAABUZhbHNlAAAAAAAAAAIAAAAAAAAABEJvb2wAAAAAAAAAB1ByZWx1ZGU=") (:decor :data) (:doc-overview "") (:type "Bool") (:namespace "Prelude.Bool"))) (33 4 ((:name "Prelude.Bool.Bool") (:implicit :False) (:key "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==") (:decor :type) (:doc-overview "Boolean Data Type") (:type "Type") (:namespace "Prelude.Bool"))) (33 4 ((:tt-term "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAEQm9vbAAAAAAAAAACAAAAAAAAAARCb29sAAAAAAAAAAdQcmVsdWRl"))) (40 4 ((:name "Prelude.Bool.True") (:implicit :False) (:key "AQAAAAAAAAAABFRydWUAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==") (:decor :data) (:doc-overview "") (:type "Bool") (:namespace "Prelude.Bool"))) (47 4 ((:name "Prelude.Bool.Bool") (:implicit :False) (:key "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==") (:decor :type) (:doc-overview "Boolean Data Type") (:type "Type") (:namespace "Prelude.Bool"))) (47 4 ((:tt-term "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAEQm9vbAAAAAAAAAACAAAAAAAAAARCb29sAAAAAAAAAAdQcmVsdWRl"))))) 2)` 10 | const payload: S_Exp.PrintDefinitionOk = [ 11 | ":ok", 12 | "data Bool : Type where\n False : Bool\n True : Bool", 13 | [ 14 | [0, 4, [[":decor", ":keyword"]]], 15 | [ 16 | 5, 17 | 4, 18 | [ 19 | [":name", "Prelude.Bool.Bool"], 20 | [":implicit", ":False"], 21 | [ 22 | ":key", 23 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 24 | ], 25 | [":decor", ":type"], 26 | [":doc-overview", "Boolean Data Type"], 27 | [":type", "Type"], 28 | [":namespace", "Prelude.Bool"], 29 | ], 30 | ], 31 | [ 32 | 12, 33 | 4, 34 | [ 35 | [":decor", ":type"], 36 | [":type", "Type"], 37 | [":doc-overview", "The type of types"], 38 | [":name", "Type"], 39 | ], 40 | ], 41 | [ 42 | 12, 43 | 4, 44 | [ 45 | [ 46 | ":tt-term", 47 | "AAAAAAAAAAAHAAAAAAAAAAASLi9QcmVsdWRlL0Jvb2wuaWRyAAAAAAAAABQ=", 48 | ], 49 | ], 50 | ], 51 | [17, 5, [[":decor", ":keyword"]]], 52 | [ 53 | 25, 54 | 5, 55 | [ 56 | [":name", "Prelude.Bool.False"], 57 | [":implicit", ":False"], 58 | [ 59 | ":key", 60 | "AQAAAAAAAAAABUZhbHNlAAAAAAAAAAIAAAAAAAAABEJvb2wAAAAAAAAAB1ByZWx1ZGU=", 61 | ], 62 | [":decor", ":data"], 63 | [":doc-overview", ""], 64 | [":type", "Bool"], 65 | [":namespace", "Prelude.Bool"], 66 | ], 67 | ], 68 | [ 69 | 33, 70 | 4, 71 | [ 72 | [":name", "Prelude.Bool.Bool"], 73 | [":implicit", ":False"], 74 | [ 75 | ":key", 76 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 77 | ], 78 | [":decor", ":type"], 79 | [":doc-overview", "Boolean Data Type"], 80 | [":type", "Type"], 81 | [":namespace", "Prelude.Bool"], 82 | ], 83 | ], 84 | [ 85 | 33, 86 | 4, 87 | [ 88 | [ 89 | ":tt-term", 90 | "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAEQm9vbAAAAAAAAAACAAAAAAAAAARCb29sAAAAAAAAAAdQcmVsdWRl", 91 | ], 92 | ], 93 | ], 94 | [ 95 | 40, 96 | 4, 97 | [ 98 | [":name", "Prelude.Bool.True"], 99 | [":implicit", ":False"], 100 | [ 101 | ":key", 102 | "AQAAAAAAAAAABFRydWUAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 103 | ], 104 | [":decor", ":data"], 105 | [":doc-overview", ""], 106 | [":type", "Bool"], 107 | [":namespace", "Prelude.Bool"], 108 | ], 109 | ], 110 | [ 111 | 47, 112 | 4, 113 | [ 114 | [":name", "Prelude.Bool.Bool"], 115 | [":implicit", ":False"], 116 | [ 117 | ":key", 118 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 119 | ], 120 | [":decor", ":type"], 121 | [":doc-overview", "Boolean Data Type"], 122 | [":type", "Type"], 123 | [":namespace", "Prelude.Bool"], 124 | ], 125 | ], 126 | [ 127 | 47, 128 | 4, 129 | [ 130 | [ 131 | ":tt-term", 132 | "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAEQm9vbAAAAAAAAAACAAAAAAAAAARCb29sAAAAAAAAAAdQcmVsdWRl", 133 | ], 134 | ], 135 | ], 136 | ], 137 | ] 138 | const rootExpr: RootExpr = [":return", payload, 2] 139 | const expected: FinalReply.PrintDefinition = { 140 | definition: "data Bool : Type where\n False : Bool\n True : Bool", 141 | metadata: [ 142 | { 143 | length: 4, 144 | metadata: { 145 | decor: ":keyword", 146 | }, 147 | start: 0, 148 | }, 149 | { 150 | length: 4, 151 | metadata: { 152 | decor: ":type", 153 | docOverview: "Boolean Data Type", 154 | implicit: ":False", 155 | key: 156 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 157 | name: "Prelude.Bool.Bool", 158 | namespace: "Prelude.Bool", 159 | type: "Type", 160 | }, 161 | start: 5, 162 | }, 163 | { 164 | length: 4, 165 | metadata: { 166 | decor: ":type", 167 | docOverview: "The type of types", 168 | name: "Type", 169 | ttTerm: "AAAAAAAAAAAHAAAAAAAAAAASLi9QcmVsdWRlL0Jvb2wuaWRyAAAAAAAAABQ=", 170 | type: "Type", 171 | }, 172 | start: 12, 173 | }, 174 | { 175 | length: 5, 176 | metadata: { 177 | decor: ":keyword", 178 | }, 179 | start: 17, 180 | }, 181 | { 182 | length: 5, 183 | metadata: { 184 | decor: ":data", 185 | docOverview: "", 186 | implicit: ":False", 187 | key: 188 | "AQAAAAAAAAAABUZhbHNlAAAAAAAAAAIAAAAAAAAABEJvb2wAAAAAAAAAB1ByZWx1ZGU=", 189 | name: "Prelude.Bool.False", 190 | namespace: "Prelude.Bool", 191 | type: "Bool", 192 | }, 193 | start: 25, 194 | }, 195 | { 196 | length: 4, 197 | metadata: { 198 | decor: ":type", 199 | docOverview: "Boolean Data Type", 200 | implicit: ":False", 201 | key: 202 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 203 | name: "Prelude.Bool.Bool", 204 | namespace: "Prelude.Bool", 205 | ttTerm: "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAEQm9vbAAAAAAAAAACAAAAAAAAAARCb29sAAAAAAAAAAdQcmVsdWRl", 206 | type: "Type", 207 | }, 208 | start: 33, 209 | }, 210 | { 211 | length: 4, 212 | metadata: { 213 | decor: ":data", 214 | docOverview: "", 215 | implicit: ":False", 216 | key: 217 | "AQAAAAAAAAAABFRydWUAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 218 | name: "Prelude.Bool.True", 219 | namespace: "Prelude.Bool", 220 | type: "Bool", 221 | }, 222 | start: 40, 223 | }, 224 | { 225 | length: 4, 226 | metadata: { 227 | decor: ":type", 228 | docOverview: "Boolean Data Type", 229 | implicit: ":False", 230 | key: 231 | "AQAAAAAAAAAABEJvb2wAAAAAAAAAAgAAAAAAAAAEQm9vbAAAAAAAAAAHUHJlbHVkZQ==", 232 | name: "Prelude.Bool.Bool", 233 | namespace: "Prelude.Bool", 234 | ttTerm: "AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAEQm9vbAAAAAAAAAACAAAAAAAAAARCb29sAAAAAAAAAAdQcmVsdWRl", 235 | type: "Type", 236 | }, 237 | start: 47, 238 | }, 239 | ], 240 | id: 2, 241 | ok: true, 242 | type: ":return", 243 | } 244 | 245 | const tokens = new TokenIter(sexp) 246 | const exprs = parse(tokens) as RootExpr 247 | assert.deepEqual(exprs, rootExpr) 248 | 249 | const parsed = parseReply(rootExpr, ":print-definition") 250 | assert.deepEqual(parsed, expected) 251 | }) 252 | 253 | it("can parse a failure sexp.", () => { 254 | const sexp = `(:return (:error "Not found") 2)` 255 | const payload: S_Exp.PrintDefinitionErr = [":error", "Not found"] 256 | const rootExpr: RootExpr = [":return", payload, 2] 257 | const expected: FinalReply.PrintDefinition = { 258 | err: "Not found", 259 | id: 2, 260 | ok: false, 261 | type: ":return", 262 | } 263 | 264 | const tokens = new TokenIter(sexp) 265 | const exprs = parse(tokens) as RootExpr 266 | assert.deepEqual(exprs, rootExpr) 267 | 268 | const parsed = parseReply(rootExpr, ":print-definition") 269 | assert.deepEqual(parsed, expected) 270 | }) 271 | }) 272 | -------------------------------------------------------------------------------- /test/parser/proof-search-next.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | // Idris 2 only. 8 | describe("Parsing :proof-search-next reply", () => { 9 | it("can parse a success sexp.", () => { 10 | const sexp = `(:return (:ok "0") 2)` 11 | const payload: S_Exp.ProofSearch = [":ok", "0"] 12 | const rootExpr: RootExpr = [":return", payload, 2] 13 | const expected: FinalReply.ProofSearch = { 14 | id: 2, 15 | ok: true, 16 | solution: "0", 17 | type: ":return", 18 | } 19 | 20 | const tokens = new TokenIter(sexp) 21 | const exprs = parse(tokens) as RootExpr 22 | assert.deepEqual(exprs, rootExpr) 23 | 24 | const parsed = parseReply(rootExpr, ":proof-search") 25 | assert.deepEqual(parsed, expected) 26 | }) 27 | 28 | it("can parse a failure sexp.", () => { 29 | const sexp = `(:return (:error "No more results") 4)` 30 | const payload: S_Exp.ProofSearch = [":error", "No more results"] 31 | const rootExpr: RootExpr = [":return", payload, 4] 32 | const expected: FinalReply.ProofSearch = { 33 | err: "No more results", 34 | id: 4, 35 | ok: false, 36 | type: ":return", 37 | } 38 | 39 | const tokens = new TokenIter(sexp) 40 | const exprs = parse(tokens) as RootExpr 41 | assert.deepEqual(exprs, rootExpr) 42 | 43 | const parsed = parseReply(rootExpr, ":proof-search-next") 44 | assert.deepEqual(parsed, expected) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/parser/proof-search.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :proof-search reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "0") 2)` 10 | const payload: S_Exp.ProofSearch = [":ok", "0"] 11 | const rootExpr: RootExpr = [":return", payload, 2] 12 | const expected: FinalReply.ProofSearch = { 13 | id: 2, 14 | ok: true, 15 | solution: "0", 16 | type: ":return", 17 | } 18 | 19 | const tokens = new TokenIter(sexp) 20 | const exprs = parse(tokens) as RootExpr 21 | assert.deepEqual(exprs, rootExpr) 22 | 23 | const parsed = parseReply(rootExpr, ":proof-search") 24 | assert.deepEqual(parsed, expected) 25 | }) 26 | 27 | // Idris 2 only. 28 | it("can parse a failure sexp.", () => { 29 | const sexp = `(:return (:error "Not a searchable hole") 2)` 30 | const payload: S_Exp.ProofSearch = [":error", "Not a searchable hole"] 31 | const rootExpr: RootExpr = [":return", payload, 2] 32 | const expected: FinalReply.ProofSearch = { 33 | err: "Not a searchable hole", 34 | id: 2, 35 | ok: false, 36 | type: ":return", 37 | } 38 | 39 | const tokens = new TokenIter(sexp) 40 | const exprs = parse(tokens) as RootExpr 41 | assert.deepEqual(exprs, rootExpr) 42 | 43 | const parsed = parseReply(rootExpr, ":proof-search") 44 | assert.deepEqual(parsed, expected) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/parser/repl-completions.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :repl-completions reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok (("getArgs" "getChar" "getEnv" "getErrno" "getFileError" "getGoal" "getGuess" "getHoles" "getLine" "getLine'" "getMyVM" "getName" "getProof" "getSourceLocation" "getStringFromBuffer" "getWitness") "")) 2)` 10 | const payload: S_Exp.ReplCompletions = [ 11 | ":ok", 12 | [ 13 | [ 14 | "getArgs", 15 | "getChar", 16 | "getEnv", 17 | "getErrno", 18 | "getFileError", 19 | "getGoal", 20 | "getGuess", 21 | "getHoles", 22 | "getLine", 23 | "getLine'", 24 | "getMyVM", 25 | "getName", 26 | "getProof", 27 | "getSourceLocation", 28 | "getStringFromBuffer", 29 | "getWitness", 30 | ], 31 | "", 32 | ], 33 | ] 34 | const rootExpr: RootExpr = [":return", payload, 2] 35 | const expected: FinalReply.ReplCompletions = { 36 | completions: [ 37 | "getArgs", 38 | "getChar", 39 | "getEnv", 40 | "getErrno", 41 | "getFileError", 42 | "getGoal", 43 | "getGuess", 44 | "getHoles", 45 | "getLine", 46 | "getLine'", 47 | "getMyVM", 48 | "getName", 49 | "getProof", 50 | "getSourceLocation", 51 | "getStringFromBuffer", 52 | "getWitness", 53 | ], 54 | id: 2, 55 | ok: true, 56 | type: ":return", 57 | } 58 | 59 | const tokens = new TokenIter(sexp) 60 | const exprs = parse(tokens) as RootExpr 61 | assert.deepEqual(exprs, rootExpr) 62 | 63 | const parsed = parseReply(rootExpr, ":repl-completions") 64 | assert.deepEqual(parsed, expected) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /test/parser/type-at.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { TokenIter } from "../../src/parser/lexer" 4 | import { RootExpr, S_Exp } from "../../src/s-exps" 5 | import { FinalReply } from "../../src/reply" 6 | 7 | describe("Parsing :type-of reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "b : Bool") 23)` 10 | const payload: S_Exp.TypeAtOk = [":ok", "b : Bool"] 11 | 12 | const rootExpr: RootExpr = [":return", payload, 23] 13 | const expected: FinalReply.TypeAt = { 14 | id: 23, 15 | ok: true, 16 | type: ":return", 17 | typeAt: "b : Bool", 18 | } 19 | 20 | const tokens = new TokenIter(sexp) 21 | const exprs = parse(tokens) as RootExpr 22 | assert.deepEqual(exprs, rootExpr) 23 | 24 | const parsed = parseReply(rootExpr, ":type-at") 25 | assert.deepEqual(parsed, expected) 26 | }) 27 | 28 | it("can parse a failure sexp", () => { 29 | const sexp = `(:return (:error "Undefined name luna. \n\n(interactive):1:1--1:1\n |\n 1 | 2 * 2\n | \n") 23)` 30 | const payload: S_Exp.TypeAtErr = [ 31 | ":error", 32 | "Undefined name luna. \n\n(interactive):1:1--1:1\n |\n 1 | 2 * 2\n | \n", 33 | ] 34 | 35 | const rootExpr: RootExpr = [":return", payload, 23] 36 | const expected: FinalReply.TypeAt = { 37 | err: 38 | "Undefined name luna. \n\n(interactive):1:1--1:1\n |\n 1 | 2 * 2\n | \n", 39 | id: 23, 40 | ok: false, 41 | type: ":return", 42 | } 43 | 44 | const tokens = new TokenIter(sexp) 45 | const exprs = parse(tokens) as RootExpr 46 | assert.deepEqual(exprs, rootExpr) 47 | 48 | const parsed = parseReply(rootExpr, ":type-at") 49 | assert.deepEqual(parsed, expected) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/parser/type-of.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { TokenIter } from "../../src/parser/lexer" 4 | import { RootExpr, S_Exp } from "../../src/s-exps" 5 | import { FinalReply } from "../../src/reply" 6 | 7 | describe("Parsing :type-of reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok "Cat : Type" ((0 3 ((:name "Example.Cat") (:implicit :False) (:key "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl") (:decor :type) (:doc-overview "") (:type "Type") (:namespace "Example"))) (6 4 ((:decor :type) (:type "Type") (:doc-overview "The type of types") (:name "Type"))) (6 4 ((:tt-term "AAAAAAAAAAAHAAAAAAAAAAAbLi8uL3Rlc3QvcmVzb3VyY2VzL3Rlc3QuaWRyAAAAAAAAABQ="))))) 2)` 10 | const payload: S_Exp.TypeOfOk = [ 11 | ":ok", 12 | "Cat : Type", 13 | [ 14 | [ 15 | 0, 16 | 3, 17 | [ 18 | [":name", "Example.Cat"], 19 | [":implicit", ":False"], 20 | [":key", "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl"], 21 | [":decor", ":type"], 22 | [":doc-overview", ""], 23 | [":type", "Type"], 24 | [":namespace", "Example"], 25 | ], 26 | ], 27 | [ 28 | 6, 29 | 4, 30 | [ 31 | [":decor", ":type"], 32 | [":type", "Type"], 33 | [":doc-overview", "The type of types"], 34 | [":name", "Type"], 35 | ], 36 | ], 37 | [ 38 | 6, 39 | 4, 40 | [ 41 | [ 42 | ":tt-term", 43 | "AAAAAAAAAAAHAAAAAAAAAAAbLi8uL3Rlc3QvcmVzb3VyY2VzL3Rlc3QuaWRyAAAAAAAAABQ=", 44 | ], 45 | ], 46 | ], 47 | ], 48 | ] 49 | 50 | const rootExpr: RootExpr = [":return", payload, 2] 51 | const expected: FinalReply.TypeOf = { 52 | typeOf: "Cat : Type", 53 | metadata: [ 54 | { 55 | length: 3, 56 | metadata: { 57 | decor: ":type", 58 | docOverview: "", 59 | implicit: ":False", 60 | key: "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl", 61 | name: "Example.Cat", 62 | namespace: "Example", 63 | type: "Type", 64 | }, 65 | start: 0, 66 | }, 67 | { 68 | length: 4, 69 | metadata: { 70 | decor: ":type", 71 | docOverview: "The type of types", 72 | name: "Type", 73 | ttTerm: "AAAAAAAAAAAHAAAAAAAAAAAbLi8uL3Rlc3QvcmVzb3VyY2VzL3Rlc3QuaWRyAAAAAAAAABQ=", 74 | type: "Type", 75 | }, 76 | start: 6, 77 | }, 78 | ], 79 | id: 2, 80 | ok: true, 81 | type: ":return", 82 | } 83 | 84 | const tokens = new TokenIter(sexp) 85 | const exprs = parse(tokens) as RootExpr 86 | assert.deepEqual(exprs, rootExpr) 87 | 88 | const parsed = parseReply(rootExpr, ":type-of") 89 | assert.deepEqual(parsed, expected) 90 | }) 91 | 92 | it("can parse a failure sexp", () => { 93 | const sexp = `(:return (:error "No such variable luna") 2)` 94 | const payload: S_Exp.TypeOfErr = [":error", "No such variable luna"] 95 | 96 | const rootExpr: RootExpr = [":return", payload, 2] 97 | const expected: FinalReply.TypeOf = { 98 | err: "No such variable luna", 99 | id: 2, 100 | ok: false, 101 | type: ":return", 102 | } 103 | 104 | const tokens = new TokenIter(sexp) 105 | const exprs = parse(tokens) as RootExpr 106 | assert.deepEqual(exprs, rootExpr) 107 | 108 | const parsed = parseReply(rootExpr, ":type-of") 109 | assert.deepEqual(parsed, expected) 110 | }) 111 | }) 112 | -------------------------------------------------------------------------------- /test/parser/version.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :version reply", () => { 8 | it("can parse a success sexp.", () => { 9 | // current version on Nixos 2020-05-23 10 | const sexp = `(:return (:ok ((1 3 2) ())) 2)` 11 | const payload: S_Exp.Version = [":ok", [[1, 3, 2], []]] 12 | const rootExpr: RootExpr = [":return", payload, 2] 13 | const expected: FinalReply.Version = { 14 | id: 2, 15 | major: 1, 16 | minor: 3, 17 | ok: true, 18 | patch: 2, 19 | tags: [], 20 | type: ":return", 21 | } 22 | 23 | const tokens = new TokenIter(sexp) 24 | const exprs = parse(tokens) as RootExpr 25 | assert.deepEqual(exprs, rootExpr) 26 | 27 | const parsed = parseReply(rootExpr, ":version") 28 | assert.deepEqual(parsed, expected) 29 | }) 30 | 31 | it("can parse a success sexp with tags.", () => { 32 | // current version on Arch Linux 2020-05-23 33 | const sexp = `(:return (:ok ((1 3 2) ("git:PRE"))) 2)` 34 | const payload: S_Exp.Version = [":ok", [[1, 3, 2], ["git:PRE"]]] 35 | const rootExpr: RootExpr = [":return", payload, 2] 36 | const expected: FinalReply.Version = { 37 | id: 2, 38 | major: 1, 39 | minor: 3, 40 | ok: true, 41 | patch: 2, 42 | tags: ["git:PRE"], 43 | type: ":return", 44 | } 45 | 46 | const tokens = new TokenIter(sexp) 47 | const exprs = parse(tokens) as RootExpr 48 | assert.deepEqual(exprs, rootExpr) 49 | 50 | const parsed = parseReply(rootExpr, ":version") 51 | assert.deepEqual(parsed, expected) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /test/parser/who-calls.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai" 2 | import { parse, parseReply } from "../../src/parser" 3 | import { FinalReply } from "../../src/reply" 4 | import { TokenIter } from "../../src/parser/lexer" 5 | import { RootExpr, S_Exp } from "../../src/s-exps" 6 | 7 | describe("Parsing :who-calls reply", () => { 8 | it("can parse a success sexp.", () => { 9 | const sexp = `(:return (:ok ((("Example.Cat" ((0 11 ((:name "Example.Cat") (:implicit :False) (:key "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl") (:decor :type) (:doc-overview "") (:type "Type") (:namespace "Example"))))) (("Example.Cas" ((0 11 ((:name "Example.Cas") (:implicit :False) (:key "AQAAAAAAAAAAA0NhcwAAAAAAAAABAAAAAAAAAAdFeGFtcGxl") (:decor :data) (:doc-overview "") (:type "Cat") (:namespace "Example"))))) ("Example.Luna" ((0 12 ((:name "Example.Luna") (:implicit :False) (:key "AQAAAAAAAAAABEx1bmEAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==") (:decor :data) (:doc-overview "") (:type "Cat") (:namespace "Example"))))) ("Example.Sherlock" ((0 16 ((:name "Example.Sherlock") (:implicit :False) (:key "AQAAAAAAAAAACFNoZXJsb2NrAAAAAAAAAAEAAAAAAAAAB0V4YW1wbGU=") (:decor :data) (:doc-overview "") (:type "Cat") (:namespace "Example"))))) ("Example.f" ((0 9 ((:name "Example.f") (:implicit :False) (:key "AQAAAAAAAAAAAWYAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==") (:decor :metavar) (:doc-overview "") (:type "Cat -> String") (:namespace "Example"))))) ("Example.getName" ((0 15 ((:name "Example.getName") (:implicit :False) (:key "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==") (:decor :function) (:doc-overview "") (:type "Cat -> String") (:namespace "Example"))))))))) 2)` 10 | const payload: S_Exp.WhoCalls = [ 11 | ":ok", 12 | [ 13 | [ 14 | [ 15 | "Example.Cat", 16 | [ 17 | [ 18 | 0, 19 | 11, 20 | [ 21 | [":name", "Example.Cat"], 22 | [":implicit", ":False"], 23 | [":key", "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl"], 24 | [":decor", ":type"], 25 | [":doc-overview", ""], 26 | [":type", "Type"], 27 | [":namespace", "Example"], 28 | ], 29 | ], 30 | ], 31 | ], 32 | [ 33 | [ 34 | "Example.Cas", 35 | [ 36 | [ 37 | 0, 38 | 11, 39 | [ 40 | [":name", "Example.Cas"], 41 | [":implicit", ":False"], 42 | [ 43 | ":key", 44 | "AQAAAAAAAAAAA0NhcwAAAAAAAAABAAAAAAAAAAdFeGFtcGxl", 45 | ], 46 | [":decor", ":data"], 47 | [":doc-overview", ""], 48 | [":type", "Cat"], 49 | [":namespace", "Example"], 50 | ], 51 | ], 52 | ], 53 | ], 54 | [ 55 | "Example.Luna", 56 | [ 57 | [ 58 | 0, 59 | 12, 60 | [ 61 | [":name", "Example.Luna"], 62 | [":implicit", ":False"], 63 | [ 64 | ":key", 65 | "AQAAAAAAAAAABEx1bmEAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 66 | ], 67 | [":decor", ":data"], 68 | [":doc-overview", ""], 69 | [":type", "Cat"], 70 | [":namespace", "Example"], 71 | ], 72 | ], 73 | ], 74 | ], 75 | [ 76 | "Example.Sherlock", 77 | [ 78 | [ 79 | 0, 80 | 16, 81 | [ 82 | [":name", "Example.Sherlock"], 83 | [":implicit", ":False"], 84 | [ 85 | ":key", 86 | "AQAAAAAAAAAACFNoZXJsb2NrAAAAAAAAAAEAAAAAAAAAB0V4YW1wbGU=", 87 | ], 88 | [":decor", ":data"], 89 | [":doc-overview", ""], 90 | [":type", "Cat"], 91 | [":namespace", "Example"], 92 | ], 93 | ], 94 | ], 95 | ], 96 | [ 97 | "Example.f", 98 | [ 99 | [ 100 | 0, 101 | 9, 102 | [ 103 | [":name", "Example.f"], 104 | [":implicit", ":False"], 105 | [ 106 | ":key", 107 | "AQAAAAAAAAAAAWYAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 108 | ], 109 | [":decor", ":metavar"], 110 | [":doc-overview", ""], 111 | [":type", "Cat -> String"], 112 | [":namespace", "Example"], 113 | ], 114 | ], 115 | ], 116 | ], 117 | [ 118 | "Example.getName", 119 | [ 120 | [ 121 | 0, 122 | 15, 123 | [ 124 | [":name", "Example.getName"], 125 | [":implicit", ":False"], 126 | [ 127 | ":key", 128 | "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 129 | ], 130 | [":decor", ":function"], 131 | [":doc-overview", ""], 132 | [":type", "Cat -> String"], 133 | [":namespace", "Example"], 134 | ], 135 | ], 136 | ], 137 | ], 138 | ], 139 | ], 140 | ], 141 | ] 142 | const rootExpr: RootExpr = [":return", payload, 2] 143 | const expected: FinalReply.WhoCalls = { 144 | callee: { 145 | metadata: [ 146 | { 147 | length: 11, 148 | metadata: { 149 | decor: ":type", 150 | docOverview: "", 151 | implicit: ":False", 152 | key: "AQAAAAAAAAAAA0NhdAAAAAAAAAABAAAAAAAAAAdFeGFtcGxl", 153 | name: "Example.Cat", 154 | namespace: "Example", 155 | type: "Type", 156 | }, 157 | start: 0, 158 | }, 159 | ], 160 | name: "Example.Cat", 161 | }, 162 | references: [ 163 | { 164 | metadata: [ 165 | { 166 | length: 11, 167 | metadata: { 168 | decor: ":data", 169 | docOverview: "", 170 | implicit: ":False", 171 | key: "AQAAAAAAAAAAA0NhcwAAAAAAAAABAAAAAAAAAAdFeGFtcGxl", 172 | name: "Example.Cas", 173 | namespace: "Example", 174 | type: "Cat", 175 | }, 176 | start: 0, 177 | }, 178 | ], 179 | name: "Example.Cas", 180 | }, 181 | { 182 | metadata: [ 183 | { 184 | length: 12, 185 | metadata: { 186 | decor: ":data", 187 | docOverview: "", 188 | implicit: ":False", 189 | key: "AQAAAAAAAAAABEx1bmEAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 190 | name: "Example.Luna", 191 | namespace: "Example", 192 | type: "Cat", 193 | }, 194 | start: 0, 195 | }, 196 | ], 197 | name: "Example.Luna", 198 | }, 199 | { 200 | metadata: [ 201 | { 202 | length: 16, 203 | metadata: { 204 | decor: ":data", 205 | docOverview: "", 206 | implicit: ":False", 207 | key: "AQAAAAAAAAAACFNoZXJsb2NrAAAAAAAAAAEAAAAAAAAAB0V4YW1wbGU=", 208 | name: "Example.Sherlock", 209 | namespace: "Example", 210 | type: "Cat", 211 | }, 212 | start: 0, 213 | }, 214 | ], 215 | name: "Example.Sherlock", 216 | }, 217 | { 218 | metadata: [ 219 | { 220 | length: 9, 221 | metadata: { 222 | decor: ":metavar", 223 | docOverview: "", 224 | implicit: ":False", 225 | key: "AQAAAAAAAAAAAWYAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 226 | name: "Example.f", 227 | namespace: "Example", 228 | type: "Cat -> String", 229 | }, 230 | start: 0, 231 | }, 232 | ], 233 | name: "Example.f", 234 | }, 235 | { 236 | metadata: [ 237 | { 238 | length: 15, 239 | metadata: { 240 | decor: ":function", 241 | docOverview: "", 242 | implicit: ":False", 243 | key: "AQAAAAAAAAAAB2dldE5hbWUAAAAAAAAAAQAAAAAAAAAHRXhhbXBsZQ==", 244 | name: "Example.getName", 245 | namespace: "Example", 246 | type: "Cat -> String", 247 | }, 248 | start: 0, 249 | }, 250 | ], 251 | name: "Example.getName", 252 | }, 253 | ], 254 | id: 2, 255 | ok: true, 256 | type: ":return", 257 | } 258 | const tokens = new TokenIter(sexp) 259 | const exprs = parse(tokens) as RootExpr 260 | assert.deepEqual(exprs, rootExpr) 261 | const parsed = parseReply(rootExpr, ":who-calls") 262 | assert.deepEqual(parsed, expected) 263 | }) 264 | 265 | it("can parse an empty sexp.", () => { 266 | const sexp = `(:return (:ok ()) 2)` 267 | const payload: S_Exp.WhoCalls = [":ok", []] 268 | const rootExpr: RootExpr = [":return", payload, 2] 269 | const expected: FinalReply.WhoCalls = { 270 | callee: null, 271 | references: [], 272 | id: 2, 273 | ok: true, 274 | type: ":return", 275 | } 276 | 277 | const tokens = new TokenIter(sexp) 278 | const exprs = parse(tokens) as RootExpr 279 | assert.deepEqual(exprs, rootExpr) 280 | 281 | const parsed = parseReply(rootExpr, ":who-calls") 282 | assert.deepEqual(parsed, expected) 283 | }) 284 | }) 285 | -------------------------------------------------------------------------------- /test/resources/test-v2.idr: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | import Data.Vect 4 | 5 | data Cat = Cas | Luna | Sherlock 6 | 7 | f : (cat: Cat) -> String 8 | 9 | getName : (cat: Cat) -> String 10 | getName Cas = "Cas" 11 | getName Luna = "Luna" 12 | getName Sherlock = "Sherlock" 13 | 14 | plusTwo : (n: Nat) -> Nat 15 | plusTwo n = ?plusTwo_rhs 16 | 17 | g : (n: Nat) -> (b: Bool) -> String 18 | g n b = ?g_rhs 19 | 20 | num : Nat 21 | num = ?n_rhs 22 | 23 | append : Vect n a -> Vect m a -> Vect (n + m) a 24 | -------------------------------------------------------------------------------- /test/resources/test-v2.lidr: -------------------------------------------------------------------------------- 1 | This is some documentation. 2 | 3 | More documentation. 4 | 5 | > module Main 6 | > 7 | > import Data.Vect 8 | > 9 | > data Cat = Cas | Luna | Sherlock 10 | > 11 | > f : (cat: Cat) -> String 12 | > 13 | > getName : (cat: Cat) -> String 14 | > getName Cas = "Cas" 15 | > getName Luna = "Luna" 16 | > getName Sherlock = "Sherlock" 17 | 18 | An intermission. 19 | 20 | > plusTwo : (n: Nat) -> Nat 21 | > plusTwo n = ?plusTwo_rhs 22 | > 23 | > g : (n: Nat) -> (b: Bool) -> String 24 | > g n b = ?g_rhs 25 | > 26 | > num : Nat 27 | > num = ?n_rhs 28 | > 29 | > append : Vect n a -> Vect m a -> Vect (n + m) a 30 | 31 | Some closing thoughts. 32 | -------------------------------------------------------------------------------- /test/resources/test-v2.md: -------------------------------------------------------------------------------- 1 | # Markdown! 2 | 3 | *more* _markdown_ 4 | 5 | [text](link) 6 | 7 | ```idris 8 | module Main 9 | 10 | import Data.Vect 11 | 12 | data Cat = Cas | Luna | Sherlock 13 | 14 | f : (cat: Cat) -> String 15 | 16 | getName : (cat: Cat) -> String 17 | getName Cas = "Cas" 18 | getName Luna = "Luna" 19 | getName Sherlock = "Sherlock" 20 | ``` 21 | 22 | ## Section 2 23 | 24 | ~~~idris2 25 | plusTwo : (n: Nat) -> Nat 26 | plusTwo n = ?plusTwo_rhs 27 | 28 | g : (n: Nat) -> (b: Bool) -> String 29 | g n b = ?g_rhs 30 | 31 | num : Nat 32 | num = ?n_rhs 33 | 34 | append : Vect n a -> Vect m a -> Vect (n + m) a 35 | 36 | ~~~ 37 | 38 | The block has to include `idris` something. It can be idris, idris2, idris3 (future proofing), etc. 39 | -------------------------------------------------------------------------------- /test/resources/test.idr: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | data Cat = Cas | Luna | Sherlock 4 | 5 | f : (cat: Cat) -> String 6 | 7 | getName : (cat: Cat) -> String 8 | getName Cas = "Cas" 9 | getName Luna = "Luna" 10 | 11 | plusTwo : (n: Nat) -> Nat 12 | plusTwo n = plus 2 n 13 | 14 | g : (n: Nat) -> (b: Bool) -> String 15 | g n b = ?g_rhs 16 | 17 | n : Nat 18 | n = ?n_rhs 19 | -------------------------------------------------------------------------------- /test/resources/test.lidr: -------------------------------------------------------------------------------- 1 | This is some documentation. 2 | 3 | More documentation. 4 | 5 | > module Main 6 | > 7 | > data Cat = Cas | Luna | Sherlock 8 | > 9 | > f : (cat: Cat) -> String 10 | > 11 | > getName : (cat: Cat) -> String 12 | > getName Cas = "Cas" 13 | > getName Luna = "Luna" 14 | 15 | An intermission. 16 | 17 | > plusTwo : (n: Nat) -> Nat 18 | > plusTwo n = plus 2 n 19 | > 20 | > g : (n: Nat) -> (b: Bool) -> String 21 | > g n b = ?g_rhs 22 | > 23 | > n : Nat 24 | > n = ?n_rhs 25 | 26 | Some closing thoughts. 27 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, rmdirSync, rmSync, stat } from "fs" 2 | 3 | /** 4 | * Remove all instances of a key, recursively, within the object. This is 5 | * necessary for the tests, to remove random values. 6 | */ 7 | export const omitKeys = (o: any, omit: string[]): any => { 8 | const recur = (next: any) => omitKeys(next, omit) 9 | if (Array.isArray(o)) return o.map(recur) 10 | else if (isObject(o)) 11 | return Object.entries(o).reduce( 12 | (acc, [k, v]) => (omit.includes(k) ? acc : { ...acc, [k]: recur(v) }), 13 | {} 14 | ) 15 | else return o 16 | } 17 | 18 | const isObject = (o: any): boolean => 19 | typeof o === "object" && o !== null && !Array.isArray(o) 20 | 21 | // The build files screw up the tests when running against files with similar names. 22 | export const clean = () => { 23 | rmSync("build/", { force: true, recursive: true }) 24 | rmSync("test/resources/test.ibc", { force: true }) 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "declaration": true, 6 | "outDir": "build", 7 | "lib": ["es6"], 8 | "sourceMap": true, 9 | "strict": true, 10 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 11 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 12 | "noUnusedParameters": true /* Report errors on unused parameters. */ 13 | }, 14 | "include": ["src"], 15 | "exclude": ["node_modules"] 16 | } 17 | --------------------------------------------------------------------------------