diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..43a02784d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,112 @@ +name: Build documentation + +on: + push: + branches: + - "main" + pull_request: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "refman" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: "20" + + - name: Install npm dependencies + run: | + make -C doc ecproof-deps + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install Python dependencies + run: | + make -C doc sphinx-deps + + - name: Set-up OCaml + uses: ocaml/setup-ocaml@v3 + with: + ocaml-compiler: 5.4 + opam-disable-sandboxing: true + dune-cache: true + + - name: Install EasyCrypt dependencies + run: | + opam pin add -n easycrypt . + opam install --deps-only --depext-only --confirm-level=unsafe-yes easycrypt + opam install --deps-only easycrypt + + - name: Compile & Install EasyCrypt + run: | + opam exec -- make PROFILE=release install + + - name: Build Sphinx HTML + run: | + opam exec -- make -C doc ecproof-bundle sphinx-html + + - name: Upload documentation (artifact) + uses: actions/upload-artifact@v6 + with: + name: refman + path: doc/_build/html + + deploy: + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - name: Download documentation (artifact) + uses: actions/download-artifact@v7 + with: + name: refman + path: _refman + + - name: Deploy documentation + env: + PAGES_TOKEN: ${{ secrets.PAGES_REPO_TOKEN }} + PAGES_REPO: EasyCrypt/refman + TARGET_DIR: refman + BUILD_DIR: _refman + + run: | + set -euo pipefail + + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + git clone --depth 1 https://x-access-token:${PAGES_TOKEN}@github.com/${PAGES_REPO}.git pages-repo + + rm -rf "pages-repo/${TARGET_DIR}" + mkdir -p "pages-repo/${TARGET_DIR}" + touch "pages-repo/${TARGET_DIR}"/.keep + + cp -a "${BUILD_DIR}/." "pages-repo/${TARGET_DIR}/" + + git -C pages-repo add -A + + if git -C pages-repo diff --cached --quiet; then + echo "No changes to deploy." + exit 0 + fi + + git -C pages-repo commit -m "Update docs: ${GITHUB_REPOSITORY}@${GITHUB_SHA}" + git -C pages-repo push origin main diff --git a/INSTALL.md b/INSTALL.md index a9808fe4f..99a9a91de 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -522,6 +522,9 @@ proceed to [install EasyCrypt from Source](#installing-easycrypt-from-source). - [OCaml ini-files](https://opam.ocaml.org/packages/ocaml-inifiles/) (version >= 1.2) Additional resources: - http://archive.ubuntu.com/ubuntu/pool/universe/o/ocaml-inifiles +- [OCaml Markdown](https://github.com/gildor478/ocaml-markdown) + Additional resources: + - https://opam.ocaml.org/packages/markdown - [Python3](https://www.python.org/downloads) You also need to install the following libraries: - [Python3 YAML](https://pyyaml.org/wiki/PyYAMLDocumentation) diff --git a/assets/.gitignore b/assets/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/assets/styles/styles.css b/assets/styles/styles.css new file mode 100644 index 000000000..e3bdc9944 --- /dev/null +++ b/assets/styles/styles.css @@ -0,0 +1,228 @@ +/* General Styling */ +/* Body */ +body { + font-family: "-apple-system", "BlinkMacSystemFont", "Roboto", "Arial", sans-serif; + line-height: 1.2; + font-size: 16px; + margin: 0; + padding: 0; + color: #333; + background-color: #f9f9f9; +} + +/* Code blocks */ +pre { + font-family: "Fira Code", "Consolas", monospace; + font-size: 1rem; + padding: 5px; + border-radius: 1px; + color: #2d2d2d; + background-color: #ecf0f1; +} + +/* Inline code */ +code { + font-family: "Fira Code", "Consolas", monospace; + font-size: 1rem; + color: #d6336c; +} + +/* Headings */ +h1, h2, h3, h4, h5, h6 { + font-family: "Roboto", "Arial", sans-serif; + font-weight: 600; + color: #1a1a1a; + margin-bottom: 0.5em; +} + +h1 { + font-size: 2.25rem; +} +h2 { + font-size: 2rem; +} +h3 { + font-size: 1.75rem; +} +h4 { + font-size: 1.5rem; +} +h5 { + font-size: 1.25rem; +} +h6 { + font-size: 1rem; +} + +/* Links */ +a { + font-family: "Roboto", "Arial", sans-serif; + color: #007bff; + text-decoration: none; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +.serif-text { + font-family: "Times New Roman", "Times", serif; + font-size: 1rem; + color: #333; +} + +/* Specific styling */ + +/* Sidebar */ +.sidebar { + width: 200px; + background-color: #2c3e50; + color: #ecf0f1; + position: fixed; + height: 100%; + overflow: auto; +} + +.sidebar-title { + padding: 20px; + color: #ecf0f1; + background-color: #34495e; + margin-bottom: 20px; +} + +.sidebar-title h2 { + font-size: 1.5em; + margin-bottom: 5px; + color: #ecf0f1; +} + +.sidebar-title .sidebar-title-theory { + font-size: 1.2em; + color: #3498db; +} + +.sidebar-title-theory { + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; +} + +.sidebar-elems { + padding: 20px; +} + +.sidebar-section-list { + list-style: none; + padding: 0; +} + +.sidebar-section-list li { + margin: 15px 0; +} + +.sidebar-section-list li a { + color: #ecf0f1; + font-weight: bold; +} + +/* Main content */ +main { + margin-left: 220px; + padding: 20px; + max-width: 960px; +} + +.page-heading-container { + border-bottom: 2px solid #ddd; + padding-bottom: 5px; + margin-bottom: 20px; +} + +.page-heading-container .page-heading { + margin-block-end: 5px; +} + +.page-heading-container .page-subheading { + margin-block-start: 0px; + margin-block-end: 5px; + font-size: 1.2em; +} + +/* Sections */ +.item-section { + margin-bottom: 40px; +} + +.section-heading { + color: #34495e; + border-bottom: 2px solid #ddd; + padding-bottom: 10px; + margin-bottom: 20px; +} + +/* Item lists */ +.item-list { + list-style: none; + padding: 0; +} + +.item-entry { + display: flex; + flex-direction: column; + margin-bottom: 20px; +} + +.item-name-desc-container { + display: flex; + align-items: flex-start; +} + +.item-name { + width: 200px; + font-weight: bold; + color: #2980b9; + white-space: normal; + overflow-wrap: break-word; +} + +.item-basic-desc { + flex: 1; + margin-left: 10px; +} + +.item-basic-desc p { + margin-top: 0px; +} + +.item-details { + margin-left: 210px; +} + +.item-details summary { + cursor: pointer; + color: #3498db; + font-weight: bold; +} + +.item-details summary:hover { + text-decoration: underline; +} + +.item-details-par { + margin-top: 10px; +} + +/* Source code */ +pre.source { + border: 2px solid #bdc3c7; + padding: 10px; + border-radius: 5px; + overflow-x: auto; + white-space: pre-wrap; +} + +/* Introduction section */ +.intro-section { + margin-bottom: 40px; +} diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 000000000..88503a8c6 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +_build/ diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 000000000..bb8b8c368 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,45 @@ +# -*- Makefile -*- + +# ------------------------------------------------------------------------ +SPHINXBUILD ?= sphinx-build +SPHINXOPTS ?= +SOURCEDIR = . +BUILDDIR = _build +NPM ?= npm + +# ------------------------------------------------------------------------ +.PHONY: + +default: + @echo "make [ecproof-deps | ecproof-bundle| sphinx-html]" >&2 + +# ------------------------------------------------------------------------ +.PHONY: sphinx-help sphinx-deps __force__ + +sphinx-help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(SPHINXOPTS) + +sphinx-deps: + pip install -r requirements.txt + +sphinx-%: __force__ + @$(SPHINXBUILD) -M $* "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(SPHINXOPTS) + +# ------------------------------------------------------------------------ +.PHONY: ecproof-deps ecproof-bundle + +ECPROOFDIR = extensions/ecproofs/proofnav + +ecproof-deps: + $(NPM) --prefix="$(ECPROOFDIR)" install + +ecproof-bundle: + $(NPM) --prefix="$(ECPROOFDIR)" run build + +# ------------------------------------------------------------------------ +clean: + rm -rf _build + rm -rf "$(ECPROOFDIR)"/dist + +mrproper: clean + rm -rf "$(ECPROOFDIR)"/node_modules diff --git a/doc/_static/.keep b/doc/_static/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 000000000..5ba1204ea --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,39 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import pathlib +import sys + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'EasyCrypt refman' +copyright = '2026, EasyCrypt development team' +author = 'EasyCrypt development team' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +EXTENSIONS = pathlib.Path('extensions').resolve() +for x in ['ecpygment', 'ecproofs']: + sys.path.append(str(EXTENSIONS / x)) + +extensions = [ + 'sphinx_rtd_theme', + 'sphinx_design', + 'ecpygment', + 'ecproofs', +] + +highlight_language = 'easycrypt' + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] diff --git a/doc/extensions/ecproofs/ecproofs.py b/doc/extensions/ecproofs/ecproofs.py new file mode 100644 index 000000000..6efe0f445 --- /dev/null +++ b/doc/extensions/ecproofs/ecproofs.py @@ -0,0 +1,142 @@ +# -------------------------------------------------------------- +from __future__ import annotations + +import docutils as du + +import sphinx.application as sa +import sphinx.errors as se +import sphinx.util as su + +import bisect +import json +import os +import re +import subprocess as subp +import tempfile + +# ====================================================================== +ROOT = os.path.dirname(__file__) + +# ====================================================================== +class ProofnavNode(du.nodes.General, du.nodes.Element): + @staticmethod + def visit_proofnav_node_html(self, node: ProofnavNode): + pass + + @staticmethod + def depart_proofnav_node_html(self, node: ProofnavNode): + uid = node["uid"] + json = node["json"] + + html = f""" +
+
+ +
+""" + + self.body.append(html) + +# ====================================================================== +class EasyCryptProofDirective(su.docutils.SphinxDirective): + has_content = True + + option_spec = { + 'title': su.docutils.directives.unchanged, + } + + def run(self): + env = self.state.document.settings.env + + rawcode = '\n'.join(self.content) + '\n' + + # Find the trap + if (trap := re.search(r'\(\*\s*\$\s*\*\)\s*', rawcode, re.MULTILINE)) is None: + raise se.SphinxError('Cannot find the trap') + code = rawcode[:trap.start()] + rawcode[trap.end():] + + # Find the trap sentence number + sentences = [ + m.end() - 1 + for m in re.finditer(r'\.(\s+|\$)', code) + ] + sentence = bisect.bisect_left(sentences, trap.start()) + + # Run EasyCrypt and extract the proof trace + with tempfile.TemporaryDirectory(delete = False) as tmpdir: + ecfile = os.path.join(tmpdir, 'input.ec') + ecofile = os.path.join(tmpdir, 'input.eco') + with open(ecfile, 'w') as ecstream: + ecstream.write(code) + subp.check_call( + ['easycrypt', 'compile', '-pragmas', 'Proofs:weak', '-trace', ecfile], + stdout = subp.DEVNULL, + stderr = subp.DEVNULL, + ) + with open(ecofile) as ecostream: + eco = json.load(ecostream) + + serial = env.new_serialno("proofnav") + uid = f"proofnav-{serial}" + + # Create widget metadata + data = dict() + + data["source"] = code + data["sentenceEnds"] = [x["position"] for x in eco["trace"][1:]] + data["sentences"] = [ + dict(goals = x["goals"], message = x["messages"]) + for x in eco["trace"][1:] + ] + data["initialSentence"] = sentence - 1 + + if 'title' in self.options: + data['title'] = self.options['title'] + + node = ProofnavNode() + node["uid"] = uid + node["json"] = json.dumps( + data, ensure_ascii = False, separators = (",", ":"), indent = 2) + + return [node] + +# ====================================================================== +def on_builder_inited(app: sa.Sphinx): + out_dir = os.path.join(app.outdir, "_static", "proofnav") + os.makedirs(out_dir, exist_ok = True) + + js = os.path.join(ROOT, "proofnav", "dist", "proofnav.bundle.js") + css = os.path.join(ROOT, "proofnav", "proofnav.css") + + if not os.path.exists(js): + raise se.SphinxError( + "proofnav: bundle not found. Run the frontend build to generate " + f"{js}" + ) + + su.fileutil.copy_asset(js, out_dir) + su.fileutil.copy_asset(js + ".map", out_dir) + su.fileutil.copy_asset(css, out_dir) + +# ====================================================================== +def setup(app: sa.Sphinx) -> su.typing.ExtensionMetadata: + app.add_node( + ProofnavNode, + html = ( + ProofnavNode.visit_proofnav_node_html, + ProofnavNode.depart_proofnav_node_html, + ) + ) + + app.add_js_file("proofnav/proofnav.bundle.js", defer = "defer") + app.add_css_file("proofnav/proofnav.css") + + app.connect("builder-inited", on_builder_inited) + + app.add_directive('ecproof', EasyCryptProofDirective) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/doc/extensions/ecproofs/proofnav/.gitignore b/doc/extensions/ecproofs/proofnav/.gitignore new file mode 100644 index 000000000..3d2bc6269 --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/.gitignore @@ -0,0 +1,2 @@ +/dist/ +/node_modules/ diff --git a/doc/extensions/ecproofs/proofnav/easycrypt.ts b/doc/extensions/ecproofs/proofnav/easycrypt.ts new file mode 100644 index 000000000..4680cd7c9 --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/easycrypt.ts @@ -0,0 +1,114 @@ +import { StreamLanguage } from "@codemirror/language" +import type { StreamParser } from "@codemirror/language" + +type KeywordGroups = Record +type TagMap = Record + +const keywords: KeywordGroups = { + bytac : ['exact', 'assumption', 'smt', 'coq', 'check', 'edit', 'fix', 'by', 'reflexivity', 'done', 'solve'], + dangerous : ['admit', 'admitted'], + global : ['axiom', 'axiomatized', 'lemma', 'realize', 'proof', 'qed', 'abort', 'goal', 'end', 'from', 'import', 'export', 'include', 'local', 'global', 'declare', 'hint', 'module', 'of', 'const', 'op', 'pred', 'inductive', 'notation', 'abbrev', 'require', 'theory', 'abstract', 'section', 'subtype', 'type', 'class', 'instance', 'print', 'search', 'locate', 'as', 'Pr', 'clone', 'with', 'rename', 'prover', 'timeout', 'why3', 'dump', 'remove', 'exit', 'Top', 'Self'], + internal : ['fail', 'time', 'undo', 'debug', 'pragma'], + prog : ['forall', 'exists', 'fun', 'glob', 'let', 'in', 'for', 'var', 'proc', 'if', 'is', 'match', 'then', 'else', 'elif', 'match', 'for', 'while', 'assert', 'return', 'res', 'equiv', 'hoare', 'ehoare', 'phoare', 'islossless', 'async'], + tactic : ['beta', 'iota', 'zeta', 'eta', 'logic', 'delta', 'simplify', 'cbv', 'congr', 'change', 'split', 'left', 'right', 'case', 'pose', 'gen', 'have', 'suff', 'elim', 'exlim', 'ecall', 'clear', 'wlog', 'idassign', 'apply', 'rewrite', 'rwnormal', 'subst', 'progress', 'trivial', 'auto', 'idtac', 'move', 'modpath', 'field', 'fieldeq', 'ring', 'ringeq', 'algebra', 'replace', 'transitivity', 'symmetry', 'seq', 'wp', 'sp', 'sim', 'skip', 'call', 'rcondt', 'rcondf', 'swap', 'cfold', 'rnd', 'rndsem', 'pr_bounded', 'bypr', 'byphoare', 'byehoare', 'byequiv', 'byupto', 'fel', 'conseq', 'exfalso', 'inline', 'outline', 'interleave', 'alias', 'weakmem', 'fission', 'fusion', 'unroll', 'splitwhile', 'kill', 'eager'], + tactical : ['try', 'first', 'last', 'do', 'expect'], +} + +const tags: TagMap = { + bytac : "annotation", + dangerous : "invalid", + global : "namespace", + internal : "invalid", + prog : "keyword", + tactic : "controlKeyword", + tactical : "controlOperator", +} + +function buildKeywordTagMap( + keywords: KeywordGroups, + tags: TagMap +): Record { + const result: Record = {} + + for (const [group, words] of Object.entries(keywords)) { + const tag = tags[group] + if (!tag) continue + + for (const word of words) { + result[word] = tag + } + } + + return result +} + +const keywordToTag = buildKeywordTagMap(keywords, tags) + +const identRE = /^[a-zA-Z_][A-Za-z0-9_']*/ +const numberRE = /^\d+/ +const punctRE = /^[()\[\]{};,.:]/ + +type State = { commentDepth: number } + +function eatNestedComment(stream: any, state: State): void { + while (!stream.eol()) { + if (stream.match("(*")) { + state.commentDepth++ + continue + } + if (stream.match("*)")) { + state.commentDepth-- + if (state.commentDepth <= 0) { + state.commentDepth = 0 + break + } + continue + } + stream.next() + } +} + +const parser: StreamParser = { + name: "easycrypt", + startState(): State { + return {commentDepth: 0} + }, + token(stream: any, state: State): string | null { + // Nested comment continuation + if (state.commentDepth > 0) { + eatNestedComment(stream, state) + return "comment" + } + + if (stream.eatSpace()) return null + + // Nested comment start + if (stream.match("(*")) { + state.commentDepth = 1 + eatNestedComment(stream, state) + return "comment" + } + + // Numbers + if (stream.match(numberRE)) { + return "number" + } + + // Identifiers / keywords + if (stream.match(identRE)) { + const word: string = stream.current() + return keywordToTag[word] ?? "variableName" + } + + // Punctuation + if (stream.match(punctRE)) { + return "punctuation" + } + + // Always make progress + stream.next() + return null + } +} + +export const easycryptHighlight = StreamLanguage.define(parser) diff --git a/doc/extensions/ecproofs/proofnav/esbuild.mjs b/doc/extensions/ecproofs/proofnav/esbuild.mjs new file mode 100644 index 000000000..61cbf24b4 --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/esbuild.mjs @@ -0,0 +1,22 @@ +import esbuild from "esbuild"; + +const watch = process.argv.includes("--watch"); + +const ctx = await esbuild.context({ + entryPoints: ["index.ts"], + bundle: true, + format: "iife", + target: ["es2019"], + outfile: "dist/proofnav.bundle.js", + sourcemap: true, + minify: true +}); + +if (watch) { + await ctx.watch(); + console.log("proofnav: watching..."); +} else { + await ctx.rebuild(); + await ctx.dispose(); + console.log("proofnav: built"); +} diff --git a/doc/extensions/ecproofs/proofnav/index.ts b/doc/extensions/ecproofs/proofnav/index.ts new file mode 100644 index 000000000..414e0110a --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/index.ts @@ -0,0 +1,46 @@ +import { createProofNavigator } from "./widget"; + +type ProofNavData = { + source: string; + sentenceEnds: number[]; + sentences: Array<{ goals?: string[]; message?: string | null }>; + initialSentence?: number; + title?: string; +}; + +function mountOne(mount: HTMLElement) { + const id = mount.id; + const dataEl = document.getElementById(id + "-data"); + if (!dataEl) return; + + let data: ProofNavData; + try { + data = JSON.parse(dataEl.textContent || "{}"); + } catch (e) { + mount.innerHTML = `
proofnav: invalid JSON
`; + return; + } + + const initialSentence = typeof data.initialSentence === "number" ? data.initialSentence : -1; + const title = typeof data.title === "string" ? data.title : undefined; + + createProofNavigator({ + parent: mount, + source: data.source, + sentenceEnds: data.sentenceEnds, + sentences: data.sentences, + initialSentence, + title, + }); +} + +function mountAll() { + const mounts = document.querySelectorAll(".proofnav-sphinx .proofnav-mount"); + mounts.forEach(mountOne); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", mountAll); +} else { + mountAll(); +} diff --git a/doc/extensions/ecproofs/proofnav/package-lock.json b/doc/extensions/ecproofs/proofnav/package-lock.json new file mode 100644 index 000000000..900f7747b --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/package-lock.json @@ -0,0 +1,614 @@ +{ + "name": "proofnav", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "proofnav", + "dependencies": { + "@codemirror/commands": "~6.10", + "@codemirror/language": "~6.12", + "@codemirror/state": "~6.5", + "@codemirror/view": "~6.39", + "@lezer/highlight": "~1.2" + }, + "devDependencies": { + "esbuild": "~0.27", + "typescript": "~5" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz", + "integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", + "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.39.11", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.11.tgz", + "integrity": "sha512-bWdeR8gWM87l4DB/kYSF9A+dVackzDb/V56Tq7QVrQ7rn86W0rgZFtlL3g3pem6AeGcb9NQNoy3ao4WpW4h5tQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz", + "integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.7.tgz", + "integrity": "sha512-wNIFWdSUfX9Jc6ePMzxSPVgTVB4EOfDIwLQLWASyiUdHKaMsiilj9bYiGkGQCKVodd0x6bgQCV207PILGFCF9Q==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + } + } +} diff --git a/doc/extensions/ecproofs/proofnav/package.json b/doc/extensions/ecproofs/proofnav/package.json new file mode 100644 index 000000000..421434db0 --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/package.json @@ -0,0 +1,20 @@ +{ + "name": "proofnav", + "private": true, + "type": "module", + "scripts": { + "build": "node esbuild.mjs", + "watch": "node esbuild.mjs --watch" + }, + "dependencies": { + "@codemirror/commands": "~6.10", + "@codemirror/state": "~6.5", + "@codemirror/view": "~6.39", + "@codemirror/language": "~6.12", + "@lezer/highlight": "~1.2" + }, + "devDependencies": { + "esbuild": "~0.27", + "typescript": "~5" + } +} diff --git a/doc/extensions/ecproofs/proofnav/proofnav.css b/doc/extensions/ecproofs/proofnav/proofnav.css new file mode 100644 index 000000000..cea082ad0 --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/proofnav.css @@ -0,0 +1,233 @@ +/* Scope everything under the directive wrapper to avoid theme conflicts */ +.proofnav-sphinx .proofnav-rtd{ + --pn-panel: #fcfcfc; + --pn-border: #e1e4e5; + --pn-text: #404040; + --pn-muted: #6a6a6a; + + /* more visible highlights */ + --pn-doneBg: #e6edf3; + --pn-curBg: #cfe3ff; + --pn-hoverBg:#e8f2ff; + + --pn-radius: 4px; + --pn-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + + font: inherit; + color: var(--pn-text); +} + +.proofnav-sphinx .proofnav-rtd.proofnav { + display: grid; + grid-template-rows: auto auto; + gap: 12px; + box-sizing: border-box; + min-width: 0; +} + +.proofnav-sphinx .proofnav-rtd .panel{ + border: 1px solid var(--pn-border); + border-radius: var(--pn-radius); + overflow: hidden; + background: var(--pn-panel); + min-width: 0; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__body{ + display: grid; + grid-template-rows: auto auto; + gap: 12px; +} + +.proofnav-sphinx .proofnav-rtd.pn-collapsed .proofnav__body{ + display: none; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__btnToggle{ + display: inline-flex; + align-items: center; + gap: 6px; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__btnToggle{ + padding: 4px 6px; + border-radius: 3px; + line-height: 1; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__sr{ + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + white-space: nowrap; + border: 0; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__chev{ + transition: transform 120ms ease; +} + +.proofnav-sphinx .proofnav-rtd.pn-collapsed .proofnav__chev{ + transform: rotate(-90deg); +} + +.proofnav-sphinx .proofnav-rtd .proofnav__sentencebar{ + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 8px 10px; + border-bottom: 1px solid var(--pn-border); + background: #fff; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__header{ + display:flex; + align-items:center; + justify-content: space-between; + padding: 8px 10px; + border-bottom: 1px solid var(--pn-border); + background: #fff; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__title{ + font-weight: 600; + font-size: 14px; + color: #2d2d2d; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__title{ + display: flex; + align-items: center; + gap: 8px; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__subtitle{ + font-size: 12px; + color: var(--pn-muted); + margin-left: 10px; + font-weight: 500; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__controls{ + display:flex; + gap: 8px; + flex-shrink: 0; +} + +.proofnav-sphinx .proofnav-rtd .proofnav__btn{ + appearance: none; + border: 1px solid var(--pn-border); + background: #fff; + color: var(--pn-text); + padding: 6px 9px; + border-radius: 3px; + cursor: pointer; + font-weight: 600; + font-size: 12px; +} +.proofnav-sphinx .proofnav-rtd .proofnav__btn:hover{ background: #f7f7f7; } + +.proofnav-sphinx .proofnav-rtd .proofnav__editor{ + height: auto; + background: #fff; + overflow: hidden; +} + +.proofnav-sphinx .proofnav-rtd .infoBody{ + padding: 10px; + display: grid; + grid-template-rows: auto auto; + gap: 10px; + box-sizing: border-box; +} + +.proofnav-sphinx .proofnav-rtd .box{ + border: 1px solid var(--pn-border); + border-radius: var(--pn-radius); + padding: 8px 10px; + background: #fff; + min-width: 0; +} + +.proofnav-sphinx .proofnav-rtd .tabs{ + display:flex; + gap: 6px; + flex-wrap: wrap; + margin-bottom: 6px; +} + +.proofnav-sphinx .proofnav-rtd .tab{ + border: 1px solid var(--pn-border); + background: #fff; + color: var(--pn-text); + padding: 4px 8px; + border-radius: 999px; + cursor: pointer; + font-weight: 600; + font-size: 12px; +} + +.proofnav-sphinx .proofnav-rtd .tab[aria-selected="true"]{ + background: #e8f0ff; + border-color: #c9d7ff; +} + +.proofnav-sphinx .proofnav-rtd .goal-sep{ + border-top: 1px solid var(--pn-border); + margin: 6px 0 8px 0; +} + +.proofnav-sphinx .proofnav-rtd pre{ + margin: 0; + white-space: pre-wrap; + word-break: break-word; + font-family: var(--pn-mono); + font-size: 11.5px; + color: var(--pn-text); +} + +.proofnav-sphinx .proofnav-rtd .empty{ + color: var(--pn-muted); + font-size: 13px; + font-weight: 600; +} + +/* sentence highlights */ +.proofnav-sphinx .proofnav-rtd .cm-sentenceDone{ background: var(--pn-doneBg); } +.proofnav-sphinx .proofnav-rtd .cm-sentenceHover{ background: var(--pn-hoverBg); } +.proofnav-sphinx .proofnav-rtd .cm-sentenceCurrent{ + background: var(--pn-curBg) !important; + box-shadow: inset 3px 0 0 rgba(32,94,255,.35); +} + +/* active sentence gutter */ +.proofnav-sphinx .proofnav-rtd .cm-activeSentenceGutter{ + background: #fbfbfb; + border-right: 1px solid var(--pn-border); + color: #2d2d2d; +} + +.proofnav-sphinx .proofnav-rtd .cm-activeSentenceMarker{ + width: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 12px; + user-select: none; +} + +/* pointer cursor only when hovering a sentence */ +.proofnav-sphinx .proofnav-rtd.pn-hovering .cm-content, +.proofnav-sphinx .proofnav-rtd.pn-hovering .cm-line, +.proofnav-sphinx .proofnav-rtd.pn-hovering .cm-gutters{ + cursor: pointer; +} diff --git a/doc/extensions/ecproofs/proofnav/widget.ts b/doc/extensions/ecproofs/proofnav/widget.ts new file mode 100644 index 000000000..e645807e5 --- /dev/null +++ b/doc/extensions/ecproofs/proofnav/widget.ts @@ -0,0 +1,461 @@ +import { + EditorState, + StateEffect, + StateField, + Range, + RangeSet, +} from "@codemirror/state"; + +import { + EditorView, + Decoration, + keymap, + gutter, + GutterMarker, + lineNumbers, +} from "@codemirror/view"; + +import { defaultKeymap } from "@codemirror/commands"; +import { syntaxHighlighting, HighlightStyle } from "@codemirror/language"; +import { tags as t } from "@lezer/highlight" + +import { easycryptHighlight } from "./easycrypt"; + +export type ProofSentence = { + goals?: string[]; + message?: string | null; +}; + +export type CreateProofNavigatorOptions = { + parent: HTMLElement; + source: string; + sentenceEnds: number[]; + sentences: ProofSentence[]; + initialSentence?: number; // allow -1 (before first) + collapsible?: boolean; // default true + initialCollapsed?: boolean; // default true + title?: string; // default "Proof Navigator" +}; + +export type ProofNavigatorHandle = { + view: EditorView; + setSentence: (idx: number, opts?: { scroll?: boolean }) => void; + getSentence: () => number; + collapse: () => void; + expand: () => void; + toggleCollapsed: () => void; + isCollapsed: () => boolean; +}; + +const rtdHighlight: HighlightStyle = HighlightStyle.define([ + { tag: t.keyword, color: "#005a9c", fontWeight: "600" }, + { tag: t.annotation, color: "#a10d2b", fontWeight: "600" }, + { tag: t.invalid, color: "#ff0000", fontWeight: "600" }, + { tag: t.namespace, color: "#b61295", fontWeight: "600" }, + { tag: t.keyword, color: "#005a9c", fontWeight: "600" }, + { tag: t.controlKeyword, color: "#005a9c", fontWeight: "600" }, + { tag: t.controlOperator, color: "#108401", fontWeight: "600" }, + { tag: [t.string, t.special(t.string)], color: "#1a7f37" }, + { tag: t.comment, color: "#6a737d", fontStyle: "italic" }, + { tag: t.number, color: "#b31d28" }, + { tag: t.variableName, color: "#24292f" }, + { tag: [t.operator, t.punctuation], color: "#57606a" }, +]); + +export function createProofNavigator(opts: CreateProofNavigatorOptions): ProofNavigatorHandle { + const { + parent, + source, + sentenceEnds, + sentences, + initialSentence = -1, + collapsible = true, + initialCollapsed = true, + title = "Proof Navigator", + } = opts; + + if (!parent) throw new Error("parent is required"); + if (!Array.isArray(sentenceEnds) || sentenceEnds.length === 0) { + throw new Error("sentenceEnds must be non-empty"); + } + if (!Array.isArray(sentences) || sentences.length !== sentenceEnds.length) { + throw new Error("sentences length mismatch"); + } + + function skipWhitespaceForward(pos: number): number { + while (pos < source.length && /\s/.test(source[pos])) pos++; + return pos; + } + + const sentenceStarts = sentenceEnds.map((_, i) => + i === 0 + ? skipWhitespaceForward(0) + : skipWhitespaceForward(sentenceEnds[i - 1]) + ); + + const clamp = (x: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, x)); + + function sentenceIndexAtPos(pos: number): number { + let lo = 0; + let hi = sentenceEnds.length - 1; + while (lo < hi) { + const mid = (lo + hi) >> 1; + if (sentenceEnds[mid] >= pos) hi = mid; + else lo = mid + 1; + } + return lo; + } + + const root = document.createElement("div"); + root.className = "proofnav proofnav-rtd"; + root.innerHTML = ` +
+
+
+ ${collapsible ? ` + + ` : ""} + +
+
+ +
+
+
+
+ + +
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `; + parent.appendChild(root); + + const elTitle = root.querySelector("[data-title]") as HTMLElement; + elTitle.textContent = title; + + const elEditor = root.querySelector("[data-editor]") as HTMLElement; + const elTabs = root.querySelector("[data-tabs]") as HTMLElement; + const elGoalContent = root.querySelector("[data-goalcontent]") as HTMLElement; + const elMessage = root.querySelector("[data-message]") as HTMLElement; + const elSentInfo = root.querySelector("[data-sentinfo]") as HTMLElement; + const elGoalSep = root.querySelector(".goal-sep") as HTMLElement; + const btnPrev = root.querySelector("[data-prev]") as HTMLButtonElement; + const btnNext = root.querySelector("[data-next]") as HTMLButtonElement; + + const btnToggle = root.querySelector("[data-toggle]") as HTMLButtonElement | null; + const elToggleLabel = root.querySelector("[data-toggle-label]") as HTMLElement | null; + + const setSentenceEffect = StateEffect.define(); + const setHoverEffect = StateEffect.define(); // number | null + + const sentenceField = StateField.define({ + create() { + return clamp(initialSentence, -1, sentenceEnds.length - 1); + }, + update(v, tr) { + for (const e of tr.effects) { + if (e.is(setSentenceEffect)) return e.value; + } + return v; + }, + }); + + const hoverField = StateField.define({ + create() { + return null; + }, + update(v, tr) { + for (const e of tr.effects) { + if (e.is(setHoverEffect)) return e.value; + } + return v; + }, + }); + + const sentenceHighlightField = StateField.define>({ + create(state) { + return buildDecorations(state.field(sentenceField), state.field(hoverField)); + }, + update(deco, tr) { + const changed = + tr.docChanged || + tr.effects.some((e) => e.is(setSentenceEffect) || e.is(setHoverEffect)); + if (changed) { + return buildDecorations(tr.state.field(sentenceField), tr.state.field(hoverField)); + } + return deco.map(tr.changes); + }, + provide: (f) => EditorView.decorations.from(f), + }); + + function buildDecorations(activeIdx: number, hoverIdx: number | null) { + const d: Range[] = []; + + if (hoverIdx != null && hoverIdx >= 0 && hoverIdx < sentenceEnds.length) { + const hs = sentenceStarts[hoverIdx]; + const he = sentenceEnds[hoverIdx]; + if (he > hs) d.push(Decoration.mark({ class: "cm-sentenceHover" }).range(hs, he)); + } + + if (activeIdx >= 0) { + const start = sentenceStarts[activeIdx]; + const end = sentenceEnds[activeIdx]; + + if (start > 0) d.push(Decoration.mark({ class: "cm-sentenceDone" }).range(0, start)); + if (end > start) d.push(Decoration.mark({ class: "cm-sentenceCurrent" }).range(start, end)); + } + + return Decoration.set(d, true); + } + + const hoverAndClick = EditorView.domEventHandlers({ + mousemove(e, view) { + const pos = view.posAtCoords({ x: e.clientX, y: e.clientY }); + if (pos == null) { + root.classList.remove("pn-hovering"); + if (view.state.field(hoverField) != null) { + view.dispatch({ effects: setHoverEffect.of(null) }); + } + return false; + } + + const idx = sentenceIndexAtPos(pos); + root.classList.add("pn-hovering"); + if (view.state.field(hoverField) !== idx) { + view.dispatch({ effects: setHoverEffect.of(idx) }); + } + return false; + }, + + mouseleave(_e, view) { + root.classList.remove("pn-hovering"); + if (view.state.field(hoverField) != null) { + view.dispatch({ effects: setHoverEffect.of(null) }); + } + return false; + }, + + mousedown(e, view) { + if (e.button !== 0) return false; + const pos = view.posAtCoords({ x: e.clientX, y: e.clientY }); + if (pos == null) return false; + setSentence(sentenceIndexAtPos(pos), { scroll: false }); + return true; + }, + }); + + class ActiveSentenceMarker extends GutterMarker { + toDOM() { + const span = document.createElement("span"); + span.className = "cm-activeSentenceMarker"; + span.textContent = "▶"; + return span; + } + } + const activeMarker = new ActiveSentenceMarker(); + + const activeSentenceGutter = gutter({ + class: "cm-activeSentenceGutter", + markers: (view) => { + const idx = view.state.field(sentenceField); + if (idx < 0) return RangeSet.empty; + const line = view.state.doc.lineAt(sentenceStarts[idx]).from; + return RangeSet.of([activeMarker.range(line)]); + }, + initialSpacer: () => activeMarker, + }); + + const rtdTheme = EditorView.theme({ + "&": { + backgroundColor: "#fff", + color: "#404040", + fontSize: "11px", + }, + ".cm-content": { + fontFamily: "var(--pn-mono)", + fontSize: "11px", + }, + ".cm-gutters": { + backgroundColor: "#fbfbfb", + borderRight: "1px solid #e1e4e5", + fontSize: "11px", + }, + ".cm-lineNumbers .cm-gutterElement": { + padding: "0 8px 0 10px", + fontSize: "11px", + } + }); + + const view = new EditorView({ + parent: elEditor, + state: EditorState.create({ + doc: source, + extensions: [ + easycryptHighlight, + syntaxHighlighting(rtdHighlight), + activeSentenceGutter, + lineNumbers(), + keymap.of(defaultKeymap), + EditorView.editable.of(false), + EditorState.readOnly.of(true), + sentenceField, + hoverField, + sentenceHighlightField, + hoverAndClick, + rtdTheme, + ], + }), + }); + + // autosize editor to content + function autosizeEditor() { + view.requestMeasure({ + read() { + return view.contentDOM.scrollHeight; + }, + write(height) { + // Small padding to avoid clipping descenders + elEditor.style.height = `${height + 6}px`; + } + }); + } + + requestAnimationFrame(() => requestAnimationFrame(autosizeEditor)); + + let activeGoalTab = 0; + + function render(idx: number) { + if (idx < 0) { + elSentInfo.textContent = "Before first sentence"; + elTabs.innerHTML = ""; + elGoalContent.innerHTML = `
No goals.
`; + elMessage.innerHTML = `
No message.
`; + elGoalSep.style.display = "none"; + return; + } + + elSentInfo.textContent = `Sentence ${idx + 1} / ${sentenceEnds.length}`; + const info = sentences[idx] || {}; + const goals = Array.isArray(info.goals) ? info.goals : []; + const msg = String(info.message ?? ""); + + elTabs.innerHTML = ""; + elGoalContent.innerHTML = ""; + elGoalSep.style.display = goals.length ? "block" : "none"; + + if (goals.length === 0) { + elGoalContent.innerHTML = `
No goals.
`; + activeGoalTab = 0; + } else { + activeGoalTab = clamp(activeGoalTab, 0, goals.length - 1); + goals.forEach((_, i) => { + const b = document.createElement("button"); + b.className = "tab"; + b.textContent = `Goal ${i + 1}`; + b.setAttribute("aria-selected", i === activeGoalTab ? "true" : "false"); + b.onclick = () => { + activeGoalTab = i; + render(getSentence()); + }; + elTabs.appendChild(b); + }); + const pre = document.createElement("pre"); + pre.textContent = goals[activeGoalTab] ?? ""; + elGoalContent.appendChild(pre); + } + + if (msg.trim()) { + const pre = document.createElement("pre"); + pre.textContent = msg; + elMessage.innerHTML = ""; + elMessage.appendChild(pre); + } else { + elMessage.innerHTML = `
No message.
`; + } + } + + function scrollTo(idx: number) { + if (idx < 0) return; + view.dispatch({ selection: { anchor: sentenceStarts[idx] }, scrollIntoView: true }); + } + + function setSentence(idx: number, { scroll = true }: { scroll?: boolean } = {}) { + const i = clamp(idx, -1, sentenceEnds.length - 1); + + const effects: StateEffect[] = [setSentenceEffect.of(i)]; + + if (i < 0) { + effects.push(setHoverEffect.of(null)); + root.classList.remove("pn-hovering"); + } + + view.dispatch({ effects }); + render(i); + if (scroll) scrollTo(i); + } + + function getSentence(): number { + return view.state.field(sentenceField); + } + + btnPrev.onclick = () => setSentence(getSentence() - 1); + btnNext.onclick = () => setSentence(getSentence() + 1); + + function isCollapsed(): boolean { + return root.classList.contains("pn-collapsed"); + } + + function setCollapsed(collapsed: boolean) { + if (!collapsible) return; + if (collapsed) root.classList.add("pn-collapsed"); + else root.classList.remove("pn-collapsed"); + + if (btnToggle) btnToggle.setAttribute("aria-expanded", collapsed ? "false" : "true"); + if (elToggleLabel) elToggleLabel.textContent = collapsed ? "Expand" : "Collapse"; + if (btnToggle) btnToggle.title = collapsed ? "Expand" : "Collapse"; + + // When expanding, CodeMirror may need a layout refresh + correct height. + if (!collapsed) { + requestAnimationFrame(() => { + autosizeEditor(); + view.requestMeasure(); + }); + } + } + + function collapse() { setCollapsed(true); } + function expand() { setCollapsed(false); } + function toggleCollapsed() { setCollapsed(!isCollapsed()); } + + if (btnToggle) { + btnToggle.onclick = () => toggleCollapsed(); + } + + setSentence(initialSentence); + + if (collapsible && initialCollapsed) { + setCollapsed(true); + } + + return { view, setSentence, getSentence, collapse, expand, toggleCollapsed, isCollapsed }; +} diff --git a/doc/extensions/ecpygment/ecpygment.py b/doc/extensions/ecpygment/ecpygment.py new file mode 100644 index 000000000..7fb05381b --- /dev/null +++ b/doc/extensions/ecpygment/ecpygment.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------- +import sphinx.application as sa +import sphinx.util as su + +from lexers.easycrypt import EasyCryptLexer + +# -------------------------------------------------------------- +def setup(app: sa.Sphinx) -> su.typing.ExtensionMetadata: + app.add_lexer("easycrypt", EasyCryptLexer) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/doc/extensions/ecpygment/lexers/easycrypt.py b/doc/extensions/ecpygment/lexers/easycrypt.py new file mode 100644 index 000000000..732013b9b --- /dev/null +++ b/doc/extensions/ecpygment/lexers/easycrypt.py @@ -0,0 +1,78 @@ +# ------------------------------------------------------------------------ +import pygments.lexer as pylex +import pygments.token as pytok + +import itertools as it + +# ------------------------------------------------------------------------ +# Generated by `scripts/srctx/keywords -m python < src/ecLexer.mll` +keywords = dict( + bytac = ['exact', 'assumption', 'smt', 'coq', 'check', 'edit', 'fix', 'by', 'reflexivity', 'done', 'solve'], + dangerous = ['admit', 'admitted'], + global_ = ['axiom', 'axiomatized', 'lemma', 'realize', 'proof', 'qed', 'abort', 'goal', 'end', 'from', 'import', 'export', 'include', 'local', 'global', 'declare', 'hint', 'module', 'of', 'const', 'op', 'pred', 'inductive', 'notation', 'abbrev', 'require', 'theory', 'abstract', 'section', 'subtype', 'type', 'class', 'instance', 'print', 'search', 'locate', 'as', 'Pr', 'clone', 'with', 'rename', 'prover', 'timeout', 'why3', 'dump', 'remove', 'exit', 'Top', 'Self'], + internal = ['fail', 'time', 'undo', 'debug', 'pragma'], + prog = ['forall', 'exists', 'fun', 'glob', 'let', 'in', 'for', 'var', 'proc', 'if', 'is', 'match', 'then', 'else', 'elif', 'match', 'for', 'while', 'assert', 'return', 'res', 'equiv', 'hoare', 'ehoare', 'phoare', 'islossless', 'async'], + tactic = ['beta', 'iota', 'zeta', 'eta', 'logic', 'delta', 'simplify', 'cbv', 'congr', 'change', 'split', 'left', 'right', 'case', 'pose', 'gen', 'have', 'suff', 'elim', 'exlim', 'ecall', 'clear', 'wlog', 'idassign', 'apply', 'rewrite', 'rwnormal', 'subst', 'progress', 'trivial', 'auto', 'idtac', 'move', 'modpath', 'field', 'fieldeq', 'ring', 'ringeq', 'algebra', 'replace', 'transitivity', 'symmetry', 'seq', 'wp', 'sp', 'sim', 'skip', 'call', 'rcondt', 'rcondf', 'swap', 'cfold', 'rnd', 'rndsem', 'pr_bounded', 'bypr', 'byphoare', 'byehoare', 'byequiv', 'byupto', 'fel', 'conseq', 'exfalso', 'inline', 'outline', 'interleave', 'alias', 'weakmem', 'fission', 'fusion', 'unroll', 'splitwhile', 'kill', 'eager'], + tactical = ['try', 'first', 'last', 'do', 'expect'], +) + +# ------------------------------------------------------------------------ +kwclasses = dict( + bytac = pytok.Name.Exception, + dangerous = pytok.Name.Exception, + global_ = pytok.Keyword.Declaration, + internal = pytok.Keyword.Declaration, + prog = pytok.Keyword.Reserved, + tactic = pytok.Keyword.Reserved, + tactical = pytok.Keyword.Pseudo, +) + +# ------------------------------------------------------------------------ +class EasyCryptLexer(pylex.RegexLexer): + name = "EasyCrypt" + filenames = ["*.ec", "*.eca"] + mimetypes = ["text/x-easycrypt"] + + tokens = { + "root": [ + # Whitespace + (r"[ \t]+", pytok.Whitespace), + (r"\n+", pytok.Whitespace), + + # Comments + (r"\(\*", pytok.Comment.Multiline, "comment"), + ] + [ + # Keywords + (pylex.words(keywords[ids], suffix=r"\b"), cls) + for ids, cls in kwclasses.items() + ] + [ + # Strings (simple single/double quoted) + (r'"([^"\\]|\\.)*"', pytok.String.Double), + + # Numbers + (r"\b\d+\b", pytok.Number.Integer), + + # Identifiers + (r"[A-Za-z_]\w*", pytok.Name), + + # Operators + (r"[+\-*/%=<>&|!]+", pytok.Operator), + + # Punctuation + (r"[()\[\]{},.;:]", pytok.Punctuation), + + # Anything else + (r".", pytok.Text), + ], + + "comment": [ + (r"\(\*", pytok.Comment.Multiline, "#push"), + + # If we see a closer, pop one nesting level + (r"\*\)", pytok.Comment.Multiline, "#pop"), + + # Otherwise consume content (keep it as Comment) + (r"[^()*]+", pytok.Comment.Multiline), + (r"[()*]", pytok.Comment.Multiline), + ], + } diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 000000000..8b6c7d9b2 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,7 @@ +EasyCrypt reference manual +======================================================================== + +.. toctree:: + :maxdepth: 2 + + tactics diff --git a/doc/package-lock.json b/doc/package-lock.json new file mode 100644 index 000000000..012938edf --- /dev/null +++ b/doc/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "doc", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 000000000..a496b96f3 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,3 @@ +Sphinx==8.2.* +sphinx_rtd_theme==3.1.* +sphinx_design==0.6.* diff --git a/doc/tactics.rst b/doc/tactics.rst new file mode 100644 index 000000000..5b954bbad --- /dev/null +++ b/doc/tactics.rst @@ -0,0 +1,8 @@ +Proof tactics reference +======================================================================== + +.. toctree:: + :maxdepth: 1 + :glob: + + tactics/* diff --git a/doc/tactics/skip.rst b/doc/tactics/skip.rst new file mode 100644 index 000000000..6c3ff2a44 --- /dev/null +++ b/doc/tactics/skip.rst @@ -0,0 +1,123 @@ +======================================================================== +Tactic: `skip` +======================================================================== + +The ``skip`` tactic applies to program-logic goals where the program(s) +under consideration are empty. In this situation, program execution +performs no computation and produces no state changes. + +Applying ``skip`` eliminates the program component of the goal and reduces +the proof obligation to a pure logical goal. Concretely, the remaining +task is to prove that the precondition implies the postcondition. + +The ``skip`` tactic does not attempt to solve this logical obligation itself. + +.. contents:: + :local: + +------------------------------------------------------------------------ +Variant: ``skip`` (HL) +------------------------------------------------------------------------ + +.. ecproof:: + :title: Hoare logic example + + require import AllCore. + + module M = { + proc f(x : int) = { + return x; + } + }. + + pred p : int. + pred q : int. + + lemma L : hoare[M.f : p x ==> q res]. + proof. + proc. (*$*) skip. + abort. + +------------------------------------------------------------------------ +Variant: ``skip`` (pRHL) +------------------------------------------------------------------------ + +In the relational Hoare logic setting, the `skip`` tactic applies only +when both programs are empty, in which case it reduces the relational +judgment to obligations on the preconditions and postconditions alone. + +.. ecproof:: + :title: Probabilistic Relational Hoare logic example + + require import AllCore. + + module M = { + proc f(x : int) = { + return x; + } + }. + + pred p : int & int. + pred q : int & int. + + lemma L : equiv[M.f ~ M.f : p x{1} x{2} ==> q res{1} res{2}]. + proof. + proc. (*$*) skip. + abort. + +------------------------------------------------------------------------ +Variant: ``skip`` (pHL) +------------------------------------------------------------------------ + +In the probabilistic Hoare logic setting, applying ``skip`` generates an +additional proof obligation compared to the pure Hoare case. Besides the +logical implication between the precondition and the postcondition, one +must also prove that the probability weight of the empty program, namely +``1%r``, satisfies the bound specified in the judgment. + +.. ecproof:: + :title: Probabilistic Hoare logic example + + require import AllCore. + + module M = { + proc f(x : int) = { + return x; + } + }. + + pred p : int. + pred q : int. + + lemma L : phoare[M.f : p x ==> q res] >= (1%r / 2%r). + proof. + proc. (*$*) skip. + abort. + +------------------------------------------------------------------------ +Variant: ``skip`` (eHL) +------------------------------------------------------------------------ + +In expectation Hoare logic, where the precondition and postcondition are +respectively a pre-expectation and a post-expectation, applying skip generates +the obligation to prove that the post-expectation is bounded above by the +pre-expectation. + +.. ecproof:: + :title: Expectation Hoare logic example + + require import AllCore Xreal. + + module M = { + proc f(x : int) = { + return x; + } + }. + + op p : int -> xreal. + op q : int -> xreal. + + lemma L : ehoare[M.f : p x ==> q res]. + proof. + proc. (*$*) skip. + abort. diff --git a/dune b/dune index e19b69c5a..7c8edf709 100644 --- a/dune +++ b/dune @@ -1,9 +1,13 @@ -(dirs 3rdparty src etc theories examples scripts) +(dirs 3rdparty src etc theories examples assets scripts) (install (section (site (easycrypt commands))) (files (scripts/testing/runtest as runtest))) +(install + (section (site (easycrypt doc))) + (files (assets/styles/styles.css as styles.css))) + (install (section (bin)) (files (scripts/testing/bin-ec-runtest as ec-runtest))) diff --git a/dune-project b/dune-project index b285ee175..85f142616 100644 --- a/dune-project +++ b/dune-project @@ -10,7 +10,7 @@ (package (name easycrypt) - (sites (lib theories) (libexec commands) (lib config)) + (sites (lib theories) (libexec commands) (lib doc) (lib config)) (depends (ocaml (>= 4.08.0)) (batteries (>= 3)) @@ -19,6 +19,7 @@ dune dune-build-info dune-site + markdown (pcre2 (>= 8)) (why3 (and (>= 1.8.0) (< 1.9))) yojson diff --git a/easycrypt.opam b/easycrypt.opam index 47ea3eb08..08bdb40ea 100644 --- a/easycrypt.opam +++ b/easycrypt.opam @@ -7,6 +7,7 @@ depends: [ "dune" {>= "3.13"} "dune-build-info" "dune-site" + "markdown" "pcre2" {>= "8"} "why3" {>= "1.8.0" & < "1.9"} "yojson" diff --git a/examples/PRG.ec b/examples/PRG.ec index f32ddf910..870a3ce0e 100644 --- a/examples/PRG.ec +++ b/examples/PRG.ec @@ -340,11 +340,8 @@ section. by wp; rnd; wp; rnd{2}; auto; rewrite dseed_ll. (* presampling ~ postsampling *) seq 2 2: (={glob A, glob F, glob Plog}); first by sim. - eager (H: Resample.resample(); ~ Resample.resample(); - : ={glob Plog} ==> ={glob Plog}) - : (={glob A, glob Plog, glob F})=> //; - first by sim. - eager proc H (={glob Plog, glob F})=> //. + eager call (: ={glob Plog, glob A, glob F}). + eager proc (={glob Plog, glob F}) => //; try sim. + eager proc; inline Resample.resample. swap{1} 3 3. swap{2} [4..5] 2. swap{2} [6..8] 1. swap{1} 4 3. swap{1} 4 2. swap{2} 2 4. @@ -357,10 +354,9 @@ section. by wp; rnd{2}; auto=> />; smt (size_ge0). rcondt{2} 1; first by move=> &hr; auto=> /#. rcondf{2} 3; first by move=> &hr; auto=> /#. - + by sim. - + by sim. + by sim. + by eager proc; swap{1} 1 4; sim. - by sim. + by auto. qed. lemma P_PrgI &m: diff --git a/examples/UC/RndO.ec b/examples/UC/RndO.ec index 2450f14fb..e7b3c0983 100644 --- a/examples/UC/RndO.ec +++ b/examples/UC/RndO.ec @@ -681,8 +681,7 @@ lemma eager_D : D(RRO).distinguish, RRO.resample(); : ={glob D, FRO.m} ==> ={FRO.m, glob D} /\ ={res}]. proof. - eager proc (H_: RRO.resample(); ~ RRO.resample();: ={FRO.m} ==> ={FRO.m}) - (={FRO.m}) =>//; try by sim. + eager proc (={FRO.m}) => //; try by sim. + by apply eager_init. + by apply eager_get. + by apply eager_set. + by apply eager_rem. + by apply eager_sample. + by apply eager_in_dom. + by apply eager_restrK. diff --git a/examples/docgen/docgenbasic.ec b/examples/docgen/docgenbasic.ec new file mode 100644 index 000000000..691efa23e --- /dev/null +++ b/examples/docgen/docgenbasic.ec @@ -0,0 +1,320 @@ +(*^ + EasyCrypt_DocGen_Tutorial.ec + + To generate documentation for a source file, run the following command: + {{ + docgen [-outdir ] + }} + Here, `` is the path to the EasyCrypt executable on your + system, `` is the directory where the generated + documentation files will be stored, and `` is the path to the + source file you want to generate documentation for. You may omit the output + directory, in which case the tool defaults to the directory of the source file. + + This is a file documentation comment. In the generated documentation file, this + comment appears at the top. File documentation comments are typically used for + summaries, overviews, and meta-information about the file. +^*) + +(*^ + This is an additional file-documentation comment. In the generated + documentation, it is added as a paragraph below the (last paragraph of the) + previous file documentation comment. +^*) + +(* + Regular, non-documentation comments like this one are excluded from the + generated documentation file. +*) +require import FinType. + +(*& + This is a regular documentation comment, which is linked to the next + "documentable" item. In the generated documentation file, it appears as + documentation for the linked item. + + At the time of writing, the "documentable" items are: + - types, + - operators, + - axioms, + - lemmas, + - module types, + - modules, and + - theories. + + Note that "scoped" items (those specified with, e.g., `local` or `declare`) + are not "documentable", even if their "non-scoped" versions would be. + + This documentation comment is linked to `type t` below. +&*) +type t. + +(*& + It is not necessary to close a documentation comment with a matching closing + delimiter. Only the opening delimiter determines the type of comment. However, + it is good practice to use a matching closing delimiter. +*) +type u. + +(*& + Multiple documentation comments can be placed consecutively without any + "documentable" items in between. All of these comments are linked to the next + "documentable" item. However, starting with the second comment, each will be + hidden under an un-foldable "details" section, indicated by a clickable arrow. + Even if fewer than two documentation comments are linked to an item, this + "details" section always contains the source code for the item, except in the + case of (sub)theories. +&*) + +(*& + As an example, both the previous and this documentation comment are linked to + `type v` below. The first comment is shown by default, while this second one + is initially hidden, but can be revealed by unfolding the corresponding + "details" section. +&*) +type v. + +(*& + Documentation comments can be interleaved with non-documentation comments, + even before the item to which the documentation comments are linked. +&*) + +(* + This is a non-documentation comment between two documentation comments linked + to the same item. +*) + +(*& + Both the previous and this __documentation__ comment are correctly linked to + `type w` below, even though they are separated by a __non-documentation__ + comment. +&*) +type w. + +(*^ + File documentation comments can be placed anywhere in the file. Each comment + is added as a new paragraph below the previous one. However, it is + considered good practice to place file documentation comments at the + beginning of the file whenever possible. +^*) + +(*& + __Any__ comments nested inside documentation comments + are excluded from the generated documentation file. + (* This comment is excluded from the generated documentation file *) + (*& This comment is excluded from the generated documentation file &*) + (*^ This comment is excluded from the generated documentation file ^*) + However, anything outside these nested comments (but within + the documentation comment, of course) is included. +&*) +type x. + +(* + All "documentable" items are included in the generated documentation file, whether + or not they have a corresponding documentation comment. The source code + of each item is always included, though it is initially hidden under an + un-foldable "details" section. If there is no corresponding documentation + comment, a default message is shown, referring to the details section for the + source code. +*) +type y. + +(*& + All documentation comments can be + formatted using (a non-standard dialect of) Markdown. + The following is supported. + + As first non-blank character on a line (followed by a space): + - \! indicates a heading (one for largest heading, two for second-largest + heading, etc.); + - \*, \+, or \- indicate an item of an unordered list; + - \# indicates an item of an ordered list; and + - \> indicates (a line of) a blockquote. + + As delimiters: + - \{\{ and \}\} delimit a code block (both the delimiters and content should be + on separate lines); + - \` delimits inline code (e.g., `inline code`); + - \* delimits bold text (e.g., *bold text* ); and + - \__ delimits emphasized text (e.g., __emph text__). + + Any special characters can be escaped with a backslash (e.g., \`). + + Hyperlinks are formatted as `[]()` + (e.g., [EasyCrypt GitHub repository](https://github.com/EasyCrypt/easycrypt)). +&*) +type z. + +(*& + It is possible to link to other documented items *within + the theory's scope* (i.e., items defined in the file itself or + imported from other theories). The syntax is similar to + that for hyperlinks: `[](>|)`, + Here, `` is one of the following: + - `Ty` (or `Type`), + - `Op` (or `Operator`), + - `Ax` (or `Axiom`), + - `Lem` (or `Lemma`), + - `ModTy` (or `ModuleType`), + - `Mod` (or `Module`), and + - `Th` (or `Theory`). + + `` is the name of the item as you would print it in the theory + itself. Particularly, this means that the name may need to be qualified, + depending on the imports in the theory. For example, `[go to type t above](>Ty|t)` + becomes [go to type t above](>Ty|t). However, `[go to operator t below](>Op|t)` + becomes [go to operator t below](>Op|t). (Note that, even though the linked + type and operator are both referred to with the same name, the correct item is + linked due to the specification of the item kind.) + + If you omit ``, the documentation tool checks each item kind in the + order listed above for the given `` and links to the first match. + E.g., instead of `[go to type t above](>Ty|t)`, you can use `[go to type t + above](>|t)` (proof: [go to type t above](>|t)). However, you still need + to explicitly specify the `Op` kind for operator `t` below, because the + documentation tool checks the `Type` kind before the `Operator` kind, which + already results in a match. +&*) +op f : u -> v. + +(*& This is a documented operator &*) +op t : v -> w. + +(*& + The generated documentation file contains a section for each item kind. Within each + section, items are displayed in the order they appear in the source. +&*) +axiom ax : true. + +(*& + If there are no items of a certain kind, + the generated documentation file does not contain a section for that kind. + For example, the generated documentation file for this theory does not + contain a section for module types. + + The navigation bar on the left-hand side of the generated documentation file + shows only the sections that are present and provides links to them + for convenience. +&*) +lemma lem : true. +proof. by trivial. qed. + +(*& + Currently, individual procedures inside of modules (and module types) cannot + be documented using documentation comments. For the time being, a + (unsatisfactory) workaround is to use regular comments within the module (or + module type), which appear in the source code for the module (or module type) + in the generated documentation file. +&*) +module M = { + (* + This regular comment appears in the source code + for this module in the generated documentation file. + *) + proc p() : int = { + return 1; + } +}. + +(*& + Theories are special as "documentable" items. They appear as documented items + in the generated documentation file for their parent theory __and__ + receive their own documentation file, in turn documenting all their + "documentable" items. This file has a subheading indicating the subtheory and + links to entry of the (sub)theory in the parent theory's documentation file. + + The file name for a (sub)theory follows this pattern: `Y!Z`, where `Y`, and + `Z` represent the parent theory and (sub)theory, respectively. (This works + recursively: `Y` may itself be a (sub)theory of another theory `X`, in which + case the file name becomes `X!Y!Z`.) + + The "introductory text" for the (sub)theory, which you would usually put in + file documentation comments, is drawn from the regular documentation comments + in the parent theory. In the parent theory's documentation file, the + (sub)theory's name links to the corresponding (sub)theory documentation file. + + No source code is shown for subtheories in the parent theory's documentation + file. +&*) +theory T. + +(*& + This item is documented in the documentation file corresponding to + (sub)theory `T'. +&*) +type s. + +(*& + Linking to items is done from the perspective of the outermost theory (`Top`), + so names for items within (sub)theories that are not imported must be qualified. + In other words, if a (sub)theory is not imported by the outermost theory, + linking requires the item name to be qualified properly, even within the + (sub)theory itself. For example, to link to `type s` above, something along + the lines of `[go to s in T](>|s)` __does not__ work, but + `[go to s in T](>|T.s)` __does__ (proof: [go to s in T](>|T.s)). + However, if the (sub)theory would be imported at some point in the + outermost theory, `[go to s in T](>|s)` __would work__, provided + there are no naming collisions. +&*) +op a : s -> s. + +(*& + Linking to items in a parent theory works as expected. For example `[go to w + in parent](>|w)` would create a link to the entry for `w` in the parent + theory's documentation file (proof: [go to w in parent](>|w)). +&*) +op b : w -> w. + +(*& + The documentation mechanism for theories works recursively. + For example, theory `U` below is treated in the documentation file for `T` in the + same way that `T` is treated in the documentation file for the outermost theory. + In addition to appearing in `T`'s documentation file, `U` also receives its own + documentation file, similar to `T`, but now linking back to `T` rather than the + outermost theory. +&*) +abstract theory U. + +end U. + +end T. + + +section. + +(* + As mentioned before, "scoped" items are never documented, even + if their "non-scoped" version are. +*) +declare op lf : t -> t. + +(*& + If a documentation comment precedes (and would normally be linked to) an item + that is "undocumentable" (e.g., due to its scope), the comment is discarded, + effectively making it a regular, non-documentation comment. +&*) +local module M' = { + +}. + +(*& + This operator is documented, but the previous documentation comment is + not visible (indicating it has been dropped). +&*) +op foo = T.a. + +end section. + +(*& + At present, similar to the previously discussed "scoped" items, clones are + "undocumentable" items. As such, any preceding (would-be-linked) documentation + comments are discarded, effectively making them regular, non-documentation + comments. +&*) +clone FinType as FT with + type t <- t. + +(*& + A documentation comment without any subsequent item is + discarded, effectively making it a regular, non-documentation comment. +&*) diff --git a/examples/ehoare/random_boolean_matrix.ec b/examples/ehoare/random_boolean_matrix.ec new file mode 100644 index 000000000..512e47ee4 --- /dev/null +++ b/examples/ehoare/random_boolean_matrix.ec @@ -0,0 +1,207 @@ +require import AllCore Array Real RealExp List. +(*---*) import RField. +require import Distr DBool Xreal. +(*---*) import Biased. +require import StdOrder. +(*---*) import RealOrder. + +(* uniformly sampling a 2-d boolean array of size n x m *) +module M = { + proc sample (n : int, m : int, a : bool array) : (bool array) = { + var i, j : int; + var b : bool; + i <- 0; + while (i < n) { + j <- 0; + while (j < m) { + b <$ dbiased 0.5; + a.[i * m + j] <- b; + j <- j + 1; + } + i <- i + 1; + } + return a; + } +}. + +op outer_shape_pred (i n m : int) (a a' : bool array) = + 0 <= i <= n + /\ 0 <= m + /\ size a = n * m + /\ size a = size a'. + +op shape_pred (i j n m : int) (a a' : bool array) = + 0 <= i < n + /\ 0 <= j <= m + /\ size a = n * m + /\ size a = size a'. + +op row_eq_upto (i m : int) (a1 a2 : bool array) = + forall (i' j' : int), + 0 <= i' < i + => 0 <= j' < m + => a1.[i' * m + j'] = a2.[i' * m + j']. + +op cell_eq_upto (i j m : int) (a1 a2 : bool array) = + forall (j' : int), + 0 <= j' < j + => a1.[i * m + j'] = a2.[i * m + j']. + +lemma row_eq_upto_increase (i m : int) (a1 a2 : bool array): + 0 <= i + => (row_eq_upto i m a1 a2 /\ cell_eq_upto i m m a1 a2 + <=> row_eq_upto (i + 1) m a1 a2). +proof. +move => ? @/row_eq_upto @/cell_eq_upto; split. +- move => ? i' j' *. + by case: (i' < i) => /#. +- move => H; split. + - move => i' j' ??. + have ?: 0 <= i' < i + 1 by smt(). + by have := H i' j' _ _ => //. + - by have := H i => /#. +qed. + +lemma cell_eq_upto_false (i j' j m : int) (a1 a2 : bool array) : + 0 <= j' < j + => a1.[i * m + j'] <> a2.[i * m + j'] + => cell_eq_upto i j m a1 a2 = false. +proof. by smt(). qed. + +lemma cell_eq_upto_split (i j m : int) (a1 a2 : bool array) : + 0 <= j < m + => (cell_eq_upto i (j + 1) m a1 a2 + <=> (cell_eq_upto i j m a1 a2 + /\ a1.[i * m + j] = a2.[i * m + j]) + ). +proof. +move => ? @/cell_eq_upto; split. +- move => H; split. + - move => j' ?. + have ?: 0 <= j' < j + 1 by smt(). + have := H j' _ => //. + - by smt(). +- move => ? j' ?. + by case (j' < j) => /#. +qed. + +lemma row_eq_upto_unrelated_set (i m x : int) (v : bool) (a1 a2 : bool array): + i * m <= x < size a1 + => (row_eq_upto i m a1 a2 <=> row_eq_upto i m a1.[x <- v] a2). +proof. +move => ? @/row_eq_upto; split. +- move => ? i' j' ??. + rewrite get_set 1:/#. + have -> /=: !(i' * m + j' = x) by smt(). + by smt(). +- move => ? i' j' ??. + by rewrite (_: a1.[_] = a1.[x <- v].[i' * m + j']) 1:get_set /#. +qed. + +lemma cell_eq_upto_unrelated_set (i j m x : int) (v : bool) (a1 a2 : bool array) : + 0 <= i /\ 0 <= j < m /\ i * m + j <= x < size a1 + => (cell_eq_upto i j m a1 a2 <=> cell_eq_upto i j m a1.[x <- v] a2). +proof. +move => [#] ????? @/cell_eq_upto; split. +- move => ? j' ?. + rewrite get_set 1:/#. + have -> /=: !(i * m + j' = x) by smt(). + by smt(). +- move => ? j' ?. + by rewrite (_: a1.[_] = a1.[x <- v].[i * m + j']) 1:get_set /#. +qed. + +(* The probability of every possible boolean matrix of size n x m is no more than 2 ^ -(n * m) *) +lemma L: + forall (a0 : bool array), + ehoare [M.sample : + (0 <= arg.`1 + /\ 0 <= arg.`2 + /\ size arg.`3 = arg.`1 * arg.`2 + /\ size arg.`3 = size a0) + `|` (1%r / (2%r ^ (n * m)))%xr ==> (res = a0)%xr]. +proof. +move => a0. +proc. +while ((0 <= i <= n + /\ 0 <= m + /\ size a = n * m + /\ size a0 = size a) + `|` (2%r ^ ((-(n - i) * m)%r))%xr + * (row_eq_upto i m a a0)%xr). +(* !cond => inv => pos_f <= inv_f *) ++ move => &hr. + apply xle_cxr_r => ?. + apply xle_cxr_r => ?. + have ->: n{hr} - i{hr} = 0 by smt(). + rewrite Ring.IntID.mul0r Ring.IntID.oppr0 rpow0 mul1m_simpl. + apply xle_rle; split => * ; 1: by smt(). + exact le_b2r. +(* {cond /\ inv | inv_f} c {inv | inv_f} *) ++ wp. + while (( 0 <= i < n + /\ 0 <= j <= m + /\ size a = n * m + /\ size a = size a0) + `|` (2%r ^ ((-((n - i) * m - j))%r))%xr + * (row_eq_upto i m a a0 /\ cell_eq_upto i j m a a0)%xr). + (* !cond => inv => pos_f <= inv_f *) + + move => &hr />. + rewrite xle_cxr_r => *. + rewrite xle_cxr_l => *. + + by smt(). + + rewrite (_: - _ * m{hr} = - ((n{hr} - i{hr}) * m{hr} - j{hr})) //= 1:/#. + rewrite (_: j{hr} = m{hr}) 1:/#. + rewrite -row_eq_upto_increase 1:/#. + rewrite ler_eqVlt; left; reflexivity. + (* {cond /\ inv | inv_f} c {inv | inv_f} *) + + wp; skip => /> &hr. + rewrite xle_cxr_r => [#] 5? Hsize *. + rewrite Ep_dbiased /= 1:/#. + have-> /=: 0 <= i{hr} < n{hr} by smt(). + have-> /=: 0 <= j{hr} + 1 <= m{hr} by smt(). + rewrite !size_set !Hsize /=. + have-> /=: n{hr} * m{hr} = size a0 by smt(). + rewrite !to_pos_pos 1,2,3,4,5:#smt:(rpow_gt0 b2r_ge0). + rewrite !cell_eq_upto_split 1,2:/#. + rewrite !get_set //=. + - split; 1: by smt(). + move => ?. + by apply (IntOrder.ltr_le_trans ((n{hr} - 1) * m{hr} + m{hr})) => /#. + - split; 1: by smt(). + move => ?. + by apply (IntOrder.ltr_le_trans ((n{hr} - 1) * m{hr} + m{hr})) => /#. + case (a0.[i{hr} * m{hr} + j{hr}]) => Hcase /=. + + rewrite -row_eq_upto_unrelated_set. + - split; 1: by smt(). + move => ?. + by apply (IntOrder.ltr_le_trans ((n{hr} - 1) * m{hr} + m{hr})) => /#. + rewrite -cell_eq_upto_unrelated_set. + - do! split; 1,2,3: by smt(). + move => ?. + by apply (IntOrder.ltr_le_trans ((n{hr} - 1) * m{hr} + m{hr})) => /#. + rewrite -{2}(rpow1 2%r) // -rpowN // -mulrA. + rewrite (mulrC (b2r _) (2%r ^ - 1%r)). + by rewrite mulrA -rpowD // /#. + + rewrite /= -row_eq_upto_unrelated_set. + - split; 1: by smt(). + move => ?. + by apply (IntOrder.ltr_le_trans ((n{hr} - 1) * m{hr} + m{hr})) => /#. + rewrite -cell_eq_upto_unrelated_set. + - do! split; 1,2,3: by smt(). + move => ?. + by apply (IntOrder.ltr_le_trans ((n{hr} - 1) * m{hr} + m{hr})) => /#. + rewrite -{2}(rpow1 2%r) // -rpowN // -mulrA. + rewrite (mulrC (b2r _) (2%r ^ - 1%r)). + by rewrite mulrA -rpowD // /#. + (* pre => inv *) + + wp; skip => &hr />. + rewrite xle_cxr_r => [#] *. + rewrite xle_cxr_l 1:/#. + have-> //: cell_eq_upto i{hr} 0 m{hr} a{hr} a0 by smt(). +auto => /> &hr. +rewrite xle_cxr_r => [#] *. +rewrite xle_cxr_l 1:/#. +rewrite fromintN rpowN //= rpow_int //=. +by have-> //: row_eq_upto 0 m{hr} a{hr} a0 by smt(). +qed. diff --git a/scripts/docker/Dockerfile.base b/scripts/docker/Dockerfile.base index b8aff9ce1..1a43903b0 100644 --- a/scripts/docker/Dockerfile.base +++ b/scripts/docker/Dockerfile.base @@ -2,11 +2,11 @@ FROM debian:stable -MAINTAINER Pierre-Yves Strub +LABEL org.opencontainers.image.maintainer="Pierre-Yves Strub " ARG user=charlie -ENV DEBIAN_FRONTEND noninteractive +ENV DEBIAN_FRONTEND=noninteractive RUN \ apt-get -q -y update && \ diff --git a/scripts/docker/Dockerfile.formosa b/scripts/docker/Dockerfile.formosa new file mode 100644 index 000000000..8ff376734 --- /dev/null +++ b/scripts/docker/Dockerfile.formosa @@ -0,0 +1,7 @@ +# syntax = devthefuture/dockerfile-x + +FROM ./Dockerfile.build as build-formosa + +RUN \ + opam install --deps-only --confirm-level=unsafe-yes jasmin && \ + opam clean diff --git a/scripts/docker/Dockerfile.test b/scripts/docker/Dockerfile.test index f8dcd165d..2e9522d3a 100644 --- a/scripts/docker/Dockerfile.test +++ b/scripts/docker/Dockerfile.test @@ -5,7 +5,6 @@ FROM ./Dockerfile.build ARG EC_VERSION=main RUN \ - opam pin --dev-repo \ - add -n easycrypt https://github.com/EasyCrypt/easycrypt.git#${EC_VERSION} && \ + opam pin add -n easycrypt https://github.com/EasyCrypt/easycrypt.git#${EC_VERSION} && \ opam install -v easycrypt && \ rm -rf .opam/packages.dev/* diff --git a/src/dune b/src/dune index d3e809314..487e9cfcf 100644 --- a/src/dune +++ b/src/dune @@ -1,9 +1,10 @@ (env - (dev (flags :standard -rectypes -warn-error -a+31 -w +28+33-9-23-32-58-67-69)) - (ci (flags :standard -rectypes -warn-error +a -w +28+33-9-23-32-58-67-69)) - (release (flags :standard -rectypes -warn-error -a -w +28+33-9-23-32-58-67-69) + (dev (flags :standard -rectypes -w @1..3@5..28@31..39@43@46..47@49..57@61..62-40-9-23-32-67-69 -warn-error -a+31)) + (ci (flags :standard -rectypes -w @1..3@5..28@31..39@43@46..47@49..57@61..62-40-9-23-32-67-69 -warn-error +a)) + (release (flags :standard -rectypes -w @1..3@5..28@31..39@43@46..47@49..57@61..62-40-9-23-32-67-69 -warn-error -a) (ocamlopt_flags -O3 -unbox-closures))) + (include_subdirs unqualified) (generate_sites_module @@ -15,7 +16,7 @@ (public_name easycrypt.ecLib) (foreign_stubs (language c) (names eunix)) (modules :standard \ ec) - (libraries batteries camlp-streams dune-build-info dune-site inifiles pcre2 why3 yojson zarith) + (libraries batteries camlp-streams dune-build-info dune-site inifiles markdown markdown.html pcre2 tyxml why3 yojson zarith) ) (executable @@ -30,4 +31,4 @@ (menhir (modules ecParser) (explain true) - (flags --table)) + (flags --table --unused-token COMMENT)) diff --git a/src/ec.ml b/src/ec.ml index d61bd4329..627d25b81 100644 --- a/src/ec.ml +++ b/src/ec.ml @@ -407,13 +407,36 @@ let main () = (* Initialize I/O + interaction module *) let module State = struct type t = { - prvopts : prv_options; - input : string option; - terminal : T.terminal lazy_t; - interactive : bool; - eco : bool; - gccompact : int option; + (*---*) prvopts : prv_options; + (*---*) input : string option; + (*---*) terminal : T.terminal lazy_t; + (*---*) interactive : bool; + (*---*) eco : bool; + (*---*) gccompact : int option; + (*---*) docgen : bool; + (*---*) outdirp : string option; + mutable trace : trace1 list option; } + + and trace1 = + { position : int + ; goals : string list option + ; messages : (EcGState.loglevel * string) list } + + module Trace = struct + let trace0 : trace1 = + { position = 0; goals = None; messages = []; } + + let push1_message (trace1 : trace1) (msg, lvl) : trace1 = + { trace1 with messages = (msg, lvl) :: trace1.messages } + + let push_message (trace : trace1 list) msg = + match trace with + | [] -> + [push1_message trace0 msg] + | trace1 :: trace -> + push1_message trace1 msg :: trace + end end in let state : State.t = @@ -467,7 +490,10 @@ let main () = ; terminal = terminal ; interactive = true ; eco = false - ; gccompact = None } + ; gccompact = None + ; docgen = false + ; outdirp = None + ; trace = None } end @@ -489,18 +515,66 @@ let main () = lazy (T.from_channel ~name ~gcstats ~progress (open_in name)) in + let trace0 = + if cmpopts.cmpo_trace then + Some [State.{ position = 0; goals = None; messages = [] }] + else None in + { prvopts = {cmpopts.cmpo_provers with prvo_iterate = true} ; input = Some name ; terminal = terminal ; interactive = false ; eco = cmpopts.cmpo_noeco - ; gccompact = cmpopts.cmpo_compact } + ; gccompact = cmpopts.cmpo_compact + ; docgen = false + ; outdirp = None + ; trace = trace0 } end | `Runtest _ -> (* Eagerly executed *) assert false + + | `DocGen docopts -> begin + let name = docopts.doco_input in + + begin try + let ext = Filename.extension name in + ignore (EcLoader.getkind ext : EcLoader.kind) + with EcLoader.BadExtension ext -> + Format.eprintf "do not know what to do with %s@." ext; + exit 1 + end; + + let prvoff = { + prvo_maxjobs = None; + prvo_timeout = None; + prvo_cpufactor = None; + prvo_provers = None; + prvo_pragmas = []; + prvo_ppwidth = None; + prvo_checkall = false; + prvo_profile = false; + prvo_iterate = false; + prvo_why3server = None; } + in + + let terminal = + lazy (T.from_channel ~name (open_in name)) + in + + { prvopts = prvoff + ; input = Some name + ; terminal = terminal + ; interactive = false + ; eco = true + ; gccompact = None + ; docgen = true + ; outdirp = docopts.doco_outdirp + ; trace = None } + end + in (match state.input with @@ -511,9 +585,10 @@ let main () = | Some pwd -> EcCommands.addidir pwd); (* Check if the .eco is up-to-date and exit if so *) - oiter - (fun input -> if EcCommands.check_eco input then exit 0) - state.input; + (if not state.docgen then + oiter + (fun input -> if EcCommands.check_eco input then exit 0) + state.input); let finalize_input input scope = match input with @@ -525,7 +600,20 @@ let main () = assert (nameo <> input); - let eco = EcEco.{ + let eco = + let mktrace (trace : State.trace1 list) : EcEco.ecotrace = + let mktrace1 (trace1 : State.trace1) : int * EcEco.ecotrace1 = + let goals = Option.value ~default:[] trace1.goals in + let messages = + let for1 (lvl, msg) = + Format.sprintf "%s: %s" + (EcGState.string_of_loglevel lvl) + msg in + String.concat "\n" (List.rev_map for1 trace1.messages) in + (trace1.position, EcEco.{ goals; messages; }) + in List.rev_map mktrace1 trace in + + EcEco.{ eco_root = EcEco.{ eco_digest = Digest.file input; eco_kind = kind; @@ -538,6 +626,7 @@ let main () = eco_kind = x.rqd_kind; } in (x.rqd_name, (ecr, x.rqd_direct))) (EcScope.Theory.required scope)); + eco_trace = Option.map mktrace state.trace; } in let out = open_out nameo in @@ -606,15 +695,23 @@ let main () = EcCommands.cm_iterate = state.prvopts.prvo_iterate; } in + let checkproof = not state.docgen in + EcCommands.initialize ~restart - ~undo:state.interactive ~boot:ldropts.ldro_boot ~checkmode; + ~undo:state.interactive + ~boot:ldropts.ldro_boot + ~checkmode + ~checkproof; (try List.iter EcCommands.apply_pragma state.prvopts.prvo_pragmas with EcCommands.InvalidPragma x -> EcScope.hierror "invalid pragma: `%s'\n%!" x); let notifier (lvl : EcGState.loglevel) (lazy msg) = - T.notice ~immediate:true lvl msg terminal + state.trace <- state.trace |> Option.map (fun trace -> + State.Trace.push_message trace (lvl, msg) + ); + T.notice ~immediate:true lvl msg terminal; in EcCommands.addnotifier notifier; @@ -633,8 +730,9 @@ let main () = | Some (`Int i) -> Some i | _ -> None); begin - match EcLocation.unloc (T.next terminal) with - | EP.P_Prog (commands, locterm) -> + match snd_map EcLocation.unloc (T.next terminal) with + | (src, EP.P_Prog (commands, locterm)) -> + let src = String.strip src in terminate := locterm; List.iter (fun p -> @@ -642,8 +740,25 @@ let main () = let timed = p.EP.gl_debug = Some `Timed in let break = p.EP.gl_debug = Some `Break in let ignore_fail = ref false in + + state.trace <- state.trace |> Option.map (fun trace -> + { State.Trace.trace0 with position = loc.loc_echar } :: trace + ); + try - let tdelta = EcCommands.process ~timed ~break p.EP.gl_action in + let tdelta = EcCommands.process ~src ~timed ~break p.EP.gl_action in + + state.trace <- state.trace |> Option.map (fun trace -> + match trace with + | [] -> assert false + | trace1 :: trace -> + assert (Option.is_none trace1.State.goals); + let goals = EcCommands.pp_all_goals () in + let goals = if List.is_empty goals then None else Some goals in + let trace1 = { trace1 with goals } in + trace1 :: trace + ); + if p.EP.gl_fail then begin ignore_fail := true; raise (EcScope.HiScopeError (None, "this command is expected to fail")) @@ -661,20 +776,24 @@ let main () = raise (EcScope.toperror_of_exn ~gloc:loc e) end; if T.interactive terminal then begin - let error = - Format.asprintf - "The following error has been ignored:@.@.@%a" - EcPException.exn_printer e in + let error = + Format.asprintf + "The following error has been ignored:@.@.@%a" + EcPException.exn_printer e in T.notice ~immediate:true `Info error terminal end end) commands - | EP.P_Undo i -> + | _, EP.P_DocComment doc -> + EcCommands.doc_comment doc + + | _, EP.P_Undo i -> EcCommands.undo i - | EP.P_Exit -> + | _, EP.P_Exit -> terminate := true end; + T.finish `ST_Ok terminal; state.gccompact |> Option.iter (fun i -> @@ -689,6 +808,8 @@ let main () = T.finalize terminal; if not state.eco then finalize_input state.input (EcCommands.current ()); + if state.docgen then + EcDoc.generate_html ?outdirp:state.outdirp state.input (EcCommands.current ()); exit 0 end; with diff --git a/src/ecCommands.ml b/src/ecCommands.ml index f2da6001d..135a2b3de 100644 --- a/src/ecCommands.ml +++ b/src/ecCommands.ml @@ -401,15 +401,15 @@ let process_print scope p = exception Pragma of [`Reset | `Restart] (* -------------------------------------------------------------------- *) -let rec process_type (scope : EcScope.scope) (tyd : ptydecl located) = +let rec process_type ?(src : string option) (scope : EcScope.scope) (tyd : ptydecl located) = EcScope.check_state `InTop "type" scope; - let scope = EcScope.Ty.add scope tyd in + let scope = EcScope.Ty.add ?src scope tyd in EcScope.notify scope `Info "added type: `%s'" (unloc tyd.pl_desc.pty_name); scope (* -------------------------------------------------------------------- *) -and process_types (scope : EcScope.scope) tyds = - List.fold_left process_type scope tyds +and process_types ?(src : string option) (scope : EcScope.scope) tyds = + List.fold_left (process_type ?src) scope tyds (* -------------------------------------------------------------------- *) and process_subtype (scope : EcScope.scope) (subtype : psubtype located) = @@ -431,19 +431,19 @@ and process_tycinst (scope : EcScope.scope) (tci : ptycinstance located) = EcScope.Ty.add_instance scope (Pragma.get ()).pm_check tci (* -------------------------------------------------------------------- *) -and process_module (scope : EcScope.scope) m = +and process_module ?(src : string option) (scope : EcScope.scope) m = EcScope.check_state `InTop "module" scope; - EcScope.Mod.add scope m + EcScope.Mod.add ?src scope m (* -------------------------------------------------------------------- *) -and process_interface (scope : EcScope.scope) intf = +and process_interface ?(src : string option) (scope : EcScope.scope) intf = EcScope.check_state `InTop "interface" scope; - EcScope.ModType.add scope intf + EcScope.ModType.add ?src scope intf (* -------------------------------------------------------------------- *) -and process_operator (scope : EcScope.scope) (pop : poperator located) = +and process_operator ?(src : string option) (scope : EcScope.scope) (pop : poperator located) = EcScope.check_state `InTop "operator" scope; - let op, axs, scope = EcScope.Op.add scope pop in + let op, axs, scope = EcScope.Op.add ?src scope pop in let ppe = EcPrinting.PPEnv.ofenv (EcScope.env scope) in List.iter (fun { pl_desc = name } -> @@ -455,14 +455,14 @@ and process_operator (scope : EcScope.scope) (pop : poperator located) = scope (* -------------------------------------------------------------------- *) -and process_procop (scope : EcScope.scope) (pop : pprocop located) = +and process_procop ?(src : string option) (scope : EcScope.scope) (pop : pprocop located) = EcScope.check_state `InTop "operator" scope; - EcScope.Op.add_opsem scope pop + EcScope.Op.add_opsem ?src scope pop (* -------------------------------------------------------------------- *) -and process_predicate (scope : EcScope.scope) (p : ppredicate located) = +and process_predicate ?(src : string option) (scope : EcScope.scope) (p : ppredicate located) = EcScope.check_state `InTop "predicate" scope; - let op, scope = EcScope.Pred.add scope p in + let op, scope = EcScope.Pred.add ?src scope p in let ppe = EcPrinting.PPEnv.ofenv (EcScope.env scope) in EcScope.notify scope `Info "added predicate %s %a" (unloc p.pl_desc.pp_name) (EcPrinting.pp_added_op ppe) op; @@ -486,9 +486,9 @@ and process_abbrev (scope : EcScope.scope) (a : pabbrev located) = scope (* -------------------------------------------------------------------- *) -and process_axiom (scope : EcScope.scope) (ax : paxiom located) = +and process_axiom ?(src : string option) (scope : EcScope.scope) (ax : paxiom located) = EcScope.check_state `InTop "axiom" scope; - let (name, scope) = EcScope.Ax.add scope (Pragma.get ()).pm_check ax in + let (name, scope) = EcScope.Ax.add ?src scope (Pragma.get ()).pm_check ax in name |> EcUtils.oiter (fun x -> match (unloc ax).pa_kind with @@ -497,9 +497,9 @@ and process_axiom (scope : EcScope.scope) (ax : paxiom located) = scope (* -------------------------------------------------------------------- *) -and process_th_open (scope : EcScope.scope) (loca, abs, name) = +and process_th_open ?(src : string option) (scope : EcScope.scope) (loca, abs, name) = EcScope.check_state `InTop "theory" scope; - EcScope.Theory.enter scope (if abs then `Abstract else `Concrete) (unloc name) loca + EcScope.Theory.enter ?src scope (if abs then `Abstract else `Concrete) (unloc name) loca (* -------------------------------------------------------------------- *) and process_th_close (scope : EcScope.scope) (clears, name) = @@ -557,7 +557,7 @@ and process_th_require1 ld scope (nm, (sysname, thname), io) = try_finally (fun () -> let commands = EcIo.parseall (EcIo.from_file filename) in let commands = - List.fold_left + List.fold_left (fun scope g -> process_internal subld scope g.gl_action) iscope commands in commands) @@ -614,19 +614,21 @@ and process_sct_close (scope : EcScope.scope) name = EcScope.Section.exit scope name (* -------------------------------------------------------------------- *) -and process_tactics (scope : EcScope.scope) t = +(* Add and store src for proofs *) +and process_tactics ?(src : string option) (scope : EcScope.scope) t = let mode = (Pragma.get ()).pm_check in match t with - | `Actual t -> snd (EcScope.Tactics.process scope mode t) - | `Proof -> EcScope.Tactics.proof scope + | `Actual t -> snd (EcScope.Tactics.process ?src scope mode t) + | `Proof -> EcScope.Tactics.proof ?src scope (* -------------------------------------------------------------------- *) -and process_save (scope : EcScope.scope) ed = +(* Add and store src for proofs *) +and process_save ?(src : string option) (scope : EcScope.scope) ed = let (oname, scope) = match unloc ed with - | `Qed -> EcScope.Ax.save scope - | `Admit -> EcScope.Ax.admit scope - | `Abort -> (None, EcScope.Ax.abort scope) + | `Qed -> EcScope.Ax.save ?src scope + | `Admit -> EcScope.Ax.admit ?src scope + | `Abort -> (None, EcScope.Ax.abort ?src scope) in oname |> EcUtils.oiter (fun x -> EcScope.notify scope `Info "added lemma: `%s'" x); @@ -748,25 +750,25 @@ and process_dump scope (source, tc) = scope (* -------------------------------------------------------------------- *) -and process (ld : Loader.loader) (scope : EcScope.scope) g = +and process ?(src : string option) (ld : Loader.loader) (scope : EcScope.scope) g = let loc = g.pl_loc in let scope = match match g.pl_desc with - | Gtype t -> `Fct (fun scope -> process_types scope (List.map (mk_loc loc) t)) + | Gtype t -> `Fct (fun scope -> process_types ?src scope (List.map (mk_loc loc) t)) | Gsubtype t -> `Fct (fun scope -> process_subtype scope (mk_loc loc t)) | Gtypeclass t -> `Fct (fun scope -> process_typeclass scope (mk_loc loc t)) | Gtycinstance t -> `Fct (fun scope -> process_tycinst scope (mk_loc loc t)) - | Gmodule m -> `Fct (fun scope -> process_module scope m) - | Ginterface i -> `Fct (fun scope -> process_interface scope i) - | Goperator o -> `Fct (fun scope -> process_operator scope (mk_loc loc o)) - | Gprocop o -> `Fct (fun scope -> process_procop scope (mk_loc loc o)) - | Gpredicate p -> `Fct (fun scope -> process_predicate scope (mk_loc loc p)) + | Gmodule m -> `Fct (fun scope -> process_module ?src scope m) + | Ginterface i -> `Fct (fun scope -> process_interface ?src scope i) + | Goperator o -> `Fct (fun scope -> process_operator ?src scope (mk_loc loc o)) + | Gprocop o -> `Fct (fun scope -> process_procop ?src scope (mk_loc loc o)) + | Gpredicate p -> `Fct (fun scope -> process_predicate ?src scope (mk_loc loc p)) | Gnotation n -> `Fct (fun scope -> process_notation scope (mk_loc loc n)) | Gabbrev n -> `Fct (fun scope -> process_abbrev scope (mk_loc loc n)) - | Gaxiom a -> `Fct (fun scope -> process_axiom scope (mk_loc loc a)) - | GthOpen name -> `Fct (fun scope -> process_th_open scope name) + | Gaxiom a -> `Fct (fun scope -> process_axiom ?src scope (mk_loc loc a)) + | GthOpen name -> `Fct (fun scope -> process_th_open ?src scope name) | GthClose info -> `Fct (fun scope -> process_th_close scope info) | GthClear info -> `Fct (fun scope -> process_th_clear scope info) | GthRequire name -> `Fct (fun scope -> process_th_require ld scope name) @@ -780,11 +782,11 @@ and process (ld : Loader.loader) (scope : EcScope.scope) g = | Gprint p -> `Fct (fun scope -> process_print scope p; scope) | Gsearch qs -> `Fct (fun scope -> process_search scope qs; scope) | Glocate x -> `Fct (fun scope -> process_locate scope x; scope) - | Gtactics t -> `Fct (fun scope -> process_tactics scope t) + | Gtactics t -> `Fct (fun scope -> process_tactics ?src scope t) | Gtcdump info -> `Fct (fun scope -> process_dump scope info) | Grealize p -> `Fct (fun scope -> process_realize scope p) | Gprover_info pi -> `Fct (fun scope -> process_proverinfo scope pi) - | Gsave ed -> `Fct (fun scope -> process_save scope ed) + | Gsave ed -> `Fct (fun scope -> process_save ?src scope ed) | Gpragma opt -> `State (fun scope -> process_pragma scope opt) | Goption opt -> `Fct (fun scope -> process_option scope opt) | Gaddrw hint -> `Fct (fun scope -> process_addrw scope hint) @@ -827,7 +829,7 @@ type checkmode = { cm_iterate : bool; } -let initial ~checkmode ~boot = +let initial ~checkmode ~boot ~checkproof = let checkall = checkmode.cm_checkall in let profile = checkmode.cm_profile in let poptions = { EcScope.Prover.empty_options with @@ -850,7 +852,14 @@ let initial ~checkmode ~boot = scope [tactics; prelude] in let scope = EcScope.Prover.set_default scope poptions in - let scope = if checkall then EcScope.Prover.full_check scope else scope in + let scope = if checkproof then + begin + if checkall then + EcScope.Prover.full_check scope + else scope + end + else EcScope.Prover.check_proof scope false + in EcScope.freeze scope @@ -890,10 +899,10 @@ let push_context scope context = |> omap (fun st -> context.ct_current :: st); } (* -------------------------------------------------------------------- *) -let initialize ~restart ~undo ~boot ~checkmode = +let initialize ~restart ~undo ~boot ~checkmode ~checkproof = assert (restart || EcUtils.is_none !context); if restart then Pragma.set dpragma; - context := Some (rootctxt ~undo (initial ~checkmode ~boot)) + context := Some (rootctxt ~undo (initial ~checkmode ~boot ~checkproof)) (* -------------------------------------------------------------------- *) type notifier = EcGState.loglevel -> string Lazy.t -> unit @@ -903,6 +912,11 @@ let addnotifier (notifier : notifier) = let gstate = EcScope.gstate (oget !context).ct_root in ignore (EcGState.add_notifier notifier gstate) +(* -------------------------------------------------------------------- *) +let notify (level : EcGState.loglevel) fmt = + assert (EcUtils.is_some !context); + EcScope.notify (oget !context).ct_root level fmt + (* -------------------------------------------------------------------- *) let current () = (oget !context).ct_current @@ -925,19 +939,30 @@ let undo (olduuid : int) = context := Some (pop_context (oget !context)) done +(* -------------------------------------------------------------------- *) +let doc_comment (doc : [`Global | `Item] * string) : unit = + let current = oget !context in + let scope = current.ct_current in + let scope = EcScope.DocComment.add scope doc in + + context := Some (push_context scope current) + (* -------------------------------------------------------------------- *) let reset () = context := Some (rootctxt (oget !context).ct_root) (* -------------------------------------------------------------------- *) -let process ?(timed = false) ?(break = false) (g : global_action located) : float option = +let process + ?(src : string option) ?(timed = false) ?(break = false) + (g : global_action located) : float option += ignore break; let current = oget !context in let scope = current.ct_current in try - let (tdelta, oscope) = EcUtils.timed (process loader scope) g in + let (tdelta, oscope) = EcUtils.timed (process ?src loader scope) g in oscope |> oiter (fun scope -> context := Some (push_context scope current)); if timed then EcScope.notify scope `Info "time: %f" tdelta; @@ -997,7 +1022,36 @@ let pp_current_goal ?(all = false) stream = end end +(* -------------------------------------------------------------------- *) let pp_maybe_current_goal stream = match (Pragma.get ()).pm_verbose with | true -> pp_current_goal ~all:(Pragma.get ()).pm_g_prall stream | false -> () + +(* -------------------------------------------------------------------- *) +let pp_all_goals () = + let scope = current () in + + match S.xgoal scope with + | Some { S.puc_active = Some ({ puc_jdg = S.PSCheck pf }, _) } -> begin + match EcCoreGoal.opened pf with + | None -> + [] + + | Some _ -> + let get_hc { EcCoreGoal.g_hyps; EcCoreGoal.g_concl } = + (EcEnv.LDecl.tohyps g_hyps, g_concl) + in + + let ppe = EcPrinting.PPEnv.ofenv (S.env scope) in + let goals = List.map get_hc (EcCoreGoal.all_opened pf) in + + List.map (fun goal -> + let buffer = Buffer.create 0 in + Format.fprintf + (Format.formatter_of_buffer buffer) + "%a@?" (EcPrinting.pp_goal1 ppe) goal; + Buffer.contents buffer) goals + end + + | _ -> [] diff --git a/src/ecCommands.mli b/src/ecCommands.mli index 69e6c47fd..a72d31a43 100644 --- a/src/ecCommands.mli +++ b/src/ecCommands.mli @@ -24,17 +24,19 @@ type checkmode = { cm_iterate : bool; } -val initial : checkmode:checkmode -> boot:bool -> EcScope.scope +val initial : checkmode:checkmode -> boot:bool -> checkproof:bool -> EcScope.scope val initialize : restart:bool -> undo:bool -> boot:bool -> checkmode:checkmode + -> checkproof:bool -> unit val current : unit -> EcScope.scope val addnotifier : notifier -> unit +val notify : EcGState.loglevel -> ('a, Format.formatter, unit, unit) format4 -> 'a (* -------------------------------------------------------------------- *) val process_internal : @@ -44,7 +46,7 @@ val process_internal : -> EcScope.scope (* -------------------------------------------------------------------- *) -val process : ?timed:bool -> ?break:bool -> +val process : ?src:string -> ?timed:bool -> ?break:bool -> EcParsetree.global_action located -> float option val undo : int -> unit @@ -54,9 +56,12 @@ val mode : unit -> string val check_eco : string -> bool +val doc_comment : [`Global | `Item] * string -> unit + (* -------------------------------------------------------------------- *) val pp_current_goal : ?all:bool -> Format.formatter -> unit val pp_maybe_current_goal : Format.formatter -> unit +val pp_all_goals : unit -> string list (* -------------------------------------------------------------------- *) val pragma_verbose : bool -> unit diff --git a/src/ecCoreModules.ml b/src/ecCoreModules.ml index 6f96118ab..a07f9ab2a 100644 --- a/src/ecCoreModules.ml +++ b/src/ecCoreModules.ml @@ -494,6 +494,9 @@ type top_module_expr = { tme_loca : locality; } +let is_me_body_alias (body : module_body) = + match body with ME_Alias _ -> true | _ -> false + (* -------------------------------------------------------------------- *) let ur_hash = EcAst.ur_hash diff --git a/src/ecCoreModules.mli b/src/ecCoreModules.mli index 1b84c0df2..178b4c581 100644 --- a/src/ecCoreModules.mli +++ b/src/ecCoreModules.mli @@ -247,6 +247,8 @@ type top_module_expr = { tme_loca : locality; } +val is_me_body_alias : module_body -> bool + (* -------------------------------------------------------------------- *) val mty_equal : module_type -> diff --git a/src/ecCorePrinting.ml b/src/ecCorePrinting.ml index d906a61e3..29051f0a0 100644 --- a/src/ecCorePrinting.ml +++ b/src/ecCorePrinting.ml @@ -112,6 +112,8 @@ module type PrinterAPI = sig val pp_hyps : PPEnv.t -> EcEnv.LDecl.hyps pp val pp_goal : PPEnv.t -> prpo_display -> ppgoal pp + val pp_goal1 : PPEnv.t -> (EcBaseLogic.hyps * form) pp + (* ------------------------------------------------------------------ *) val pp_by_theory : PPEnv.t -> (PPEnv.t -> (EcPath.path * 'a) pp) -> ((EcPath.path * 'a) list) pp diff --git a/src/ecCoreSubst.ml b/src/ecCoreSubst.ml index c39a87a27..161d7d3c7 100644 --- a/src/ecCoreSubst.ml +++ b/src/ecCoreSubst.ml @@ -14,7 +14,7 @@ type mod_extra = { mex_glob : memory -> form; } -type sc_instanciate = { +type sc_instantiate = { sc_memtype : memtype; sc_mempred : mem_pr Mid.t; sc_expr : expr Mid.t; diff --git a/src/ecCoreSubst.mli b/src/ecCoreSubst.mli index 80531ef9c..f829b8d38 100644 --- a/src/ecCoreSubst.mli +++ b/src/ecCoreSubst.mli @@ -8,7 +8,7 @@ open EcCoreModules open EcCoreFol (* -------------------------------------------------------------------- *) -type sc_instanciate = { +type sc_instantiate = { sc_memtype : memtype; sc_mempred : mem_pr Mid.t; sc_expr : expr Mid.t; diff --git a/src/ecDecl.ml b/src/ecDecl.ml index 5636641ac..bcc414242 100644 --- a/src/ecDecl.ml +++ b/src/ecDecl.ml @@ -15,42 +15,42 @@ type ty_param = EcIdent.t * EcPath.Sp.t type ty_params = ty_param list type ty_pctor = [ `Int of int | `Named of ty_params ] -type tydecl = { - tyd_params : ty_params; - tyd_type : ty_body; - tyd_loca : locality; -} - -and ty_body = [ - | `Concrete of EcTypes.ty - | `Abstract of Sp.t - | `Datatype of ty_dtype - | `Record of ty_record -] - -and ty_record = +type ty_record = EcCoreFol.form * (EcSymbols.symbol * EcTypes.ty) list -and ty_dtype_ctor = +type ty_dtype_ctor = EcSymbols.symbol * EcTypes.ty list -and ty_dtype = { +type ty_dtype = { tydt_ctors : ty_dtype_ctor list; tydt_schelim : EcCoreFol.form; tydt_schcase : EcCoreFol.form; } +type ty_body = + | Concrete of EcTypes.ty + | Abstract of Sp.t + | Datatype of ty_dtype + | Record of ty_record + + +type tydecl = { + tyd_params : ty_params; + tyd_type : ty_body; + tyd_loca : locality; +} + let tydecl_as_concrete (td : tydecl) = - match td.tyd_type with `Concrete x -> Some x | _ -> None + match td.tyd_type with Concrete x -> Some x | _ -> None let tydecl_as_abstract (td : tydecl) = - match td.tyd_type with `Abstract x -> Some x | _ -> None + match td.tyd_type with Abstract x -> Some x | _ -> None let tydecl_as_datatype (td : tydecl) = - match td.tyd_type with `Datatype x -> Some x | _ -> None + match td.tyd_type with Datatype x -> Some x | _ -> None let tydecl_as_record (td : tydecl) = - match td.tyd_type with `Record x -> Some x | _ -> None + match td.tyd_type with Record (x, y) -> Some (x, y) | _ -> None (* -------------------------------------------------------------------- *) let abs_tydecl ?(tc = Sp.empty) ?(params = `Int 0) lc = @@ -65,10 +65,10 @@ let abs_tydecl ?(tc = Sp.empty) ?(params = `Int 0) lc = (EcUid.NameGen.bulk ~fmt n) in - { tyd_params = params; tyd_type = `Abstract tc; tyd_loca = lc; } + { tyd_params = params; tyd_type = Abstract tc; tyd_loca = lc; } (* -------------------------------------------------------------------- *) -let ty_instanciate (params : ty_params) (args : ty list) (ty : ty) = +let ty_instantiate (params : ty_params) (args : ty list) (ty : ty) = let subst = CS.Tvar.init (List.map fst params) args in CS.Tvar.subst subst ty diff --git a/src/ecDecl.mli b/src/ecDecl.mli index 7864a0e0d..a974a6048 100644 --- a/src/ecDecl.mli +++ b/src/ecDecl.mli @@ -11,31 +11,31 @@ type ty_param = EcIdent.t * EcPath.Sp.t type ty_params = ty_param list type ty_pctor = [ `Int of int | `Named of ty_params ] -type tydecl = { - tyd_params : ty_params; - tyd_type : ty_body; - tyd_loca : locality; -} - -and ty_body = [ - | `Concrete of EcTypes.ty - | `Abstract of Sp.t - | `Datatype of ty_dtype - | `Record of ty_record -] - -and ty_record = +type ty_record = EcCoreFol.form * (EcSymbols.symbol * EcTypes.ty) list -and ty_dtype_ctor = +type ty_dtype_ctor = EcSymbols.symbol * EcTypes.ty list -and ty_dtype = { +type ty_dtype = { tydt_ctors : ty_dtype_ctor list; tydt_schelim : EcCoreFol.form; tydt_schcase : EcCoreFol.form; } +type ty_body = + | Concrete of EcTypes.ty + | Abstract of Sp.t + | Datatype of ty_dtype + | Record of ty_record + + +type tydecl = { + tyd_params : ty_params; + tyd_type : ty_body; + tyd_loca : locality; +} + val tydecl_as_concrete : tydecl -> EcTypes.ty option val tydecl_as_abstract : tydecl -> Sp.t option val tydecl_as_datatype : tydecl -> ty_dtype option @@ -43,7 +43,7 @@ val tydecl_as_record : tydecl -> (form * (EcSymbols.symbol * EcTypes.ty) list) val abs_tydecl : ?tc:Sp.t -> ?params:ty_pctor -> locality -> tydecl -val ty_instanciate : ty_params -> ty list -> ty -> ty +val ty_instantiate : ty_params -> ty list -> ty -> ty (* -------------------------------------------------------------------- *) type locals = EcIdent.t list diff --git a/src/ecDoc.ml b/src/ecDoc.ml new file mode 100644 index 000000000..01b343770 --- /dev/null +++ b/src/ecDoc.ml @@ -0,0 +1,338 @@ +(* -------------------------------------------------------------------- *) +open Tyxml.Html + +open EcScope + +(* -------------------------------------------------------------------- *) +let styles_file : string = + let (module Sites) = EcRelocate.sites in + Filename.concat Sites.doc "styles.css" + +let stdlib_doc_dp (th : string) : string = + match th with + | _ -> "" + +(* -------------------------------------------------------------------- *) +let from_stdlib (th : string) : bool = + match th with + | _ -> false + +(* -------------------------------------------------------------------- *) +let c_filename ?(ext : string option) (nms : string list) = + match ext with + | None -> String.concat "!" nms + | Some ext -> String.concat "!" nms ^ ext + +(* -------------------------------------------------------------------- *) +let thkind_str (kind : EcLoader.kind) : string = + match kind with + | `Ec -> "Theory" + | `EcA -> "Abstract Theory" + +(* -------------------------------------------------------------------- *) +let itemkind_str_pl (ik : itemkind) : string = + match ik with + | `Type -> "Types" + | `Operator -> "Operators" + | `Axiom -> "Axioms" + | `Lemma -> "Lemmas" + | `ModuleType -> "Module Types" + | `Module -> "Modules" + | `Theory -> "Theories" + +let itemkind_lookup_path (ik : itemkind) (q : EcSymbols.qsymbol) (env : EcEnv.env) = + match ik with + | `Type -> EcEnv.Ty.lookup_path q env + | `Operator -> EcEnv.Op.lookup_path q env + | `Axiom -> EcEnv.Ax.lookup_path q env + | `Lemma -> EcEnv.Ax.lookup_path q env + | `ModuleType -> EcEnv.ModTy.lookup_path q env + | `Module -> + begin + match (EcEnv.Mod.lookup_path q env).m_top with + | `Concrete (p, None) -> p + | `Concrete (_, Some _) -> failwith "Linking to sub-modules not supported." + | `Local _ -> failwith "Linking to local/declared modules not supported." + end + | `Theory -> EcEnv.Theory.lookup_path ~mode:`All q env + +(* -------------------------------------------------------------------- *) +let rec bot_env_of_qsymbol (q : EcSymbols.qsymbol) (env : EcEnv.env)= + match fst q with + | [] | ["Top"] -> env + | x :: xs -> + let p = EcEnv.Theory.lookup_path ~mode:`All ([], x) env in + let env = EcEnv.Theory.env_of_theory p env in + bot_env_of_qsymbol (xs, snd q) env + +let filename_of_path ?(ext : string option) (rth : string) (p : EcPath.path) = + let qs = EcPath.toqsymbol p in + match fst qs with + | [] -> assert false + | ["Top"] -> c_filename ?ext [rth] + | "Top" :: ts -> + let reqrt = (List.hd ts) in + if from_stdlib reqrt then + Filename.concat (stdlib_doc_dp reqrt) (c_filename ?ext ts) + else + (c_filename ?ext (rth :: ts)) + | _ -> assert false + +(* -------------------------------------------------------------------- *) +let md_pre_format ~kind (s : string) = + match kind with | _ -> pre [txt s] + +let md_href_format (rth : string) (env : EcEnv.env) (hr : Markdown.href) : Html_types.phrasing elt = + let il_format = Str.regexp "^>\\([^|]*\\)|\\([^|]+\\)$" in + if Str.string_match il_format hr.href_target 0 then + let tkind = Str.matched_group 1 hr.href_target in + let tname = Str.matched_group 2 hr.href_target in + let tqs = EcSymbols.qsymbol_of_string tname in + let env = bot_env_of_qsymbol tqs env in + let ikstr, path = + match tkind with + | "Ty" | "Type" -> itemkind_str_pl `Type, itemkind_lookup_path `Type tqs env + | "Op" | "Operator" -> itemkind_str_pl `Operator, itemkind_lookup_path `Operator tqs env + | "Ax" | "Axiom" -> itemkind_str_pl `Axiom, itemkind_lookup_path `Axiom tqs env + | "Lem" | "Lemma" -> itemkind_str_pl `Lemma, itemkind_lookup_path `Lemma tqs env + | "ModTy" | "ModuleType" -> itemkind_str_pl `ModuleType, itemkind_lookup_path `ModuleType tqs env + | "Mod" | "Module" -> itemkind_str_pl `Module, itemkind_lookup_path `Module tqs env + | "Th" | "Theory" -> itemkind_str_pl `Theory, itemkind_lookup_path `Theory tqs env + | "" -> + let rec try_lookup = function + | [] -> failwith (Printf.sprintf "No item/entity found with name `%s'." tname) + | ik :: iks -> + try itemkind_str_pl ik, itemkind_lookup_path ik tqs env + with EcEnv.LookupFailure _ -> try_lookup iks + in + let iks = [`Type; `Operator; `Axiom; `Lemma; `ModuleType; `Module; `Theory] in + try_lookup iks + | _ -> failwith (Printf.sprintf "Invalid item/entity kind `%s'." tkind) + in + let fn = filename_of_path ~ext:".html" rth path in + let il = fn ^ "#" ^ ikstr ^ snd tqs in + a ~a:[a_href (uri_of_string il)] [txt hr.href_desc] + else + a ~a:[a_href (uri_of_string hr.href_target)] [txt hr.href_desc] + +let md_img_format (_ : Markdown.img_ref) = + failwith "Image embedding not supported." + +let c_markdown (input : string) (rth : string) (env : EcEnv.env) = + let input = Markdown.parse_text input in + + MarkdownHTML.to_html + ~render_pre:md_pre_format + ~render_link:(md_href_format rth env) + ~render_img:md_img_format + input + + +(* -------------------------------------------------------------------- *) +let c_head (tstr : string) : [> Html_types.head] elt = + head (title (txt tstr)) [link ~rel:[`Stylesheet] ~href:styles_file ()] + +(* -------------------------------------------------------------------- *) +let c_sidebar (th : string) (lents : EcScope.docentity list) = + let iks = [`Type; `Operator; `Axiom; `Lemma; `ModuleType; `Module; `Theory] in + let iksin = + List.filter (fun ik -> + List.exists (fun ldoc -> + match ldoc with + | ItemDoc (_, (_, ikp, _, _)) -> ikp = ik + | SubDoc ((_, (_, ikp, _, _)), _) -> ikp = ik) lents) iks + in + nav ~a:[a_class ["sidebar"]] + [ + div ~a:[a_class["sidebar-title"]] + [ + h2 [txt "EasyCrypt Documentation"]; + span ~a:[a_class ["sidebar-title-theory"]] [txt th] + ]; + div ~a:[a_class ["sidebar-elems"]] + [ + ul ~a:[a_class ["sidebar-section-list"]] + (List.map (fun ik -> + let ikstr = itemkind_str_pl ik in + li [a ~a:[a_href (Xml.uri_of_string ("#" ^ ikstr))] [txt ikstr]]) iksin) + ] + ] + +(* -------------------------------------------------------------------- *) +let c_section_intro (rth : string) (gdoc : string list) (env : EcEnv.env) = + match gdoc with + | [] -> [] + | _ -> [ + let ids = "Introduction" in + section ~a:[a_id ids; a_title ids; a_class ["intro-section"]] [ + div ~a:[a_class ["intro-text-container"]] + (List.map (fun s -> div ~a:[a_class ["intro-par-container"]] (c_markdown s rth env)) gdoc) + ] + ] + +(* -------------------------------------------------------------------- *) +let c_section_main_itemkind_li ?(supthf : string option) (rth : string) (th : string) (lent_ik : EcScope.docentity) (env : EcEnv.env) = + match lent_ik with + | SubDoc ((doc, (_, ik, subth, _)), _) -> + begin + match ik with + | `Theory -> + let (hdoc, tdoc) = + if doc = [] then "No description available.", [] + else if List.length doc = 1 then List.hd doc, [] + else List.hd doc, List.tl doc + in + let hn = + match supthf with + | None -> c_filename ~ext:(".html") [th; subth] + | Some supf -> c_filename ~ext:(".html") [supf; th; subth] + in + li ~a:[a_id (itemkind_str_pl ik ^ subth); a_class ["item-entry"]] ([ + div ~a:[a_class ["item-name-desc-container"]] [ + div ~a:[a_class ["item-name"]] [a ~a:[a_href (Xml.uri_of_string hn)] [txt subth]]; + div ~a:[a_class ["item-basic-desc"]] (c_markdown hdoc rth env) + ] + ] @ (if tdoc <> [] + then [details ~a:[a_class ["item-details"]] (summary []) + (List.map (fun d -> div ~a:[a_class ["item-details-par"]] (c_markdown d rth env)) tdoc)] + else [])) + | _ -> assert false + end + | ItemDoc (doc, (_, ik, nm, src)) -> + let psrc = String.trim (String.concat "\n" src) in + match ik with + | `Theory -> assert false + | _ -> + let (hdoc, tdoc) = + if doc = [] then "No description available. (However, see source below.)", [] + else if List.length doc = 1 then List.hd doc, [] + else List.hd doc, List.tl doc + in + li ~a:[a_id (itemkind_str_pl ik ^ nm) ; a_class ["item-entry"]] [ + div ~a:[a_class ["item-name-desc-container"]] [ + div ~a:[a_class ["item-name"]] [txt nm]; + div ~a:[a_class ["item-basic-desc"]] (c_markdown hdoc rth env) + ]; + details ~a:[a_class ["item-details"]] (summary []) + (List.map (fun d -> div ~a:[a_class ["item-details-par"]] (c_markdown d rth env)) tdoc + @ [div ~a:[a_class ["source-container"]] + [txt "Source:"; pre ~a:[a_class ["source"]] [txt psrc]]]) + ] + +(* -------------------------------------------------------------------- *) +let c_section_main_itemkind ?(supthf : string option) (rth : string) (th : string) (lents_ik : EcScope.docentity list) (env : EcEnv.env) = + [ + ul ~a:[a_class ["item-list"]] + (List.map (fun lent_ik -> c_section_main_itemkind_li ?supthf rth th lent_ik env) lents_ik) + ] + +(* -------------------------------------------------------------------- *) +let c_section_main ?(supthf : string option) (rth : string) (th : string) (lents : EcScope.docentity list) (env : EcEnv.env) = + let iks = [`Type; `Operator; `Axiom; `Lemma; `ModuleType; `Module; `Theory] in + List.concat + (List.map (fun ik -> + let lents_ik = List.filter (fun ent -> + match ent with + | ItemDoc (_, (md, ikp, _, _)) -> md = `Specific && ikp = ik + | SubDoc ((_, (_, ikp, _, _)), _) -> ikp = ik) lents + in + match lents_ik with + | [] -> [] + | _ -> [ + let iks = itemkind_str_pl ik in + section ~a:[a_id iks; a_title iks; a_class ["item-section"]] [ + h2 ~a:[a_class ["section-heading"]] [txt iks]; + div ~a:[a_class ["item-list-container"]] (c_section_main_itemkind ?supthf rth th lents_ik env) + ] + ] + ) + iks) + +let c_body ?(supths : string option) ?(supthf : string option) (rth : string) (th : string) (tstr : string) (gdoc : string list) (ldocents : EcScope.docentity list) (env : EcEnv.env) : [> Html_types.body] elt = + let sidebar = c_sidebar th ldocents in + let page_heading = + div ~a:[a_class ["page-heading-container"]] + (h1 ~a:[a_class ["page-heading"]] [txt tstr] + :: + match supths with + | None -> [] + | Some sup -> + match supthf with + | None -> assert false + | Some supf -> + [ + h2 ~a:[a_class ["page-subheading"]] [ + txt ("Subtheory of "); + a ~a:[a_href (Xml.uri_of_string (supf ^ ".html" ^ "#" ^ itemkind_str_pl `Theory ^ th))] [txt sup] + ] + ]) + in + let sec_intro = c_section_intro rth gdoc env in + let sec_main = c_section_main ?supthf rth th ldocents env in + body (sidebar :: [main (page_heading :: sec_intro @ sec_main)]) + +(* -------------------------------------------------------------------- *) +let c_page ?(supths : string option) ?(supthf : string option) (rth : string) (th : string) (tstr : string) (gdoc : string list) (ldocents : EcScope.docentity list) (env : EcEnv.env) : [> Html_types.html] elt = + html (c_head tstr) (c_body ?supths ?supthf rth th tstr gdoc ldocents env) + +(* -------------------------------------------------------------------- *) +let emit_page (dp : string) (fn : string) (page : [> Html_types.html ] elt) = + let wp = Filename.concat dp fn ^ ".html" in + let file = open_out wp in + let fmt = Format.formatter_of_out_channel file in + pp () fmt page; + Format.fprintf fmt "@."; + close_out file + +(* -------------------------------------------------------------------- *) +let emit_pages (dp : string) (th : string) (tstr : string) (gdoc : string list) (ldocents : EcScope.docentity list) (env : EcEnv.env) = + let rec c_subpages ?supths ?supthf th docents = + match docents with + | [] -> [] + | de :: docents' -> + match de with + | ItemDoc _ -> c_subpages ?supths ?supthf th docents' + | SubDoc ((sgdoc, (smd, _, sth, _)), sldocents) -> + let ststr = (if smd = `Abstract then "Abstract " else "") ^ "Theory " ^ sth in + let stsupf = + match supthf with + | None -> th + | Some supf -> c_filename [supf; th] + in + let stf = c_filename [stsupf; sth] in + (stf, c_page ~supths:th ~supthf:stsupf th sth ststr sgdoc sldocents env) + :: c_subpages ~supths:th ~supthf:stsupf sth sldocents + @ c_subpages ?supths ?supthf th docents' + in + let spgs = c_subpages th ldocents in + List.iter (fun fnpg -> emit_page dp (fst fnpg) (snd fnpg)) spgs; + emit_page dp th (c_page th th tstr gdoc ldocents env) + +(* -------------------------------------------------------------------- *) +(* input = input name, scope contains all documentation items *) +let generate_html ?(outdirp : string option) (fname : string option) (scope : EcScope.scope) : unit = + match fname with + | Some fn -> + let kind = + try EcLoader.getkind (Filename.extension fn) + with EcLoader.BadExtension _ -> assert false + in + let dp = + match outdirp with + | None -> Filename.dirname fn + | Some outdirp -> + try + if Sys.is_directory outdirp + then outdirp + else raise (Invalid_argument (Format.sprintf "%s is not an existing directory." outdirp)) + with + | _ as ex -> Printf.eprintf "Exception: %s\n." (Printexc.to_string ex); raise ex + in + let fn = Filename.basename fn in + let th = Filename.remove_extension fn in + let tstr = thkind_str kind ^ " " ^ th in + begin + try emit_pages dp th tstr (get_gdocstrings scope) (get_ldocentities scope) (env scope) with + | _ as ex -> Printf.eprintf "Exception: %s\n." (Printexc.to_string ex); raise ex + end + | None -> () diff --git a/src/ecDoc.mli b/src/ecDoc.mli new file mode 100644 index 000000000..1e8fd31d2 --- /dev/null +++ b/src/ecDoc.mli @@ -0,0 +1,2 @@ +(* -------------------------------------------------------------------- *) +val generate_html : ?outdirp:string -> string option -> EcScope.scope -> unit \ No newline at end of file diff --git a/src/ecEco.ml b/src/ecEco.ml index cd80c8020..fc8f41c98 100644 --- a/src/ecEco.ml +++ b/src/ecEco.ml @@ -5,7 +5,7 @@ module Json = Yojson (* -------------------------------------------------------------------- *) module Version = struct - let current : int = 3 + let current : int = 4 end (* -------------------------------------------------------------------- *) @@ -16,9 +16,16 @@ type ecoroot = { eco_digest : digest; } +type ecorange = int + +type ecotrace1 = { goals: string list; messages: string; } + +type ecotrace = (ecorange * ecotrace1) list + type eco = { eco_root : ecoroot; eco_depends : ecodepend Mstr.t; + eco_trace : ecotrace option; } and ecodepend = @@ -36,6 +43,24 @@ let flag_of_json (data : Json.t) : bool = let flag_to_json (flag : bool) : Json.t = `Bool flag +(* -------------------------------------------------------------------- *) +let int_of_json (data : Json.t) : int = + match data with + | `Int i -> i + | _ -> raise InvalidEco + +(* -------------------------------------------------------------------- *) +let string_of_json (data : Json.t) : string = + match data with + | `String s -> s + | _ -> raise InvalidEco + +(* -------------------------------------------------------------------- *) +let list_of_json (tx : Json.t -> 'a) (data : Json.t) : 'a list = + match data with + | `List data -> List.map tx data + | _ -> raise InvalidEco + (* -------------------------------------------------------------------- *) let kind_to_json (k : EcLoader.kind) = match k with @@ -71,9 +96,9 @@ let ecoroot_to_map (ecor : ecoroot) : (string * Json.t) list = "digest", digest_to_json ecor.eco_digest ] let ecoroot_of_map (data : Json.t Mstr.t) : ecoroot = - let kd = kind_of_json (Mstr.find_exn InvalidEco "kind" data) in - let dg = digest_of_json (Mstr.find_exn InvalidEco "digest" data) in - { eco_kind = kd; eco_digest = dg; } + let eco_kind = kind_of_json (Mstr.find_exn InvalidEco "kind" data) in + let eco_digest = digest_of_json (Mstr.find_exn InvalidEco "digest" data) in + { eco_kind; eco_digest; } (* -------------------------------------------------------------------- *) let ecoroot_to_json (ecor : ecoroot) : Json.t = @@ -86,6 +111,43 @@ let ecoroot_of_json (data : Json.t) : ecoroot = | _ -> raise InvalidEco +(* -------------------------------------------------------------------- *) +let trace_to_json (trace : ecotrace option) : Json.t = + match trace with + | None -> + `Null + + | Some trace -> + let for1 ((position, { goals; messages; })) = + `Assoc [ + ("position", `Int position); + ("goals" , `List (List.map (fun goal -> `String goal) goals)); + ("messages", `String messages); + ] + in `List (List.map for1 trace) + +let trace_of_json (data : Json.t) : ecotrace option = + match data with + | `Null -> + None + + | `List data -> + let for1 (data : Json.t) = + match data with + | `Assoc data -> + let data = Mstr.of_list data in + let position = Mstr.find_exn InvalidEco "position" data |> int_of_json in + let goals = Mstr.find_exn InvalidEco "goals" data |> list_of_json string_of_json in + let messages = Mstr.find_exn InvalidEco "messages" data |> string_of_json in + (position, { goals; messages; }) + | _ -> + raise InvalidEco + + in Some (List.map for1 data) + + | _ -> + raise InvalidEco + (* -------------------------------------------------------------------- *) let ecodepend_to_json ((ecor, direct) : ecodepend) : Json.t = `Assoc ([ "direct", flag_to_json direct] @ (ecoroot_to_map ecor)) @@ -119,6 +181,7 @@ let to_json (eco : eco) : Json.t = [ "version", `Int Version.current; "echash" , `String EcVersion.hash; "root" , ecoroot_to_json eco.eco_root; + "trace" , trace_to_json eco.eco_trace; "depends", `Assoc depends ] (* -------------------------------------------------------------------- *) @@ -135,10 +198,11 @@ let of_json (data : Json.t) : eco = if echash <> `String EcVersion.hash then raise InvalidEco; - let root = ecoroot_of_json (Mstr.find_exn InvalidEco "root" data) in - let depends = depends_of_json (Mstr.find_exn InvalidEco "depends" data) in + let eco_root = ecoroot_of_json (Mstr.find_exn InvalidEco "root" data) in + let eco_depends = depends_of_json (Mstr.find_exn InvalidEco "depends" data) in + let eco_trace = trace_of_json (Mstr.find_exn InvalidEco "trace" data) in - { eco_root = root; eco_depends = depends; } + { eco_root; eco_depends; eco_trace; } | _ -> raise InvalidEco diff --git a/src/ecEnv.ml b/src/ecEnv.ml index a4a5c8a7c..4490390b9 100644 --- a/src/ecEnv.ml +++ b/src/ecEnv.ml @@ -781,10 +781,10 @@ module MC = struct let loca = tyd.tyd_loca in match tyd.tyd_type with - | `Concrete _ -> mc - | `Abstract _ -> mc + | Concrete _ -> mc + | Abstract _ -> mc - | `Datatype dtype -> + | Datatype dtype -> let cs = dtype.tydt_ctors in let schelim = dtype.tydt_schelim in let schcase = dtype.tydt_schcase in @@ -828,7 +828,7 @@ module MC = struct _up_operator candup mc name (ipath name, op) ) mc projs - | `Record (scheme, fields) -> + | Record (scheme, fields) -> let params = List.map (fun (x, _) -> tvar x) tyd.tyd_params in let nfields = List.length fields in let cfields = @@ -2360,7 +2360,7 @@ module NormMp = struct match item with | MI_Module me -> mod_use env rm fdone us (EcPath.mqname mp me.me_name) | MI_Variable v -> add_var env (xpath mp v.v_name) us - | MI_Function f -> fun_use_aux env rm fdone us (xpath mp f.f_name) + | MI_Function f -> gen_fun_use env fdone rm us (xpath mp f.f_name) and body_use env rm fdone mp us comps body = match body with @@ -2372,9 +2372,6 @@ module NormMp = struct | ME_Structure ms -> List.fold_left (item_use env rm fdone mp) us ms.ms_body - and fun_use_aux env rm fdone us f = - gen_fun_use env fdone rm us f - let mod_use_top env mp = let mp = norm_mpath env mp in let me, _ = Mod.by_mpath mp env in @@ -2544,12 +2541,12 @@ module Ty = struct let defined (name : EcPath.path) (env : env) = match by_path_opt name env with - | Some { tyd_type = `Concrete _ } -> true + | Some { tyd_type = Concrete _ } -> true | _ -> false let unfold (name : EcPath.path) (args : EcTypes.ty list) (env : env) = match by_path_opt name env with - | Some ({ tyd_type = `Concrete body } as tyd) -> + | Some ({ tyd_type = Concrete body } as tyd) -> Tvar.subst (Tvar.init (List.map fst tyd.tyd_params) args) body @@ -2585,14 +2582,15 @@ module Ty = struct match ty.ty_node with | Tconstr (p, tys) -> begin match by_path_opt p env with - | Some ({ tyd_type = (`Datatype _ | `Record _) as body }) -> + | Some ({ tyd_type = (Datatype _ | Record _) as body }) -> let prefix = EcPath.prefix p in let basename = EcPath.basename p in let basename = match body, mode with - | `Record _, (`Ind | `Case) -> basename ^ "_ind" - | `Datatype _, `Ind -> basename ^ "_ind" - | `Datatype _, `Case -> basename ^ "_case" + | Record _, (`Ind | `Case) -> basename ^ "_ind" + | Datatype _, `Ind -> basename ^ "_ind" + | Datatype _, `Case -> basename ^ "_case" + | _, _ -> assert false in Some (EcPath.pqoname prefix basename, tys) | _ -> None @@ -2608,7 +2606,7 @@ module Ty = struct let env = MC.bind_tydecl name ty env in match ty.tyd_type with - | `Abstract tc -> + | Abstract tc -> let myty = let myp = EcPath.pqname (root env) name in let typ = List.map (fst_map EcIdent.fresh) ty.tyd_params in @@ -2814,7 +2812,7 @@ module Ax = struct let rebind name ax env = MC.bind_axiom name ax env - let instanciate p tys env = + let instantiate p tys env = match by_path_opt p env with | Some ({ ax_spec = f } as ax) -> Tvar.f_subst ~freshen:true (List.map fst ax.ax_tparams) tys f @@ -2934,7 +2932,7 @@ module Theory = struct | Th_type (x, tyd) -> begin match tyd.tyd_type with - | `Abstract tc -> + | Abstract tc -> let myty = let typ = List.map (fst_map EcIdent.fresh) tyd.tyd_params in (typ, EcTypes.tconstr (xpath x) (List.map (tvar |- fst) typ)) diff --git a/src/ecEnv.mli b/src/ecEnv.mli index 5a1d5bf60..fe21dc247 100644 --- a/src/ecEnv.mli +++ b/src/ecEnv.mli @@ -168,7 +168,7 @@ module Ax : sig val iter : ?name:qsymbol -> (path -> t -> unit) -> env -> unit val all : ?check:(path -> t -> bool) -> ?name:qsymbol -> env -> (path * t) list - val instanciate : path -> EcTypes.ty list -> env -> form + val instantiate : path -> EcTypes.ty list -> env -> form end (* -------------------------------------------------------------------- *) diff --git a/src/ecFol.mli b/src/ecFol.mli index 108bed966..080a4d3de 100644 --- a/src/ecFol.mli +++ b/src/ecFol.mli @@ -116,7 +116,7 @@ val f_ty_app : EcEnv.env -> form -> form list -> form (* -------------------------------------------------------------------- *) (* WARNING : this function should be use only in a context ensuring - * that the quantified variables can be instanciated *) + * that the quantified variables can be instantiated *) val f_betared : form -> form diff --git a/src/ecHiGoal.ml b/src/ecHiGoal.ml index f08413545..b16b7eb03 100644 --- a/src/ecHiGoal.ml +++ b/src/ecHiGoal.ml @@ -1515,8 +1515,12 @@ let rec process_mintros_1 ?(cf = true) ttenv pis gs = and intro1_rw (_ : ST.state) (o, s) tc = let h = EcIdent.create "_" in let rwt tc = - let pt = PT.pt_of_hyp !!tc (FApi.tc1_hyps tc) h in - process_rewrite1_core ~close:false (s, None, o) pt tc + match LDecl.by_id h (FApi.tc1_hyps tc) with + | LD_hyp _ -> + let pt = PT.pt_of_hyp !!tc (FApi.tc1_hyps tc) h in + process_rewrite1_core ~close:false (s, None, o) pt tc + | _ -> + tc_error !!tc "top assumption is not an hypothesis"; in t_seqs [t_intros_i [h]; rwt; t_clear h] tc and intro1_unfold (_ : ST.state) (s, o) p tc = diff --git a/src/ecHiInductive.ml b/src/ecHiInductive.ml index a51dede08..72cf50e85 100644 --- a/src/ecHiInductive.ml +++ b/src/ecHiInductive.ml @@ -23,7 +23,7 @@ type dterror = | DTE_TypeError of TT.tyerror | DTE_DuplicatedCtor of symbol | DTE_InvalidCTorType of symbol * TT.tyerror -| DTE_NonPositive +| DTE_NonPositive of symbol * EI.non_positive_context | DTE_Empty type fxerror = @@ -52,7 +52,7 @@ let trans_record (env : EcEnv.env) (name : ptydname) (rc : precord) = Msym.odup unloc (List.map fst rc) |> oiter (fun (x, y) -> rcerror y.pl_loc env (RCE_DuplicatedField x.pl_desc)); - (* Check for emptyness *) + (* Check for emptiness *) if List.is_empty rc then rcerror loc env RCE_Empty; @@ -84,7 +84,7 @@ let trans_datatype (env : EcEnv.env) (name : ptydname) (dt : pdatatype) = let env0 = let myself = { tyd_params = EcUnify.UniEnv.tparams ue; - tyd_type = `Abstract EcPath.Sp.empty; + tyd_type = Abstract EcPath.Sp.empty; tyd_loca = lc; } in EcEnv.Ty.bind (unloc name) myself env @@ -106,7 +106,7 @@ let trans_datatype (env : EcEnv.env) (name : ptydname) (dt : pdatatype) = dt |> List.map for1 in - (* Check for emptyness *) + (* Check for emptiness *) begin let rec isempty_n (ctors : (ty list) list) = List.for_all isempty_1 ctors @@ -131,21 +131,24 @@ let trans_datatype (env : EcEnv.env) (name : ptydname) (dt : pdatatype) = let tdecl = EcEnv.Ty.by_path_opt tname env0 |> odfl (EcDecl.abs_tydecl ~params:(`Named tparams) lc) in - let tyinst () = - fun ty -> ty_instanciate tdecl.tyd_params targs ty in + let tyinst = ty_instantiate tdecl.tyd_params targs in match tdecl.tyd_type with - | `Abstract _ -> - List.exists isempty (targs) + | Abstract _ -> + List.exists isempty targs - | `Concrete ty -> - isempty_1 [tyinst () ty] + | Concrete ty -> + isempty_1 [ tyinst ty ] - | `Record (_, fields) -> - isempty_1 (List.map (tyinst () |- snd) fields) + | Record (_, fields) -> + isempty_1 (List.map (tyinst |- snd) fields) - | `Datatype dt -> - isempty_n (List.map (List.map (tyinst ()) |- snd) dt.tydt_ctors) + | Datatype dt -> + (* FIXME: Inspecting all constructors recursively causes + non-termination in some cases. One can have the same + limitation as is done for positivity in order to limit this + unfolding to well-behaved cases. *) + isempty_n (List.map (List.map tyinst |- snd) dt.tydt_ctors) in diff --git a/src/ecHiInductive.mli b/src/ecHiInductive.mli index 32fd11645..1db4bd011 100644 --- a/src/ecHiInductive.mli +++ b/src/ecHiInductive.mli @@ -16,7 +16,7 @@ type dterror = | DTE_TypeError of EcTyping.tyerror | DTE_DuplicatedCtor of symbol | DTE_InvalidCTorType of symbol * EcTyping.tyerror -| DTE_NonPositive +| DTE_NonPositive of symbol * non_positive_context | DTE_Empty type fxerror = diff --git a/src/ecHiTacticals.ml b/src/ecHiTacticals.ml index 9eb6521e3..45af10852 100644 --- a/src/ecHiTacticals.ml +++ b/src/ecHiTacticals.ml @@ -223,9 +223,8 @@ and process1_phl (_ : ttenv) (t : phltactic located) (tc : tcenv1) = | Peager_if -> EcPhlEager.process_if | Peager_while info -> EcPhlEager.process_while info | Peager_fun_def -> EcPhlEager.process_fun_def - | Peager_fun_abs infos -> curry EcPhlEager.process_fun_abs infos + | Peager_fun_abs infos -> EcPhlEager.process_fun_abs infos | Peager_call info -> EcPhlEager.process_call info - | Peager infos -> curry EcPhlEager.process_eager infos | Pbd_equiv (nm, f1, f2) -> EcPhlConseq.process_bd_equiv nm (f1, f2) | Pauto -> EcPhlAuto.t_auto ~conv:`Conv | Plossless -> EcPhlHiAuto.t_lossless diff --git a/src/ecInductive.ml b/src/ecInductive.ml index a873688f4..f21f3003c 100644 --- a/src/ecInductive.ml +++ b/src/ecInductive.ml @@ -83,10 +83,120 @@ let datatype_ind_path (mode : indmode) (p : EcPath.path) = EcPath.pqoname (EcPath.prefix p) name (* -------------------------------------------------------------------- *) -exception NonPositive - -let indsc_of_datatype ?normty (mode : indmode) (dt : datatype) = - let normty = odfl (identity : ty -> ty) normty in +type non_positive_intype = Concrete | Record of symbol | Variant of symbol + +type non_positive_description = + | InType of EcIdent.ident option * non_positive_intype + | NonPositiveOcc of ty + | AbstractTypeRestriction + | TypePositionRestriction of ty + +type non_positive_context = (symbol * non_positive_description) list + +exception NonPositive of non_positive_context + +let with_context ?ident p ctx f = + try f () with NonPositive l -> raise (NonPositive ((EP.basename p, InType (ident, ctx)) :: l)) + +let non_positive (p : EP.path) ctx = raise (NonPositive [(EP.basename p, ctx)]) +let non_positive' (s : EcIdent.ident) ctx = raise (NonPositive [(s.id_symb, ctx)]) + +(** below, [fct] designates the function that takes a path to a type constructor + and returns the corresponding type declaration *) + +(** Strict positivity enforces the following, for every variant of the datatype p: + - for each subterm (a → b), p ∉ fv(a); + - inductive occurences a₁ a₂ .. aₙ p are such that ∀i. p ∉ fv(aᵢ) + + Crucially, this has to be checked whenever p occurs in an instance of + another type constructor. + + FIXME: The current implementation prohibits the use of a type which changes + its type arguments like e.g. + {v + type ('a, 'b) t = [ + | Elt of 'a + | Swap of ('b, 'a) t + ]. + v} + to be used in some places while defining another inductive type. *) + +let rec occurs ?(normty = identity) p t = + match (normty t).ty_node with + | Tconstr (p', _) when EcPath.p_equal p p' -> true + | _ -> EcTypes.ty_sub_exists (occurs p) t + +(** Tests whether the first list is a list of type variables, matching the + identifiers of the second list. *) +let ty_params_compat = + List.for_all2 (fun ty (param_id, _) -> + match ty.ty_node with + | Tvar id -> EcIdent.id_equal id param_id + | _ -> false) + +(** Ensures all occurrences of type variable [ident] are positive in type + declaration [decl] (with name [p]). + This function provide error context in case the check fails. *) +let rec check_positivity_in_decl fct p decl ident = + let check x () = check_positivity_ident fct p decl.tyd_params ident x + and iter l f = List.iter f l in + + match decl.tyd_type with + | Concrete ty -> with_context ~ident p Concrete (check ty) + | Abstract _ -> non_positive p AbstractTypeRestriction + | Datatype { tydt_ctors } -> + iter tydt_ctors @@ fun (name, argty) -> + iter argty @@ fun ty -> + with_context ~ident p (Variant name) (check ty) + | Record (_, tys) -> + iter tys @@ fun (name, ty) -> + with_context ~ident p (Record name) (check ty) + +(** Ensures all occurrences of type variable [ident] are positive in type [ty] *) +and check_positivity_ident fct p params ident ty = + match ty.ty_node with + | Tglob _ | Tunivar _ | Tvar _ -> () + | Ttuple tys -> List.iter (check_positivity_ident fct p params ident) tys + | Tconstr (q, args) when EcPath.p_equal q p -> + if not (ty_params_compat args params) then + non_positive p (TypePositionRestriction ty) + | Tconstr (q, args) -> + let decl = fct q in + List.iter (check_positivity_ident fct p params ident) args; + List.combine args decl.tyd_params + |> List.filter_map (fun (arg, (ident', _)) -> + if EcTypes.var_mem ident arg then Some ident' else None) + |> List.iter (check_positivity_in_decl fct q decl) + | Tfun (from, to_) -> + if EcTypes.var_mem ident from then non_positive' ident (NonPositiveOcc ty); + check_positivity_ident fct p params ident to_ + +(** Ensures all occurrences of path [p] are positive in type [ty] *) +let rec check_positivity_path fct p ty = + match ty.ty_node with + | Tglob _ | Tunivar _ | Tvar _ -> () + | Ttuple tys -> List.iter (check_positivity_path fct p) tys + | Tconstr (q, args) when EcPath.p_equal q p -> + if List.exists (occurs p) args then non_positive p (NonPositiveOcc ty) + | Tconstr (q, args) -> + let decl = fct q in + List.iter (check_positivity_path fct p) args; + List.combine args decl.tyd_params + |> List.filter_map (fun (arg, (ident, _)) -> + if occurs p arg then Some ident else None) + |> List.iter (check_positivity_in_decl fct q decl) + | Tfun (from, to_) -> + if occurs p from then non_positive p (NonPositiveOcc ty); + check_positivity_path fct p to_ + +let check_positivity fct dt = + let check ty () = check_positivity_path fct dt.dt_path ty + and iter l f = List.iter f l in + iter dt.dt_ctors @@ fun (name, argty) -> + iter argty @@ fun ty -> + with_context dt.dt_path (Variant name) (check ty) + +let indsc_of_datatype ?(normty = identity) (mode : indmode) (dt : datatype) = let tpath = dt.dt_path in let rec scheme1 p (pred, fac) ty = @@ -103,13 +213,11 @@ let indsc_of_datatype ?normty (mode : indmode) (dt : datatype) = | scs -> Some (FL.f_let (LTuple xs) fac (FL.f_ands scs)) end - | Tconstr (p', ts) -> - if List.exists (occurs p) ts then raise NonPositive; + | Tconstr (p', _) -> if not (EcPath.p_equal p p') then None else Some (FL.f_app pred [fac] tbool) | Tfun (ty1, ty2) -> - if occurs p ty1 then raise NonPositive; let x = fresh_id_of_ty ty1 in scheme1 p (pred, FL.f_app fac [FL.f_local x ty1] ty2) ty2 |> omap (FL.f_forall [x, GTty ty1]) @@ -152,11 +260,6 @@ let indsc_of_datatype ?normty (mode : indmode) (dt : datatype) = let form = FL.f_forall [predx, GTty predty] form in form - and occurs p t = - match (normty t).ty_node with - | Tconstr (p', _) when EcPath.p_equal p p' -> true - | _ -> EcTypes.ty_sub_exists (occurs p) t - in scheme mode (List.map fst dt.dt_tparams, tpath) dt.dt_ctors (* -------------------------------------------------------------------- *) diff --git a/src/ecInductive.mli b/src/ecInductive.mli index 2b1c5a97c..32d9cd4b0 100644 --- a/src/ecInductive.mli +++ b/src/ecInductive.mli @@ -43,7 +43,25 @@ val datatype_proj_name : symbol -> symbol val datatype_proj_path : path -> symbol -> path (* -------------------------------------------------------------------- *) -exception NonPositive +type non_positive_intype = Concrete | Record of symbol | Variant of symbol + +type non_positive_description = + | InType of EcIdent.ident option * non_positive_intype + | NonPositiveOcc of ty + | AbstractTypeRestriction + | TypePositionRestriction of ty + +type non_positive_context = (symbol * non_positive_description) list + +exception NonPositive of non_positive_context + +val check_positivity : (path -> tydecl) -> datatype -> unit +(** Evaluates whether a given datatype protype satisfies the strict + positivity check. The first argument defines how to retrieve the + effective definition of a type constructor from its path. + + raises the exception [NonPositive] if the check fails, otherwise + the function returns a unit value. *) val indsc_of_datatype : ?normty:(ty -> ty) -> [`Elim|`Case] -> datatype -> form diff --git a/src/ecIo.ml b/src/ecIo.ml index e630d4b49..016545d85 100644 --- a/src/ecIo.ml +++ b/src/ecIo.ml @@ -34,16 +34,20 @@ let isuniop_fun () : unit parser_t = (* -------------------------------------------------------------------- *) type ecreader_r = { (*---*) ecr_lexbuf : Lexing.lexbuf; + (*---*) ecr_source : Buffer.t; mutable ecr_atstart : bool; + mutable ecr_trim : int; mutable ecr_tokens : EcParser.token list; } type ecreader = ecreader_r Disposable.t (* -------------------------------------------------------------------- *) -let ecreader_of_lexbuf (lexbuf : L.lexbuf) : ecreader_r = +let ecreader_of_lexbuf (buffer : Buffer.t) (lexbuf : L.lexbuf) : ecreader_r = { ecr_lexbuf = lexbuf; + ecr_source = buffer; ecr_atstart = true; + ecr_trim = 0; ecr_tokens = []; } (* -------------------------------------------------------------------- *) @@ -51,28 +55,42 @@ let lexbuf (reader : ecreader) = (Disposable.get reader).ecr_lexbuf (* -------------------------------------------------------------------- *) -let from_channel ~(name : string) (channel : in_channel) = - let lexbuf = lexbuf_from_channel name channel in +let from_channel ?(close = false) ~name channel = + let buffer = Buffer.create 0 in + + let refill (bytes : bytes) (len : int) = + let aout = input channel bytes 0 len in + Buffer.add_bytes buffer (Bytes.sub bytes 0 aout); + aout + in + + let lexbuf = Lexing.from_function refill in + + Lexing.set_filename lexbuf name; + Disposable.create - (ecreader_of_lexbuf lexbuf) + ~cb:(fun _ -> if close then close_in channel) + (ecreader_of_lexbuf buffer lexbuf) (* -------------------------------------------------------------------- *) let from_file (filename : string) = let channel = open_in filename in + try - let lexbuf = lexbuf_from_channel filename channel in - Disposable.create - ~cb:(fun _ -> close_in channel) - (ecreader_of_lexbuf lexbuf) + from_channel ~close:true ~name:filename channel with e -> (try close_in channel with _ -> ()); raise e (* -------------------------------------------------------------------- *) -let from_string (data : string) = - Disposable.create - (ecreader_of_lexbuf (Lexing.from_string data)) +let from_string data = + let lexbuf = Lexing.from_string data in + let buffer = Buffer.create (String.length data) in + + Buffer.add_string buffer data; + + Disposable.create (ecreader_of_lexbuf buffer lexbuf) (* -------------------------------------------------------------------- *) let finalize (ecreader : ecreader) = @@ -86,8 +104,20 @@ let lexer ?(checkpoint : _ I.checkpoint option) (ecreader : ecreader_r) = | EcParser.FINAL _ -> true | _ -> false in - if List.is_empty (ecreader.ecr_tokens) then - ecreader.ecr_tokens <- EcLexer.main lexbuf; + if ecreader.ecr_atstart then + ecreader.ecr_trim <- ecreader.ecr_lexbuf.Lexing.lex_curr_p.pos_cnum; + + while List.is_empty (ecreader.ecr_tokens) do + match EcLexer.main lexbuf with + | [COMMENT] -> + if ecreader.ecr_atstart then + ecreader.ecr_trim <- (Lexing.lexeme_end_p ecreader.ecr_lexbuf).pos_cnum + | [DOCCOMMENT _] as tokens -> + if ecreader.ecr_atstart then + ecreader.ecr_tokens <- tokens + | tokens -> + ecreader.ecr_tokens <- tokens + done; let token, queue = List.destruct ecreader.ecr_tokens in @@ -103,7 +133,16 @@ let lexer ?(checkpoint : _ I.checkpoint option) (ecreader : ecreader_r) = in ecreader.ecr_tokens <- prequeue @ queue; - ecreader.ecr_atstart <- (isfinal token); + + if isfinal token then + ecreader.ecr_atstart <- true + else + ecreader.ecr_atstart <- ecreader.ecr_atstart && ( + match token with + | P.DOCCOMMENT _ | P.COMMENT -> true + | _ -> false + ); + (token, Lexing.lexeme_start_p lexbuf, Lexing.lexeme_end_p lexbuf) (* -------------------------------------------------------------------- *) @@ -119,7 +158,7 @@ let drain (ecreader : ecreader) = drain () (* -------------------------------------------------------------------- *) -let parse (ecreader : ecreader) = +let parse (ecreader : ecreader) : EcParsetree.prog = let ecreader = Disposable.get ecreader in let rec parse (checkpoint : EcParsetree.prog I.checkpoint) : EcParsetree.prog = @@ -138,6 +177,17 @@ let parse (ecreader : ecreader) = in parse (EcParser.Incremental.prog ecreader.ecr_lexbuf.lex_curr_p) +(* -------------------------------------------------------------------- *) +let xparse (ecreader : ecreader) : string * EcParsetree.prog = + let ecr = Disposable.get ecreader in + + let p1 = ecr.ecr_lexbuf.Lexing.lex_curr_p.pos_cnum in + let cd = parse ecreader in + let p2 = ecr.ecr_lexbuf.Lexing.lex_curr_p.pos_cnum in + let p1 = max p1 ecr.ecr_trim in + + (Buffer.sub ecr.ecr_source p1 (p2 - p1), cd) + (* -------------------------------------------------------------------- *) let parseall (ecreader : ecreader) = let rec aux acc = @@ -145,6 +195,8 @@ let parseall (ecreader : ecreader) = | EcParsetree.P_Prog (commands, terminate) -> let acc = List.rev_append commands acc in if terminate then List.rev acc else aux acc + | EcParsetree.P_DocComment _ -> + aux acc | EcParsetree.P_Undo _ | EcParsetree.P_Exit -> assert false (* FIXME *) in diff --git a/src/ecIo.mli b/src/ecIo.mli index ce52869b4..42d28ba74 100644 --- a/src/ecIo.mli +++ b/src/ecIo.mli @@ -2,12 +2,13 @@ type ecreader (* -------------------------------------------------------------------- *) -val from_channel : name:string -> in_channel -> ecreader +val from_channel : ?close:bool -> name:string -> in_channel -> ecreader val from_file : string -> ecreader val from_string : string -> ecreader (* -------------------------------------------------------------------- *) val finalize : ecreader -> unit +val xparse : ecreader -> string * EcParsetree.prog val parse : ecreader -> EcParsetree.prog val parseall : ecreader -> EcParsetree.global list val drain : ecreader -> unit diff --git a/src/ecLexer.mll b/src/ecLexer.mll index a1b90c7c8..19536eaae 100644 --- a/src/ecLexer.mll +++ b/src/ecLexer.mll @@ -383,7 +383,14 @@ rule main = parse with Not_found -> [PUNIOP name] } - | "(*" { comment lexbuf; main lexbuf } + | "(*" (['&' '^'] as c) { + let buffer = doccomment c (Buffer.create 0) lexbuf in + let kind = match c with '&' -> `Item | '^' -> `Global | _ -> assert false in + [DOCCOMMENT (kind, Buffer.contents buffer)] + } + + | "(*" { comment lexbuf; [COMMENT] } + | "\"" { [STRING (Buffer.contents (string (Buffer.create 0) lexbuf))] } (* string symbols *) @@ -460,6 +467,20 @@ and comment = parse | eof { unterminated_comment () } | _ { comment lexbuf } +and doccomment kind buf = parse + | ['&' '^']? "*)" { buf } + | "(*" { comment lexbuf; doccomment kind buf lexbuf } + | eof { unterminated_comment () } + | newline { + Lexing.new_line lexbuf; + Buffer.add_char buf '\n'; + doccomment kind buf lexbuf + } + | _ as c { + Buffer.add_char buf c; + doccomment kind buf lexbuf + } + and string buf = parse | "\"" { buf } | "\\n" { Buffer.add_char buf '\n'; string buf lexbuf } diff --git a/src/ecLowGoal.ml b/src/ecLowGoal.ml index fd20b27fd..14e2e72f6 100644 --- a/src/ecLowGoal.ml +++ b/src/ecLowGoal.ml @@ -168,7 +168,7 @@ module LowApply = struct | PTGlobal (p, tys) -> (* FIXME: poor API ==> poor error recovery *) let env = LDecl.toenv (hyps_of_ckenv tc) in - (pt, EcEnv.Ax.instanciate p tys env, subgoals) + (pt, EcEnv.Ax.instantiate p tys env, subgoals) | PTTerm pt -> let pt, ax, subgoals = check_ `Elim pt subgoals tc in @@ -406,7 +406,8 @@ let t_hred_with_info ?target (ri : reduction_info) (tc : tcenv1) = FApi.tcenv_of_tcenv1 (t_change_r ~fail:true ?target action tc) (* -------------------------------------------------------------------- *) -let rec t_lazy_match ?(reduce = `Full) (tx : form -> FApi.backward) +let rec t_lazy_match ?(reduce = `Full) ?(texn = fun _ -> raise InvalidGoalShape) + (tx : form -> FApi.backward) (tc : tcenv1) = let concl = FApi.tc1_goal tc in try tx concl tc @@ -416,7 +417,7 @@ let rec t_lazy_match ?(reduce = `Full) (tx : form -> FApi.backward) | `None -> raise InvalidGoalShape | `Full -> EcReduction.full_red | `NoDelta -> EcReduction.nodelta in - FApi.t_seq (t_hred_with_info strategy) (t_lazy_match ~reduce tx) tc + FApi.t_seq (FApi.t_or (t_hred_with_info strategy) texn) (t_lazy_match ~reduce tx) tc (* -------------------------------------------------------------------- *) type smode = [ `Cbv | `Cbn ] @@ -2598,8 +2599,8 @@ let t_solve ?(canfail = true) ?(bases = [EcEnv.Auto.dname]) ?(mode = fmdelta) ?( let pt = PT.pt_of_uglobal !!tc (FApi.tc1_hyps tc) p in try Apply.t_apply_bwd_r ~ri ~mode ~canview:false pt tc - with Apply.NoInstance _ -> - t_fail tc + with Apply.NoInstance _ -> + t_fail tc in let rec t_apply ctn ip tc = diff --git a/src/ecLowGoal.mli b/src/ecLowGoal.mli index 56004018a..ef5dca098 100644 --- a/src/ecLowGoal.mli +++ b/src/ecLowGoal.mli @@ -69,7 +69,7 @@ val t_change : ?ri:EcReduction.reduction_info -> ?target:ident -> form -> FApi. (* -------------------------------------------------------------------- *) val t_lazy_match: - ?reduce:lazyred -> (form -> FApi.backward)-> FApi.backward + ?reduce:lazyred -> ?texn:EcCoreGoal.FApi.backward -> (form -> FApi.backward)-> FApi.backward (* -------------------------------------------------------------------- *) val t_reflex : ?mode:[`Alpha | `Conv] -> ?reduce:lazyred -> FApi.backward @@ -362,4 +362,4 @@ val pp_tc :tcenv -> unit [@@ocaml.alert debug "Debug function, remove uses before merging"] val pp_tc1 :tcenv1 -> unit - [@@ocaml.alert debug "Debug function, remove uses before merging"] \ No newline at end of file + [@@ocaml.alert debug "Debug function, remove uses before merging"] diff --git a/src/ecLowPhlGoal.ml b/src/ecLowPhlGoal.ml index 4bec3d0ca..97fe5f0b4 100644 --- a/src/ecLowPhlGoal.ml +++ b/src/ecLowPhlGoal.ml @@ -206,6 +206,27 @@ let tc1_get_stmt side tc = | _ -> tc_error_noXhl ~kinds:(hlkinds_Xhl_r `Stmt) !!tc +(* ------------------------------------------------------------------ *) +let tc1_process_codepos_range tc (side, cpr) = + let me, _ = tc1_get_stmt side tc in + let env = FApi.tc1_env tc in + let env = EcEnv.Memory.push_active_ss me env in + EcTyping.trans_codepos_range env cpr + +(* ------------------------------------------------------------------ *) +let tc1_process_codepos tc (side, cpos) = + let me, _ = tc1_get_stmt side tc in + let env = FApi.tc1_env tc in + let env = EcEnv.Memory.push_active_ss me env in + EcTyping.trans_codepos env cpos + +(* ------------------------------------------------------------------ *) +let tc1_process_codepos1 tc (side, cpos) = + let me, _ = tc1_get_stmt side tc in + let env = FApi.tc1_env tc in + let env = EcEnv.Memory.push_active_ss me env in + EcTyping.trans_codepos1 env cpos + (* -------------------------------------------------------------------- *) let hl_set_stmt (side : side option) (f : form) (s : stmt) = match side, f.f_node with @@ -256,28 +277,28 @@ let tc1_get_post tc = (* -------------------------------------------------------------------- *) let set_pre ~pre f = match f.f_node, pre with - | FhoareF hf, Inv_ss pre -> + | FhoareF hf, Inv_ss pre -> let pre = ss_inv_rebind pre hf.hf_m in f_hoareF pre hf.hf_f (hf_po hf) - | FhoareS hs, Inv_ss pre -> + | FhoareS hs, Inv_ss pre -> let pre = ss_inv_rebind pre (fst hs.hs_m) in f_hoareS (snd hs.hs_m) pre hs.hs_s (hs_po hs) - | FeHoareF hf, Inv_ss pre -> + | FeHoareF hf, Inv_ss pre -> let pre = ss_inv_rebind pre hf.ehf_m in f_eHoareF pre hf.ehf_f (ehf_po hf) - | FeHoareS hs, Inv_ss pre -> + | FeHoareS hs, Inv_ss pre -> let pre = ss_inv_rebind pre (fst hs.ehs_m) in f_eHoareS (snd hs.ehs_m) pre hs.ehs_s (ehs_po hs) | FbdHoareF hf, Inv_ss pre -> let pre = ss_inv_rebind pre hf.bhf_m in f_bdHoareF pre hf.bhf_f (bhf_po hf) hf.bhf_cmp (bhf_bd hf) - | FbdHoareS hs, Inv_ss pre -> + | FbdHoareS hs, Inv_ss pre -> let pre = ss_inv_rebind pre (fst hs.bhs_m) in f_bdHoareS (snd hs.bhs_m) pre hs.bhs_s (bhs_po hs) hs.bhs_cmp (bhs_bd hs) - | FequivF ef, Inv_ts pre -> + | FequivF ef, Inv_ts pre -> let pre = ts_inv_rebind pre ef.ef_ml ef.ef_mr in f_equivF pre ef.ef_fl ef.ef_fr (ef_po ef) - | FequivS es, Inv_ts pre -> + | FequivS es, Inv_ts pre -> let pre = ts_inv_rebind pre (fst es.es_ml) (fst es.es_mr) in f_equivS (snd es.es_ml) (snd es.es_mr) pre es.es_sl es.es_sr (es_po es) | _ -> assert false @@ -307,33 +328,33 @@ let t_hS_or_bhS_or_eS ?th ?teh ?tbh ?te tc = | FeHoareS _ when EcUtils.is_some teh -> (oget teh) tc | FbdHoareS _ when EcUtils.is_some tbh -> (oget tbh) tc | FequivS _ when EcUtils.is_some te -> (oget te ) tc - | _ -> let kinds = List.flatten [ - if EcUtils.is_some th then [`Hoare `Stmt] else []; - if EcUtils.is_some teh then [`EHoare `Stmt] else []; - if EcUtils.is_some tbh then [`PHoare `Stmt] else []; - if EcUtils.is_some te then [`Equiv `Stmt] else []] - + if EcUtils.is_some th then [`Hoare `Stmt] else []; + if EcUtils.is_some teh then [`EHoare `Stmt] else []; + if EcUtils.is_some tbh then [`PHoare `Stmt] else []; + if EcUtils.is_some te then [`Equiv `Stmt] else []] in tc_error_noXhl ~kinds !!tc let t_hF_or_bhF_or_eF ?th ?teh ?tbh ?te ?teg tc = - match (FApi.tc1_goal tc).f_node with - | FhoareF _ when EcUtils.is_some th -> (oget th ) tc - | FeHoareF _ when EcUtils.is_some teh -> (oget teh) tc - | FbdHoareF _ when EcUtils.is_some tbh -> (oget tbh) tc - | FequivF _ when EcUtils.is_some te -> (oget te ) tc - | FeagerF _ when EcUtils.is_some teg -> (oget teg) tc - - | _ -> + let texn tc = let kinds = List.flatten [ if EcUtils.is_some th then [`Hoare `Pred] else []; if EcUtils.is_some teh then [`EHoare `Pred] else []; if EcUtils.is_some tbh then [`PHoare `Pred] else []; if EcUtils.is_some te then [`Equiv `Pred] else []; if EcUtils.is_some teg then [`Eager ] else []] + in tc_error_noXhl ~kinds !!tc in + let tx f tc = + match f.f_node with + | FhoareF _ when EcUtils.is_some th -> (oget th ) tc + | FeHoareF _ when EcUtils.is_some teh -> (oget teh) tc + | FbdHoareF _ when EcUtils.is_some tbh -> (oget tbh) tc + | FequivF _ when EcUtils.is_some te -> (oget te ) tc + | FeagerF _ when EcUtils.is_some teg -> (oget teg) tc + | _ -> raise EcProofTyping.NoMatch in + EcLowGoal.t_lazy_match ~texn tx tc - in tc_error_noXhl ~kinds !!tc (* -------------------------------------------------------------------- *) let tag_sym_with_side ?mc name m = @@ -672,7 +693,7 @@ let t_code_transform (side : oside) ?(bdhoare = false) cpos tr tx tc = let pr, po = bhs_pr bhs, bhs_po bhs in let (me, stmt, cs) = tx (pf, hyps) cpos (pr.inv, po.inv) (bhs.bhs_m, bhs.bhs_s) in - let concl = f_bdHoareS (snd me) (bhs_pr bhs) stmt (bhs_po bhs) + let concl = f_bdHoareS (snd me) (bhs_pr bhs) stmt (bhs_po bhs) bhs.bhs_cmp (bhs_bd bhs) in FApi.xmutate1 tc (tr None) (cs @ [concl]) diff --git a/src/ecMatching.ml b/src/ecMatching.ml index 7c8e6a2fb..b84d8d430 100644 --- a/src/ecMatching.ml +++ b/src/ecMatching.ml @@ -837,19 +837,20 @@ let f_match_core opts hyps (ue, ev) f1 f2 = cb (odfl reduced (EcReduction.h_red_opt EcReduction.beta_red hyps reduced)) and doit_mem _env mxs m1 m2 = - match EV.get m1 !ev.evm_mem with - | None -> - if not (EcMemory.mem_equal m1 m2) then + if not (EcMemory.mem_equal m1 m2) then begin + match EV.get m1 !ev.evm_mem with + | None -> raise MatchFailure - | Some `Unset -> - if Mid.mem m2 mxs then - raise MatchFailure; - ev := { !ev with evm_mem = EV.set m1 m2 !ev.evm_mem } + | Some `Unset -> + if Mid.mem m2 mxs then + raise MatchFailure; + ev := { !ev with evm_mem = EV.set m1 m2 !ev.evm_mem } - | Some (`Set m1) -> - if not (EcMemory.mem_equal m1 m2) then - raise MatchFailure + | Some (`Set m1) -> + if not (EcMemory.mem_equal m1 m2) then + raise MatchFailure + end and doit_bindings env (subst, mxs) q1 q2 = let doit_binding (env, subst, mxs) (x1, gty1) (x2, gty2) = @@ -914,7 +915,7 @@ let f_match opts hyps (ue, ev) f1 f2 = raise MatchFailure; let clue = try EcUnify.UniEnv.close ue - with EcUnify.UninstanciateUni -> raise MatchFailure + with EcUnify.UninstantiateUni -> raise MatchFailure in (ue, clue, ev) diff --git a/src/ecOptions.ml b/src/ecOptions.ml index b3704e1a1..f012e8e8d 100644 --- a/src/ecOptions.ml +++ b/src/ecOptions.ml @@ -9,6 +9,7 @@ type command = [ | `Config | `Runtest of run_option | `Why3Config + | `DocGen of doc_option ] and options = { @@ -24,6 +25,7 @@ and cmp_option = { cmpo_tstats : string option; cmpo_noeco : bool; cmpo_script : bool; + cmpo_trace : bool; } and cli_option = { @@ -40,6 +42,11 @@ and run_option = { runo_rawargs : string list; } +and doc_option = { + doco_input : string; + doco_outdirp : string option; +} + and prv_options = { prvo_maxjobs : int option; prvo_timeout : int option; @@ -341,6 +348,7 @@ let specs = { `Spec ("tstats" , `String, "Save timing statistics to "); `Spec ("script" , `Flag , "Computer-friendly output"); `Spec ("no-eco" , `Flag , "Do not cache verification results"); + `Spec ("trace" , `Flag , "Save all goals & messages in .eco"); `Spec ("compact", `Int , "")]); ("cli", "Run EasyCrypt top-level", [ @@ -359,6 +367,10 @@ let specs = { ]); ("why3config", "Configure why3", []); + + ("docgen", "Generate documentation", [ + `Spec ("outdir", `String, "Output documentation files in ") + ]); ]; xp_groups = [ @@ -506,7 +518,8 @@ let cmp_options_of_values ini values input = cmpo_compact = get_int "compact" values; cmpo_tstats = get_string "tstats" values; cmpo_noeco = get_flag "no-eco" values; - cmpo_script = get_flag "script" values; } + cmpo_script = get_flag "script" values; + cmpo_trace = get_flag "trace" values; } let runtest_options_of_values ini values (input, scenarios) = { runo_input = input; @@ -516,6 +529,10 @@ let runtest_options_of_values ini values (input, scenarios) = runo_jobs = get_int "jobs" values; runo_rawargs = get_strings "raw-args" values; } +let doc_options_of_values values input = + { doco_input = input; + doco_outdirp = get_string "outdir" values; } + (* -------------------------------------------------------------------- *) let parse getini argv = let (command, values, anons) = parse specs argv in @@ -575,6 +592,18 @@ let parse getini argv = (cmd, ini, true) + | "docgen" -> + begin + match anons with + | [input] -> + let ini = getini None in + let cmd = `DocGen (doc_options_of_values values input) in + (cmd, ini, true) + + | _ -> + raise (Arg.Bad "this command takes a single input file as argument") + end + | _ -> assert false in { diff --git a/src/ecOptions.mli b/src/ecOptions.mli index b779aa44e..59009718a 100644 --- a/src/ecOptions.mli +++ b/src/ecOptions.mli @@ -5,6 +5,7 @@ type command = [ | `Config | `Runtest of run_option | `Why3Config + | `DocGen of doc_option ] and options = { @@ -20,6 +21,7 @@ and cmp_option = { cmpo_tstats : string option; cmpo_noeco : bool; cmpo_script : bool; + cmpo_trace : bool; } and cli_option = { @@ -36,6 +38,11 @@ and run_option = { runo_rawargs : string list; } +and doc_option = { + doco_input : string; + doco_outdirp : string option; +} + and prv_options = { prvo_maxjobs : int option; prvo_timeout : int option; diff --git a/src/ecParser.mly b/src/ecParser.mly index eb1a1a980..a1c9acf08 100644 --- a/src/ecParser.mly +++ b/src/ecParser.mly @@ -412,6 +412,7 @@ %token COLON %token COLONTILD %token COMMA +%token COMMENT %token CONGR %token CONSEQ %token CONST @@ -606,7 +607,8 @@ %token ZETA %token NOP LOP1 ROP1 LOP2 ROP2 LOP3 ROP3 LOP4 ROP4 NUMOP %token LTCOLON DASHLT GT LT GE LE LTSTARGT LTLTSTARGT LTSTARGTGT -%token < Lexing.position> FINAL +%token FINAL +%token DOCCOMMENT %nonassoc prec_below_comma %nonassoc COMMA ELSE @@ -2845,35 +2847,25 @@ logtactic: | WLOG b=boption(SUFF) COLON ids=loc(ipcore_name)* SLASH f=form { Pwlog (ids, b, f) } -eager_info: -| h=ident - { LE_done h } - -| LPAREN h=ident COLON s1=stmt TILD s2=stmt COLON pr=form LONGARROW po=form RPAREN - { LE_todo (h, s1, s2, pr, po) } - eager_tac: -| SEQ n1=codepos1 n2=codepos1 i=eager_info COLON p=sform - { Peager_seq (i, (n1, n2), p) } +| SEQ n1=codepos1 n2=codepos1 COLON s=stmt COLON p=form_or_double_form + { Peager_seq ((n1, n2), s, p) } | IF { Peager_if } -| WHILE i=eager_info +| WHILE i=sform { Peager_while i } | PROC { Peager_fun_def } -| PROC i=eager_info f=sform - { Peager_fun_abs (i, f) } +| PROC f=sform + { Peager_fun_abs f } | CALL info=gpterm(call_info) { Peager_call info } -| info=eager_info COLON p=sform - { Peager (info, p) } - form_or_double_form: | f=sform { Single f } @@ -3904,6 +3896,9 @@ prog_r: | EXIT FINAL { P_Exit } +| d=DOCCOMMENT + { P_DocComment d } + | error { parse_error (EcLocation.make $startpos $endpos) None } diff --git a/src/ecParsetree.ml b/src/ecParsetree.ml index b438cc904..e9991a3ea 100644 --- a/src/ecParsetree.ml +++ b/src/ecParsetree.ml @@ -382,6 +382,12 @@ let rec pf_ident ?(raw = false) f = | PFtuple [f] when not raw -> pf_ident ~raw f | _ -> None + let rec pcmhd_ident (pcmhd : pmodule_header) : psymbol = + match pcmhd with + | Pmh_ident nm -> nm + | Pmh_params x -> pcmhd_ident (fst (unloc x)) + | Pmh_cast (pmh, _) -> pcmhd_ident pmh + (* -------------------------------------------------------------------- *) type psubtype = { pst_name : psymbol; @@ -596,11 +602,6 @@ type trans_formula = type trans_info = trans_kind * trans_formula -(* -------------------------------------------------------------------- *) -type eager_info = - | LE_done of psymbol - | LE_todo of psymbol * pstmt * pstmt * pformula * pformula - (* -------------------------------------------------------------------- *) type bdh_split = | BDH_split_bop of pformula * pformula * pformula option @@ -776,13 +777,12 @@ type phltactic = (* Eager *) - | Peager_seq of (eager_info * pcodepos1 pair * pformula) + | Peager_seq of (pcodepos1 pair * pstmt * pformula doption) | Peager_if - | Peager_while of (eager_info) + | Peager_while of pformula | Peager_fun_def - | Peager_fun_abs of (eager_info * pformula) - | Peager_call of (call_info gppterm) - | Peager of (eager_info * pformula) + | Peager_fun_abs of pformula + | Peager_call of call_info gppterm (* Relation between logic *) | Pbd_equiv of (side * pformula * pformula) @@ -1318,5 +1318,8 @@ type prog_r = | P_Prog of global list * bool | P_Exit | P_Undo of int + | P_DocComment of (dockind * string) + +and dockind = [`Global | `Item] type prog = prog_r located diff --git a/src/ecPrinting.ml b/src/ecPrinting.ml index b37fbe228..854a61a59 100644 --- a/src/ecPrinting.ml +++ b/src/ecPrinting.ml @@ -620,7 +620,7 @@ let pp_modtype1 (ppe : PPEnv.t) fmt mty = (* -------------------------------------------------------------------- *) let pp_local (ppe : PPEnv.t) fmt x = - Format.fprintf fmt "%s" (EcIdent.name x) + Format.fprintf fmt "%s" (PPEnv.local_symb ppe x) (* -------------------------------------------------------------------- *) let pp_local ?fv (ppe : PPEnv.t) fmt x = @@ -1281,7 +1281,7 @@ let pp_opapp let recp = EcDecl.operator_as_rcrd op in match EcEnv.Ty.by_path_opt recp env with - | Some { tyd_type = `Record (_, fields) } + | Some { tyd_type = Record (_, fields) } when List.length fields = List.length es -> begin let wmap = @@ -2280,12 +2280,12 @@ let pp_typedecl (ppe : PPEnv.t) fmt (x, tyd) = and pp_body fmt = match tyd.tyd_type with - | `Abstract _ -> () (* FIXME: TC HOOK *) + | Abstract _ -> () (* FIXME: TC HOOK *) - | `Concrete ty -> + | Concrete ty -> Format.fprintf fmt " =@ %a" (pp_type ppe) ty - | `Datatype { tydt_ctors = cs } -> + | Datatype { tydt_ctors = cs } -> let pp_ctor fmt (c, cty) = match cty with | [] -> @@ -2296,7 +2296,7 @@ let pp_typedecl (ppe : PPEnv.t) fmt (x, tyd) = in Format.fprintf fmt " =@ [@[%a@]]" (pp_list " |@ " pp_ctor) cs - | `Record (_, fields) -> + | Record (_, fields) -> let pp_field fmt (f, fty) = Format.fprintf fmt "%s: @[%a@]" f (pp_type ppe) fty in @@ -3314,6 +3314,10 @@ let pp_goal (ppe : PPEnv.t) (prpo : prpo_display) fmt (g, extra) = (PPGoal.pp_goal1 ~pphyps:false ~prpo ~idx:(i+2) ppe) g) gs +(* -------------------------------------------------------------------- *) +let pp_goal1 (ppe : PPEnv.t) (fmt : Format.formatter) (g : EcBaseLogic.hyps * form) = + PPGoal.pp_goal1 ppe fmt g + (* -------------------------------------------------------------------- *) let pp_ovdecl ppe fmt ov = Format.fprintf fmt "%s : %a" (odfl "_" ov.ov_name) (pp_type ppe) ov.ov_type diff --git a/src/ecProofTerm.ml b/src/ecProofTerm.ml index 7f3c3d0e1..0f1a19d11 100644 --- a/src/ecProofTerm.ml +++ b/src/ecProofTerm.ml @@ -193,7 +193,7 @@ let pt_of_hyp_r ptenv x = (* -------------------------------------------------------------------- *) let pt_of_global pf hyps p tys = let ptenv = ptenv_of_penv hyps pf in - let ax = EcEnv.Ax.instanciate p tys (LDecl.toenv hyps) in + let ax = EcEnv.Ax.instantiate p tys (LDecl.toenv hyps) in { ptev_env = ptenv; ptev_pt = ptglobal ~tys p; @@ -202,7 +202,7 @@ let pt_of_global pf hyps p tys = (* -------------------------------------------------------------------- *) let pt_of_global_r ptenv p tys = let env = LDecl.toenv ptenv.pte_hy in - let ax = EcEnv.Ax.instanciate p tys env in + let ax = EcEnv.Ax.instantiate p tys env in { ptev_env = ptenv; ptev_pt = ptglobal ~tys p; diff --git a/src/ecProofTyping.ml b/src/ecProofTyping.ml index 73164e474..2674da433 100644 --- a/src/ecProofTyping.ml +++ b/src/ecProofTyping.ml @@ -27,7 +27,7 @@ let process_form_opt ?mv hyps pf oty = let ts = Tuni.subst (EcUnify.UniEnv.close ue) in EcFol.Fsubst.f_subst ts ff - with EcUnify.UninstanciateUni -> + with EcUnify.UninstantiateUni -> EcTyping.tyerror pf.EcLocation.pl_loc (LDecl.toenv hyps) EcTyping.FreeTypeVariables @@ -132,7 +132,7 @@ let tc1_process_stmt ?map hyps tc c = let tc1_process_prhl_stmt ?map tc side c = let concl = FApi.tc1_goal tc in - let ml, mr = match concl.f_node with + let ml, mr = match concl.f_node with | FequivS {es_ml=ml; es_mr=mr} -> (ml, mr) | FeagerF {eg_ml=ml; eg_mr=mr} -> EcMemory.abstract ml, EcMemory.abstract mr @@ -188,27 +188,6 @@ let tc1_process_Xhl_formula ?side tc pf = let tc1_process_Xhl_formula_xreal tc pf = tc1_process_Xhl_form tc txreal pf -(* ------------------------------------------------------------------ *) -let tc1_process_codepos_range tc (side, cpr) = - let me, _ = EcLowPhlGoal.tc1_get_stmt side tc in - let env = FApi.tc1_env tc in - let env = EcEnv.Memory.push_active_ss me env in - EcTyping.trans_codepos_range env cpr - -(* ------------------------------------------------------------------ *) -let tc1_process_codepos tc (side, cpos) = - let me, _ = EcLowPhlGoal.tc1_get_stmt side tc in - let env = FApi.tc1_env tc in - let env = EcEnv.Memory.push_active_ss me env in - EcTyping.trans_codepos env cpos - -(* ------------------------------------------------------------------ *) -let tc1_process_codepos1 tc (side, cpos) = - let me, _ = EcLowPhlGoal.tc1_get_stmt side tc in - let env = FApi.tc1_env tc in - let env = EcEnv.Memory.push_active_ss me env in - EcTyping.trans_codepos1 env cpos - (* ------------------------------------------------------------------ *) (* FIXME: factor out to typing module *) (* FIXME: TC HOOK - check parameter constraints *) diff --git a/src/ecProofTyping.mli b/src/ecProofTyping.mli index b450ac189..359b57045 100644 --- a/src/ecProofTyping.mli +++ b/src/ecProofTyping.mli @@ -4,7 +4,6 @@ open EcIdent open EcDecl open EcEnv open EcCoreGoal -open EcMatching.Position open EcAst (* -------------------------------------------------------------------- *) @@ -59,10 +58,6 @@ val tc1_process_prhl_stmt : val tc1_process_Xhl_stmt : ?map:EcTyping.ismap -> tcenv1 -> pstmt -> stmt -val tc1_process_codepos_range : tcenv1 -> oside * pcodepos_range -> codepos_range -val tc1_process_codepos : tcenv1 -> oside * pcodepos -> codepos -val tc1_process_codepos1 : tcenv1 -> oside * pcodepos1 -> codepos1 - (* -------------------------------------------------------------------- *) exception NoMatch diff --git a/src/ecReduction.ml b/src/ecReduction.ml index 1dce11fab..976c52312 100644 --- a/src/ecReduction.ml +++ b/src/ecReduction.ml @@ -1258,23 +1258,23 @@ let rec simplify ri env f = match f.f_node with | FhoareF hf when ri.ri.modpath -> let hf_f = EcEnv.NormMp.norm_xfun env hf.hf_f in - f_map (fun ty -> ty) (simplify ri env) + f_map (fun ty -> ty) (simplify ri env) (f_hoareF (hf_pr hf) hf_f (hf_po hf)) | FeHoareF hf when ri.ri.modpath -> let ehf_f = EcEnv.NormMp.norm_xfun env hf.ehf_f in - f_map (fun ty -> ty) (simplify ri env) + f_map (fun ty -> ty) (simplify ri env) (f_eHoareF (ehf_pr hf) ehf_f (ehf_po hf)) | FbdHoareF hf when ri.ri.modpath -> let bhf_f = EcEnv.NormMp.norm_xfun env hf.bhf_f in - f_map (fun ty -> ty) (simplify ri env) + f_map (fun ty -> ty) (simplify ri env) (f_bdHoareF (bhf_pr hf) bhf_f (bhf_po hf) hf.bhf_cmp (bhf_bd hf)) | FequivF ef when ri.ri.modpath -> let ef_fl = EcEnv.NormMp.norm_xfun env ef.ef_fl in let ef_fr = EcEnv.NormMp.norm_xfun env ef.ef_fr in - f_map (fun ty -> ty) (simplify ri env) + f_map (fun ty -> ty) (simplify ri env) (f_equivF (ef_pr ef) ef_fl ef_fr (ef_po ef)) | FeagerF eg when ri.ri.modpath -> @@ -1666,6 +1666,12 @@ let h_red_opt ri hyps f = try Some (h_red ri hyps f) with NotReducible -> None +let rec h_red_until ?(until = fun _ -> false) ri hyps f = + if until f then f + else match h_red ri hyps f with + | f -> h_red_until ~until ri hyps f + | exception NotReducible -> f + (* -------------------------------------------------------------------- *) type xconv = [`Eq | `AlphaEq | `Conv] diff --git a/src/ecReduction.mli b/src/ecReduction.mli index 116cb8015..27dea22f8 100644 --- a/src/ecReduction.mli +++ b/src/ecReduction.mli @@ -88,6 +88,14 @@ val reduce_logic : reduction_info -> env -> LDecl.hyps -> form -> form val h_red_opt : reduction_info -> LDecl.hyps -> form -> form option val h_red : reduction_info -> LDecl.hyps -> form -> form +(* [hred_until ~until ri hyps f] performs head reduction on [f] + until [test f] is true or that no more head reduction is possible. + If no [until] argument is provided then head reduction is performed + until it is possible. +*) +val h_red_until : + ?until:(form -> bool) -> reduction_info -> LDecl.hyps -> form -> form + val reduce_user_gen : (EcFol.form -> EcFol.form) -> reduction_info -> @@ -109,4 +117,4 @@ type xconv = [`Eq | `AlphaEq | `Conv] val xconv : xconv -> LDecl.hyps -> form -> form -> bool val ss_inv_alpha_eq : LDecl.hyps -> ss_inv -> ss_inv -> bool -val ts_inv_alpha_eq : LDecl.hyps -> ts_inv -> ts_inv -> bool \ No newline at end of file +val ts_inv_alpha_eq : LDecl.hyps -> ts_inv -> ts_inv -> bool diff --git a/src/ecRelocate.ml b/src/ecRelocate.ml index f07cb429a..0505a9b36 100644 --- a/src/ecRelocate.ml +++ b/src/ecRelocate.ml @@ -23,6 +23,7 @@ let local (name : string list) : string = module type Sites = sig val commands : string val theories : string list + val doc : string val config : string end @@ -30,6 +31,7 @@ end module LocalSites() : Sites = struct let commands = local ["scripts"; "testing"] let theories = [local ["theories"]] + let doc = local ["assets"; "styles"] let config = local ["etc"] end @@ -42,7 +44,11 @@ module DuneSites() : Sites = struct let theories = EcDuneSites.Sites.theories - let config = + let doc = + Option.value ~default:"." + (EcUtils.List.Exceptionless.hd EcDuneSites.Sites.doc) + + let config = Option.value ~default:"etc" (EcUtils.List.Exceptionless.hd EcDuneSites.Sites.config) end diff --git a/src/ecRelocate.mli b/src/ecRelocate.mli index 8600315c0..59e80d735 100644 --- a/src/ecRelocate.mli +++ b/src/ecRelocate.mli @@ -5,6 +5,7 @@ val sourceroot : string option module type Sites = sig val commands : string val theories : string list + val doc : string val config : string end diff --git a/src/ecScope.ml b/src/ecScope.ml index bce77e0ab..1ab346c66 100644 --- a/src/ecScope.ml +++ b/src/ecScope.ml @@ -337,8 +337,105 @@ type scope = { sc_clears : path list; sc_pr_uc : proof_uc option; sc_options : GenOptions.options; + sc_globdoc : string list; + sc_locdoc : docstate; } +and docstate = { + docentities : docentity list; + subdocentbl : docentity list; + docstringbl : string list; + srcstringbl : string list; + currentname : string option; + currentkind : itemkind option; + currentmode : mode option; + currentproc : bool; +} + +and docentity = + | ItemDoc of string list * docitem + | SubDoc of (string list * docitem) * docentity list + +and docitem = + mode * itemkind * string * string list (* dec/reg, kind, name, src *) + +and itemkind = [`Type | `Operator | `Axiom | `Lemma | `ModuleType | `Module | `Theory] + +and mode = [`Abstract | `Specific] + +(* -------------------------------------------------------------------- *) +let get_gdocstrings (sc : scope) : string list = + sc.sc_globdoc + +let get_ldocentities (sc : scope) : docentity list = + sc.sc_locdoc.docentities + +module DocState = struct + let empty : docstate = + { docentities = []; + subdocentbl = []; + docstringbl = []; + srcstringbl = []; + currentname = None; + currentkind = None; + currentmode = None; + currentproc = false; } + + let start_process (state : docstate) (name : string) (kind : itemkind) (md : mode): docstate = + { state with + currentname = Some name; + currentkind = Some kind; + currentmode = Some md; + currentproc = true } + + let prevent_process (state : docstate) : docstate = + { state with + currentname = None; + currentkind = None; + currentmode = None; + currentproc = false } + + let reinitialize_process (state : docstate) : docstate = + { state with + docstringbl = []; + srcstringbl = []; + currentname = None; + currentkind = None; + currentmode = None; + currentproc = false } + + let push_docbl (state : docstate) (docc : string) : docstate = + { state with docstringbl = state.docstringbl @ [docc] } + + let push_srcbl (state : docstate) (srcs : string) : docstate = + { state with srcstringbl = state.srcstringbl @ [srcs] } + + let add_entity (state : docstate) (docent : docentity) : docstate = + { state with docentities = state.docentities @ [docent] } + + let add_item (state : docstate) : docstate = + let state = + if state.currentproc + then + add_entity state (ItemDoc (state.docstringbl, (oget state.currentmode, oget state.currentkind, oget state.currentname, state.srcstringbl))) + else + state + in + reinitialize_process state + + let add_sub (state : docstate) (substate : docstate) : docstate = + let state = + if state.currentproc + then + add_entity state (SubDoc ((state.docstringbl, (oget state.currentmode, oget state.currentkind, oget state.currentname, state.srcstringbl)), + (substate.docentities))) + else + state + in + reinitialize_process state + + end + (* -------------------------------------------------------------------- *) let empty (gstate : EcGState.gstate) = let env = EcEnv.initial gstate in @@ -350,7 +447,9 @@ let empty (gstate : EcGState.gstate) = sc_required = []; sc_clears = []; sc_pr_uc = None; - sc_options = GenOptions.freeze (); } + sc_options = GenOptions.freeze (); + sc_globdoc = []; + sc_locdoc = DocState.empty; } (* -------------------------------------------------------------------- *) let env (scope : scope) = @@ -470,7 +569,8 @@ let for_loading (scope : scope) = sc_clears = []; sc_pr_uc = None; sc_options = GenOptions.for_loading scope.sc_options; - } + sc_globdoc = []; + sc_locdoc = DocState.empty; } (* -------------------------------------------------------------------- *) let subscope (scope : scope) (mode : EcTheory.thmode) (name : symbol) lc = @@ -484,7 +584,10 @@ let subscope (scope : scope) (mode : EcTheory.thmode) (name : symbol) lc = sc_required = scope.sc_required; sc_clears = []; sc_pr_uc = None; - sc_options = GenOptions.for_subscope scope.sc_options; } + sc_options = GenOptions.for_subscope scope.sc_options; + sc_globdoc = []; + sc_locdoc = DocState.empty; + } (* -------------------------------------------------------------------- *) module Prover = struct @@ -694,7 +797,7 @@ module Tactics = struct let pi scope pi = Prover.do_prover_info scope pi - let proof (scope : scope) = + let proof ?(src : string option) (scope : scope) = check_state `InActiveProof "proof script" scope; match (oget scope.sc_pr_uc).puc_active with @@ -705,10 +808,14 @@ module Tactics = struct hierror "[proof] can only be used at beginning of a proof script"; { pac with puc_started = true } in - { scope with sc_pr_uc = - Some { (oget scope.sc_pr_uc) with puc_active = Some (pac, pct); } } + { scope with + sc_pr_uc = Some { (oget scope.sc_pr_uc) with puc_active = Some (pac, pct) }; + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } - let process_r ?reloc mark (mode : proofmode) (scope : scope) (tac : ptactic list) = + let process_r ?(src : string option) ?reloc mark (mode : proofmode) (scope : scope) (tac : ptactic list) = check_state `InProof "proof script" scope; let scope = @@ -720,6 +827,13 @@ module Tactics = struct else scope in + let scope = { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + let puc = oget (scope.sc_pr_uc) in let pac, pct = oget (puc).puc_active in @@ -760,7 +874,7 @@ module Tactics = struct let pac = { pac with puc_jdg = PSCheck juc } in let puc = { puc with puc_active = Some (pac, pct); } in - let scope = { scope with sc_pr_uc = Some puc } in + let scope = { scope with sc_pr_uc = Some puc; } in Some (penv, hds), scope let process1_r mark mode scope t = @@ -770,8 +884,8 @@ module Tactics = struct let ts = List.map (fun t -> { pt_core = t; pt_intros = []; }) ts in snd (process_r mark mode scope ts) - let process scope mode tac = - process_r true mode scope tac + let process ?(src : string option) scope mode tac = + process_r ?src true mode scope tac end (* -------------------------------------------------------------------- *) @@ -825,7 +939,9 @@ module Ax = struct let bind ?(import = true) (scope : scope) ((x, ax) : _ * axiom) = assert (scope.sc_pr_uc = None); let item = EcTheory.mkitem ~import (EcTheory.Th_axiom (x, ax)) in - { scope with sc_env = EcSection.add_item item scope.sc_env } + { scope with sc_env = + EcSection.add_item item scope.sc_env; + sc_locdoc = DocState.add_item scope.sc_locdoc; } (* ------------------------------------------------------------------ *) let start_lemma scope (cont, axflags) check ?name (axd, ctxt) = @@ -1017,22 +1133,69 @@ module Ax = struct save_r scope (* ------------------------------------------------------------------ *) - let save scope = + let save ?(src : string option) scope = check_state `InProof "save" scope; + + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in save_r ~mode:`Save scope (* ------------------------------------------------------------------ *) - let admit scope = + let admit ?(src : string option) scope = check_state `InProof "admitted" scope; + + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + save_r ~mode:`Admit scope (* ------------------------------------------------------------------ *) - let abort scope = + let abort ?(src : string option) scope = check_state `InProof "abort" scope; + + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + snd (save_r ~mode:`Abort scope) (* ------------------------------------------------------------------ *) - let add (scope : scope) (mode : proofmode) (ax : paxiom located) = + let add ?(src : string option) (scope : scope) (mode : proofmode) (ax : paxiom located) = + let uax = unloc ax in + let kind = + match uax.pa_kind with + | PLemma _ -> `Lemma + | _ -> `Axiom + in + let scope = + { scope with + sc_locdoc = + match uax.pa_locality with + | `Local -> DocState.prevent_process scope.sc_locdoc + | `Global -> DocState.start_process scope.sc_locdoc (unloc uax.pa_name) kind `Specific + | `Declare -> DocState.start_process scope.sc_locdoc (unloc uax.pa_name) kind `Abstract} + in + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in add_r scope mode ax (* ------------------------------------------------------------------ *) @@ -1088,10 +1251,30 @@ module Op = struct let bind ?(import = true) (scope : scope) ((x, op) : _ * operator) = assert (scope.sc_pr_uc = None); let item = EcTheory.mkitem ~import (EcTheory.Th_operator (x, op)) in - { scope with sc_env = EcSection.add_item item scope.sc_env; } + { scope with sc_env = + EcSection.add_item item scope.sc_env; + sc_locdoc = DocState.add_item scope.sc_locdoc; } - let add (scope : scope) (op : poperator located) = + let add ?(src : string option) (scope : scope) (op : poperator located) = assert (scope.sc_pr_uc = None); + + let uop = unloc op in + let scope = + { scope with + sc_locdoc = + match uop.po_locality with + | `Local -> DocState.prevent_process scope.sc_locdoc + | `Global -> DocState.start_process scope.sc_locdoc (unloc uop.po_name) `Operator `Specific + | `Declare -> DocState.start_process scope.sc_locdoc (unloc uop.po_name) `Operator `Abstract } + in + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + let op = op.pl_desc and loc = op.pl_loc in let eenv = env scope in let ue = TT.transtyvars eenv (loc, op.po_tyvars) in @@ -1318,9 +1501,26 @@ module Op = struct tyop, List.rev !axs, scope - let add_opsem (scope : scope) (op : pprocop located) = + let add_opsem ?(src : string option) (scope : scope) (op : pprocop located) = let module Sem = EcProcSem in + let uop = unloc op in + let scope = + { scope with + sc_locdoc = + match uop.ppo_locality with + | `Local -> DocState.prevent_process scope.sc_locdoc + | `Global -> DocState.start_process scope.sc_locdoc (unloc uop.ppo_name) `Operator `Specific + | `Declare -> DocState.start_process scope.sc_locdoc (unloc uop.ppo_name) `Operator `Abstract } + in + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + let op = unloc op in let f = EcTyping.trans_gamepath (env scope) op.ppo_target in let sig_, body = @@ -1453,9 +1653,26 @@ end module Pred = struct module TT = EcTyping - let add (scope : scope) (pr : ppredicate located) = + let add ?(src : string option) (scope : scope) (pr : ppredicate located) = assert (scope.sc_pr_uc = None); + let upr = unloc pr in + let scope = + { scope with + sc_locdoc = + match upr.pp_locality with + | `Local -> DocState.prevent_process scope.sc_locdoc + | `Global -> DocState.start_process scope.sc_locdoc (unloc upr.pp_name) `Operator `Specific + | `Declare -> DocState.start_process scope.sc_locdoc (unloc upr.pp_name) `Operator `Abstract } + in + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + let typr = EcHiPredicates.trans_preddecl (env scope) pr in let scope = Op.bind scope (unloc (unloc pr).pp_name, typr) in typr, scope @@ -1480,14 +1697,34 @@ module Mod = struct let bind ?(import = true) (scope : scope) (m : top_module_expr) = assert (scope.sc_pr_uc = None); let item = EcTheory.mkitem ~import (EcTheory.Th_module m) in - { scope with sc_env = EcSection.add_item item scope.sc_env } + { scope with + sc_env = EcSection.add_item item scope.sc_env; + sc_locdoc = DocState.add_item scope.sc_locdoc; } - let add_concrete (scope : scope) lc (ptm : pmodule_def) = + let add_concrete ?(src : string option) (scope : scope) lc (ptm : pmodule_def) = assert (scope.sc_pr_uc = None); if lc = `Declare then hierror "cannot use [declare] for concrete modules"; + let nm = unloc (EcParsetree.pcmhd_ident ptm.ptm_header) in + + let scope = + { scope with + sc_locdoc = + match lc with + | `Local -> DocState.prevent_process scope.sc_locdoc + | `Global -> DocState.start_process scope.sc_locdoc nm `Module `Specific + | `Declare -> DocState.start_process scope.sc_locdoc nm `Module `Abstract } + in + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + let m = TT.transmod (env scope) ~attop:true ptm in let ur = EcModules.get_uninit_read_of_module (path scope) m in @@ -1517,10 +1754,10 @@ module Mod = struct { scope with sc_env = EcSection.add_decl_mod name tysig scope.sc_env } - let add (scope : scope) (m : pmodule_def_or_decl) = + let add ?(src : string option) (scope : scope) (m : pmodule_def_or_decl) = match m with | { ptm_locality = lc; ptm_def = `Concrete def } -> - add_concrete scope lc def + add_concrete ?src scope lc def | { ptm_locality = lc; ptm_def = `Abstract decl } -> if lc <> `Declare then @@ -1541,10 +1778,27 @@ module ModType = struct = assert (scope.sc_pr_uc = None); let item = EcTheory.mkitem ~import (EcTheory.Th_modtype (x, tysig)) in - { scope with sc_env = EcSection.add_item item scope.sc_env } + { scope with + sc_env = EcSection.add_item item scope.sc_env; + sc_locdoc = DocState.add_item scope.sc_locdoc; } - let add (scope : scope) (intf : pinterface) = + let add ?(src : string option) (scope : scope) (intf : pinterface) = assert (scope.sc_pr_uc = None); + + let scope = + { scope with + sc_locdoc = + match intf.pi_locality with + | `Local -> DocState.prevent_process scope.sc_locdoc + | `Global -> DocState.start_process scope.sc_locdoc (unloc intf.pi_name) `ModuleType `Specific } + in + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in let tysig = EcTyping.transmodsig (env scope) intf in bind scope (unloc intf.pi_name, tysig) end @@ -1581,8 +1835,23 @@ module Theory = struct in { scope with sc_required = List.map for1 scope.sc_required } (* ------------------------------------------------------------------ *) - let enter (scope : scope) (mode : thmode) (name : symbol) = + let enter ?(src : string option) (scope : scope) (mode : thmode) (name : symbol) = assert (scope.sc_pr_uc = None); + let sc_locdoc = scope.sc_locdoc in + let sc_locdoc = + match src with + | None -> DocState.prevent_process scope.sc_locdoc + | Some src -> + let sc_locdoc = + DocState.start_process sc_locdoc name `Theory + (match mode with `Concrete -> `Specific | `Abstract -> `Abstract) + in + DocState.push_srcbl sc_locdoc src + in + let + scope = { scope with sc_locdoc } + in + subscope scope mode name (* ------------------------------------------------------------------ *) @@ -1633,7 +1902,10 @@ module Theory = struct let _, cth, _ = EcSection.exit_theory ?pempty ~clears scope.sc_env in let loaded = scope.sc_loaded in let required = scope.sc_required in - let sup = { sup with sc_loaded = loaded; } in + let sup = { + sup with + sc_loaded = loaded; + sc_locdoc = DocState.add_sub sup.sc_locdoc scope.sc_locdoc} in ((cth, required), scope.sc_name, sup) (* ------------------------------------------------------------------ *) @@ -1897,7 +2169,7 @@ module Cloning = struct | `Include -> scope) scope in - + if is_none thcl.pthc_local && oth.cth_loca = `Local then notify scope `Info "Theory `%s` has inherited `local` visibility. \ @@ -1929,10 +2201,29 @@ module Ty = struct let bind ?(import = true) (scope : scope) ((x, tydecl) : (_ * tydecl)) = assert (scope.sc_pr_uc = None); let item = EcTheory.mkitem ~import (EcTheory.Th_type (x, tydecl)) in - { scope with sc_env = EcSection.add_item item scope.sc_env } + { scope with + sc_env = EcSection.add_item item scope.sc_env; + sc_locdoc = DocState.add_item scope.sc_locdoc; } (* ------------------------------------------------------------------ *) - let add scope (tyd : ptydecl located) = + let add ?(src : string option) scope (tyd : ptydecl located) = + let utyd = unloc tyd in + let scope = + { scope with + sc_locdoc = + match utyd.pty_locality with + | `Local -> DocState.prevent_process scope.sc_locdoc + | `Global -> DocState.start_process scope.sc_locdoc (unloc utyd.pty_name) `Type `Specific + | `Declare -> DocState.start_process scope.sc_locdoc (unloc utyd.pty_name) `Type `Abstract } + in + let scope = + { scope with + sc_locdoc = + match src with + | Some src -> DocState.push_srcbl scope.sc_locdoc src + | None -> scope.sc_locdoc; } + in + let loc = loc tyd in let { pty_name = name; pty_tyvars = args; @@ -1948,25 +2239,28 @@ module Ty = struct (fun tc -> fst (EcEnv.TypeClass.lookup (unloc tc) env)) tcs in let ue = TT.transtyvars env (loc, Some args) in - EcUnify.UniEnv.tparams ue, `Abstract (Sp.of_list tcs) + EcUnify.UniEnv.tparams ue, Abstract (Sp.of_list tcs) | PTYD_Alias bd -> let ue = TT.transtyvars env (loc, Some args) in let body = transty tp_tydecl env ue bd in - EcUnify.UniEnv.tparams ue, `Concrete body - - | PTYD_Datatype dt -> - let datatype = EHI.trans_datatype env (mk_loc loc (args,name)) dt in - let tparams, tydt = - try ELI.datatype_as_ty_dtype datatype - with ELI.NonPositive -> EHI.dterror loc env EHI.DTE_NonPositive - in - tparams, `Datatype tydt + EcUnify.UniEnv.tparams ue, Concrete body + + | PTYD_Datatype dt -> ( + let datatype = EHI.trans_datatype env (mk_loc loc (args, name)) dt in + let ty_from_ctor ctor = EcEnv.Ty.by_path ctor env in + try + ELI.check_positivity ty_from_ctor datatype; + let tparams, tydt = ELI.datatype_as_ty_dtype datatype in + (tparams, Datatype tydt) + with ELI.NonPositive ctx -> + let symbol = basename datatype.dt_path in + EHI.dterror loc env (EHI.DTE_NonPositive (symbol, ctx))) | PTYD_Record rt -> let record = EHI.trans_record env (mk_loc loc (args,name)) rt in let scheme = ELI.indsc_of_record record in - record.ELI.rc_tparams, `Record (scheme, record.ELI.rc_fields) + record.ELI.rc_tparams, Record (scheme, record.ELI.rc_fields) in bind scope (unloc name, { tyd_params; tyd_type; tyd_loca; }) @@ -1979,21 +2273,21 @@ module Ty = struct let scope = let decl = EcDecl.{ tyd_params = []; - tyd_type = `Abstract Sp.empty; + tyd_type = Abstract Sp.empty; tyd_loca = `Global; (* FIXME:SUBTYPE *) } in bind scope (unloc subtype.pst_name, decl) in let carrier = let ue = EcUnify.UniEnv.create None in transty tp_tydecl env ue subtype.pst_carrier in - + let pred = let x = EcIdent.create (fst subtype.pst_pred).pl_desc in let env = EcEnv.Var.bind_local x carrier env in let ue = EcUnify.UniEnv.create None in let pred = EcTyping.trans_prop env ue (snd subtype.pst_pred) in if not (EcUnify.UniEnv.closed ue) then - hierror ~loc:(snd subtype.pst_pred).pl_loc + hierror ~loc:(snd subtype.pst_pred).pl_loc "the predicate contains free type variables"; let uidmap = EcUnify.UniEnv.close ue in let fs = Tuni.subst uidmap in @@ -2015,12 +2309,12 @@ module Ty = struct ev_bynames = Msym.empty; ev_global = [ (None, Some [`Include, "prove"]) ] } } in - + let cname = Option.map unloc subtype.pst_cname in let npath = ofold ((^~) EcPath.pqname) (EcEnv.root env) cname in let cpath = EcPath.fromqsymbol ([EcCoreLib.i_top], "Subtype") in let theory = EcEnv.Theory.by_path ~mode:`Abstract cpath env in - + let renames = match subtype.pst_rename with | None -> [] @@ -2043,7 +2337,7 @@ module Ty = struct ) in let proofs = Cloning.replay_proofs scope `Check proofs in - + Ax.add_defer scope proofs (* ------------------------------------------------------------------ *) @@ -2074,7 +2368,7 @@ module Ty = struct let asty = let body = ofold (fun p tc -> Sp.add p tc) Sp.empty uptc in { tyd_params = []; - tyd_type = `Abstract body; + tyd_type = Abstract body; tyd_loca = (lc :> locality); } in let scenv = EcEnv.Ty.bind name asty scenv in @@ -2508,3 +2802,14 @@ end notify scope `Info "%s" (Buffer.contents buffer) end + +(* -------------------------------------------------------------------- *) +module DocComment = struct + let add (scope : scope) ((kind, docc) : [`Global | `Item] * string) : scope = + match kind with + | `Global -> + { scope with sc_globdoc = scope.sc_globdoc @ [docc] } + + | `Item -> + { scope with sc_locdoc = DocState.push_docbl scope.sc_locdoc docc } +end diff --git a/src/ecScope.mli b/src/ecScope.mli index bc3c2812d..d64007674 100644 --- a/src/ecScope.mli +++ b/src/ecScope.mli @@ -54,9 +54,25 @@ and pucflags = { puc_local : bool; } +type docentity = + | ItemDoc of string list * docitem + | SubDoc of (string list * docitem) * docentity list + +and docitem = + mode * itemkind * string * string list (* dec/reg, kind, name, src *) + +and itemkind = [`Type | `Operator | `Axiom | `Lemma | `ModuleType | `Module | `Theory] + +and mode = [`Abstract | `Specific] + (* -------------------------------------------------------------------- *) val notify : scope -> EcGState.loglevel -> ('a, Format.formatter, unit, unit) format4 -> 'a +(* -------------------------------------------------------------------- *) +val get_gdocstrings : scope -> string list +val get_ldocentities : scope -> docentity list + + (* -------------------------------------------------------------------- *) val empty : EcGState.gstate -> scope val gstate : scope -> EcGState.gstate @@ -93,30 +109,30 @@ end (* -------------------------------------------------------------------- *) module Op : sig - val add : scope -> poperator located -> EcDecl.operator * string list * scope + val add : ?src:string -> scope -> poperator located -> EcDecl.operator * string list * scope - val add_opsem : scope -> pprocop located -> scope + val add_opsem : ?src:string -> scope -> pprocop located -> scope end (* -------------------------------------------------------------------- *) module Pred : sig - val add : scope -> ppredicate located -> EcDecl.operator * scope + val add : ?src:string -> scope -> ppredicate located -> EcDecl.operator * scope end (* -------------------------------------------------------------------- *) module Ax : sig type proofmode = [`WeakCheck | `Check | `Report] - val add : scope -> proofmode -> paxiom located -> symbol option * scope - val save : scope -> string option * scope - val admit : scope -> string option * scope - val abort : scope -> scope + val add : ?src:string -> scope -> proofmode -> paxiom located -> symbol option * scope + val save : ?src:string -> scope -> string option * scope + val admit : ?src:string -> scope -> string option * scope + val abort : ?src:string -> scope -> scope val realize : scope -> proofmode -> prealize located -> symbol option * scope end (* -------------------------------------------------------------------- *) module Ty : sig - val add : scope -> ptydecl located -> scope + val add : ?src:string -> scope -> ptydecl located -> scope val add_subtype : scope -> psubtype located -> scope val add_class : scope -> ptypeclass located -> scope @@ -125,14 +141,14 @@ end (* -------------------------------------------------------------------- *) module Mod : sig - val add : scope -> pmodule_def_or_decl -> scope + val add : ?src:string ->scope -> pmodule_def_or_decl -> scope val declare : scope -> pmodule_decl -> scope val import : scope -> pmsymbol located -> scope end (* -------------------------------------------------------------------- *) module ModType : sig - val add : scope -> pinterface -> scope + val add : ?src:string -> scope -> pinterface -> scope end (* -------------------------------------------------------------------- *) @@ -147,7 +163,7 @@ module Theory : sig (* [enter scope mode name] start a theory in scope [scope] with * name [name] and mode (abstract/concrete) [mode]. *) - val enter : scope -> thmode -> symbol -> EcTypes.is_local -> scope + val enter : ?src:string -> scope -> thmode -> symbol -> EcTypes.is_local -> scope (* [exit scope] close and finalize the top-most theory and returns * its name. Raises [TopScope] if [scope] has not super scope. *) @@ -195,8 +211,8 @@ module Tactics : sig type prinfos = proofenv * (handle * handle list) type proofmode = Ax.proofmode - val process : scope -> proofmode -> ptactic list -> prinfos option * scope - val proof : scope -> scope + val process : ?src:string -> scope -> proofmode -> ptactic list -> prinfos option * scope + val proof : ?src:string -> scope -> scope end (* -------------------------------------------------------------------- *) @@ -260,3 +276,8 @@ module Search : sig val search : scope -> pformula list -> unit val locate : scope -> pqsymbol -> unit end + +(* -------------------------------------------------------------------- *) +module DocComment : sig + val add : scope -> [`Global | `Item] * string -> scope +end diff --git a/src/ecSection.ml b/src/ecSection.ml index cdd5b1cb2..b87cf7b68 100644 --- a/src/ecSection.ml +++ b/src/ecSection.ml @@ -42,7 +42,10 @@ let pp_cbarg env fmt (who : cbarg) = | `Module mp -> let ppe = match mp.m_top with - | `Local id -> EcPrinting.PPEnv.add_locals ppe [id] + | `Local id -> + if EcEnv.Mod.is_declared id env then + ppe + else EcPrinting.PPEnv.add_locals ppe [id] | _ -> ppe in Format.fprintf fmt "module %a" (EcPrinting.pp_topmod ppe) mp | `ModuleType p -> @@ -91,325 +94,396 @@ let hierror fmt = bfmt fmt (* -------------------------------------------------------------------- *) -let rec on_mp (cb : cb) (mp : mpath) = - let f = m_functor mp in - cb (`Module f); - List.iter (on_mp cb) mp.m_args +type aenv = { + env : EcEnv.env; (* Global environment for dep. analysis *) + cb : cb; (* Dep. analysis callback *) + cache : acache ref; (* Dep. analysis cache *) +} -let on_xp (cb : cb) (xp : xpath) = - on_mp cb xp.x_top +and acache = { + op : Sp.t; (* Operator declaration already handled *) + type_ : Sp.t; (* Type declaration already handled *) +} -let rec on_ty (cb : cb) (ty : ty) = - match ty.ty_node with - | Tunivar _ -> () - | Tvar _ -> () - | Tglob _ -> () - | Ttuple tys -> List.iter (on_ty cb) tys - | Tconstr (p, tys) -> cb (`Type p); List.iter (on_ty cb) tys - | Tfun (ty1, ty2) -> List.iter (on_ty cb) [ty1; ty2] +(* -------------------------------------------------------------------- *) +let empty_acache : acache = + { op = Sp.empty; type_ = Sp.empty; } + +(* -------------------------------------------------------------------- *) +let mkaenv (env : EcEnv.env) (cb : cb) : aenv = + { env; cb; cache = ref empty_acache; } + +(* -------------------------------------------------------------------- *) +let rec on_mp (aenv : aenv) (mp : mpath) = + aenv.cb (`Module (m_functor mp)); + List.iter (on_mp aenv) mp.m_args + +(* -------------------------------------------------------------------- *) +and on_xp (aenv : aenv) (xp : xpath) = + on_mp aenv xp.x_top + +(* -------------------------------------------------------------------- *) +and on_memtype (aenv : aenv) (mt : EcMemory.memtype) = + EcMemory.mt_iter_ty (on_ty aenv) mt -let on_pv (cb : cb) (pv : prog_var)= +(* -------------------------------------------------------------------- *) +and on_memenv (aenv : aenv) (m : EcMemory.memenv) = + on_memtype aenv (snd m) + +(* -------------------------------------------------------------------- *) +and on_pv (aenv : aenv) (pv : prog_var)= match pv with - | PVglob xp -> on_xp cb xp + | PVglob xp -> on_xp aenv xp | _ -> () -let on_lp (cb : cb) (lp : lpattern) = +(* -------------------------------------------------------------------- *) +and on_lp (aenv : aenv) (lp : lpattern) = match lp with - | LSymbol (_, ty) -> on_ty cb ty - | LTuple xs -> List.iter (fun (_, ty) -> on_ty cb ty) xs - | LRecord (_, xs) -> List.iter (on_ty cb |- snd) xs + | LSymbol (_, ty) -> on_ty aenv ty + | LTuple xs -> List.iter (fun (_, ty) -> on_ty aenv ty) xs + | LRecord (_, xs) -> List.iter (on_ty aenv |- snd) xs -let on_binding (cb : cb) ((_, ty) : (EcIdent.t * ty)) = - on_ty cb ty +(* -------------------------------------------------------------------- *) +and on_binding (aenv : aenv) ((_, ty) : (EcIdent.t * ty)) = + on_ty aenv ty -let on_bindings (cb : cb) (bds : (EcIdent.t * ty) list) = - List.iter (on_binding cb) bds +(* -------------------------------------------------------------------- *) +and on_bindings (aenv : aenv) (bds : (EcIdent.t * ty) list) = + List.iter (on_binding aenv) bds -let rec on_expr (cb : cb) (e : expr) = - let cbrec = on_expr cb in +(* -------------------------------------------------------------------- *) +and on_ty (aenv : aenv) (ty : ty) = + match ty.ty_node with + | Tunivar _ -> () + | Tvar _ -> () + | Tglob m -> aenv.cb (`Module (mident m)) + | Ttuple tys -> List.iter (on_ty aenv) tys + | Tconstr (p, tys) -> on_tyname aenv p; List.iter (on_ty aenv) tys + | Tfun (ty1, ty2) -> List.iter (on_ty aenv) [ty1; ty2] + +(* -------------------------------------------------------------------- *) +and on_tyname (aenv : aenv) (p : path) = + aenv.cb (`Type p); + if not (Sp.mem p !(aenv.cache).type_) then begin + let cache = { !(aenv.cache) with type_ = Sp.add p !(aenv.cache).type_ } in + aenv.cache := cache; + on_tydecl aenv (EcEnv.Ty.by_path p aenv.env) + end + +(* -------------------------------------------------------------------- *) +and on_opname (aenv : aenv) (p : EcPath.path) = + aenv.cb (`Op p); + if not (Sp.mem p !(aenv.cache).op) then begin + let cache = { !(aenv.cache) with op = Sp.add p !(aenv.cache).op } in + aenv.cache := cache; + on_opdecl aenv (EcEnv.Op.by_path p aenv.env); + end + +(* -------------------------------------------------------------------- *) +and on_expr (aenv : aenv) (e : expr) = + let cbrec = on_expr aenv in let fornode () = match e.e_node with | Eint _ -> () | Elocal _ -> () - | Equant (_, bds, e) -> on_bindings cb bds; cbrec e - | Evar pv -> on_pv cb pv - | Elet (lp, e1, e2) -> on_lp cb lp; List.iter cbrec [e1; e2] + | Equant (_, bds, e) -> on_bindings aenv bds; cbrec e + | Evar pv -> on_pv aenv pv + | Elet (lp, e1, e2) -> on_lp aenv lp; List.iter cbrec [e1; e2] | Etuple es -> List.iter cbrec es - | Eop (p, tys) -> cb (`Op p); List.iter (on_ty cb) tys | Eapp (e, es) -> List.iter cbrec (e :: es) | Eif (c, e1, e2) -> List.iter cbrec [c; e1; e2] - | Ematch (e, es, ty) -> on_ty cb ty; List.iter cbrec (e :: es) + | Ematch (e, es, ty) -> on_ty aenv ty; List.iter cbrec (e :: es) | Eproj (e, _) -> cbrec e - in on_ty cb e.e_ty; fornode () + | Eop (p, tys) -> begin + on_opname aenv p; + List.iter (on_ty aenv) tys; + end + + in on_ty aenv e.e_ty; fornode () -let on_lv (cb : cb) (lv : lvalue) = - let for1 (pv, ty) = on_pv cb pv; on_ty cb ty in +(* -------------------------------------------------------------------- *) +and on_lv (aenv : aenv) (lv : lvalue) = + let for1 (pv, ty) = on_pv aenv pv; on_ty aenv ty in match lv with | LvVar pv -> for1 pv | LvTuple pvs -> List.iter for1 pvs -let rec on_instr (cb : cb) (i : instr)= +(* -------------------------------------------------------------------- *) +and on_instr (aenv : aenv) (i : instr)= match i.i_node with | Srnd (lv, e) | Sasgn (lv, e) -> - on_lv cb lv; - on_expr cb e + on_lv aenv lv; + on_expr aenv e | Sassert e -> - on_expr cb e + on_expr aenv e | Scall (lv, f, args) -> - lv |> oiter (on_lv cb); - on_xp cb f; - List.iter (on_expr cb) args + oiter (on_lv aenv) lv; + on_xp aenv f; + List.iter (on_expr aenv) args | Sif (e, s1, s2) -> - on_expr cb e; - List.iter (on_stmt cb) [s1; s2] + on_expr aenv e; + List.iter (on_stmt aenv) [s1; s2] | Swhile (e, s) -> - on_expr cb e; - on_stmt cb s + on_expr aenv e; + on_stmt aenv s | Smatch (e, b) -> let forb (bs, s) = - List.iter (on_ty cb |- snd) bs; - on_stmt cb s - in on_expr cb e; List.iter forb b + List.iter (on_ty aenv |- snd) bs; + on_stmt aenv s + in on_expr aenv e; List.iter forb b | Sabstract _ -> () -and on_stmt (cb : cb) (s : stmt) = - List.iter (on_instr cb) s.s_node - -let on_memtype cb mt = - EcMemory.mt_iter_ty (on_ty cb) mt - -let on_memenv cb (m : EcMemory.memenv) = - on_memtype cb (snd m) +(* -------------------------------------------------------------------- *) +and on_stmt (aenv : aenv) (s : stmt) = + List.iter (on_instr aenv) s.s_node -let rec on_form (cb : cb) (f : EcFol.form) = - let cbrec = on_form cb in +(* -------------------------------------------------------------------- *) +and on_form (aenv : aenv) (f : EcFol.form) = + let cbrec = on_form aenv in let rec fornode () = match f.EcAst.f_node with | EcAst.Fint _ -> () | EcAst.Flocal _ -> () - | EcAst.Fquant (_, b, f) -> on_gbindings cb b; cbrec f + | EcAst.Fquant (_, b, f) -> on_gbindings aenv b; cbrec f | EcAst.Fif (f1, f2, f3) -> List.iter cbrec [f1; f2; f3] - | EcAst.Fmatch (b, fs, ty) -> on_ty cb ty; List.iter cbrec (b :: fs) - | EcAst.Flet (lp, f1, f2) -> on_lp cb lp; List.iter cbrec [f1; f2] - | EcAst.Fop (p, tys) -> cb (`Op p); List.iter (on_ty cb) tys + | EcAst.Fmatch (b, fs, ty) -> on_ty aenv ty; List.iter cbrec (b :: fs) + | EcAst.Flet (lp, f1, f2) -> on_lp aenv lp; List.iter cbrec [f1; f2] | EcAst.Fapp (f, fs) -> List.iter cbrec (f :: fs) | EcAst.Ftuple fs -> List.iter cbrec fs | EcAst.Fproj (f, _) -> cbrec f - | EcAst.Fpvar (pv, _) -> on_pv cb pv + | EcAst.Fpvar (pv, _) -> on_pv aenv pv | EcAst.Fglob _ -> () - | EcAst.FhoareF hf -> on_hf cb hf - | EcAst.FhoareS hs -> on_hs cb hs - | EcAst.FeHoareF hf -> on_ehf cb hf - | EcAst.FeHoareS hs -> on_ehs cb hs - | EcAst.FequivF ef -> on_ef cb ef - | EcAst.FequivS es -> on_es cb es - | EcAst.FeagerF eg -> on_eg cb eg - | EcAst.FbdHoareS bhs -> on_bhs cb bhs - | EcAst.FbdHoareF bhf -> on_bhf cb bhf - | EcAst.Fpr pr -> on_pr cb pr - - and on_hf cb hf = - on_form cb (hf_pr hf).inv; - on_form cb (hf_po hf).inv; - on_xp cb hf.EcAst.hf_f - - and on_hs cb hs = - on_form cb (hs_pr hs).inv; - on_form cb (hs_po hs).inv; - on_stmt cb hs.EcAst.hs_s; - on_memenv cb hs.EcAst.hs_m - - and on_ef cb ef = - on_form cb (EcAst.ef_pr ef).inv; - on_form cb (EcAst.ef_po ef).inv; - on_xp cb ef.EcAst.ef_fl; - on_xp cb ef.EcAst.ef_fr - - and on_es cb es = - on_form cb (EcAst.es_pr es).inv; - on_form cb (EcAst.es_po es).inv; - on_stmt cb es.EcAst.es_sl; - on_stmt cb es.EcAst.es_sr; - on_memenv cb es.EcAst.es_ml; - on_memenv cb es.EcAst.es_mr - - and on_eg cb eg = - on_form cb (EcAst.eg_pr eg).inv; - on_form cb (EcAst.eg_po eg).inv; - on_xp cb eg.EcAst.eg_fl; - on_xp cb eg.EcAst.eg_fr; - on_stmt cb eg.EcAst.eg_sl; - on_stmt cb eg.EcAst.eg_sr; - - and on_ehf cb hf = - on_form cb (EcAst.ehf_pr hf).inv; - on_form cb (EcAst.ehf_po hf).inv; - on_xp cb hf.EcAst.ehf_f - - and on_ehs cb hs = - on_form cb (EcAst.ehs_pr hs).inv; - on_form cb (EcAst.ehs_po hs).inv; - on_stmt cb hs.EcAst.ehs_s; - on_memenv cb hs.EcAst.ehs_m - - and on_bhf cb bhf = - on_form cb (EcAst.bhf_pr bhf).inv; - on_form cb (EcAst.bhf_po bhf).inv; - on_form cb (EcAst.bhf_bd bhf).inv; - on_xp cb bhf.EcAst.bhf_f - - and on_bhs cb bhs = - on_form cb (EcAst.bhs_pr bhs).inv; - on_form cb (EcAst.bhs_po bhs).inv; - on_form cb (EcAst.bhs_bd bhs).inv; - on_stmt cb bhs.EcAst.bhs_s; - on_memenv cb bhs.EcAst.bhs_m - - - and on_pr cb pr = - on_xp cb pr.EcAst.pr_fun; - List.iter (on_form cb) [pr.EcAst.pr_event.inv; pr.EcAst.pr_args] + | EcAst.FhoareF hf -> on_hf aenv hf + | EcAst.FhoareS hs -> on_hs aenv hs + | EcAst.FeHoareF hf -> on_ehf aenv hf + | EcAst.FeHoareS hs -> on_ehs aenv hs + | EcAst.FequivF ef -> on_ef aenv ef + | EcAst.FequivS es -> on_es aenv es + | EcAst.FeagerF eg -> on_eg aenv eg + | EcAst.FbdHoareS bhs -> on_bhs aenv bhs + | EcAst.FbdHoareF bhf -> on_bhf aenv bhf + | EcAst.Fpr pr -> on_pr aenv pr + + | EcAst.Fop (p, tys) -> begin + on_opname aenv p; + List.iter (on_ty aenv) tys; + end + + and on_hf (aenv : aenv) hf = + on_form aenv (hf_pr hf).inv; + on_form aenv (hf_po hf).inv; + on_xp aenv hf.EcAst.hf_f + + and on_hs (aenv : aenv) hs = + on_form aenv (hs_pr hs).inv; + on_form aenv (hs_po hs).inv; + on_stmt aenv hs.EcAst.hs_s; + on_memenv aenv hs.EcAst.hs_m + + and on_ef (aenv : aenv) ef = + on_form aenv (EcAst.ef_pr ef).inv; + on_form aenv (EcAst.ef_po ef).inv; + on_xp aenv ef.EcAst.ef_fl; + on_xp aenv ef.EcAst.ef_fr + + and on_es (aenv : aenv) es = + on_form aenv (EcAst.es_pr es).inv; + on_form aenv (EcAst.es_po es).inv; + on_stmt aenv es.EcAst.es_sl; + on_stmt aenv es.EcAst.es_sr; + on_memenv aenv es.EcAst.es_ml; + on_memenv aenv es.EcAst.es_mr + + and on_eg (aenv : aenv) eg = + on_form aenv (EcAst.eg_pr eg).inv; + on_form aenv (EcAst.eg_po eg).inv; + on_xp aenv eg.EcAst.eg_fl; + on_xp aenv eg.EcAst.eg_fr; + on_stmt aenv eg.EcAst.eg_sl; + on_stmt aenv eg.EcAst.eg_sr; + + and on_ehf (aenv : aenv) hf = + on_form aenv (EcAst.ehf_pr hf).inv; + on_form aenv (EcAst.ehf_po hf).inv; + on_xp aenv hf.EcAst.ehf_f + + and on_ehs (aenv : aenv) hs = + on_form aenv (EcAst.ehs_pr hs).inv; + on_form aenv (EcAst.ehs_po hs).inv; + on_stmt aenv hs.EcAst.ehs_s; + on_memenv aenv hs.EcAst.ehs_m + + and on_bhf (aenv : aenv) bhf = + on_form aenv (EcAst.bhf_pr bhf).inv; + on_form aenv (EcAst.bhf_po bhf).inv; + on_form aenv (EcAst.bhf_bd bhf).inv; + on_xp aenv bhf.EcAst.bhf_f + + and on_bhs (aenv : aenv) bhs = + on_form aenv (EcAst.bhs_pr bhs).inv; + on_form aenv (EcAst.bhs_po bhs).inv; + on_form aenv (EcAst.bhs_bd bhs).inv; + on_stmt aenv bhs.EcAst.bhs_s; + on_memenv aenv bhs.EcAst.bhs_m + + + and on_pr (aenv : aenv) pr = + on_xp aenv pr.EcAst.pr_fun; + List.iter (on_form aenv) [pr.EcAst.pr_event.inv; pr.EcAst.pr_args] in - on_ty cb f.EcAst.f_ty; fornode () + on_ty aenv f.EcAst.f_ty; fornode () -and on_restr (cb : cb) (restr : mod_restr) = - let doit (xs, ms) = Sx.iter (on_xp cb) xs; Sm.iter (on_mp cb) ms in +(* -------------------------------------------------------------------- *) +and on_restr (aenv : aenv) (restr : mod_restr) = + let doit (xs, ms) = Sx.iter (on_xp aenv) xs; Sm.iter (on_mp aenv) ms in oiter doit restr.ur_pos; doit restr.ur_neg -and on_modty cb (mty : module_type) = - cb (`ModuleType mty.mt_name); - List.iter (fun (_, mty) -> on_modty cb mty) mty.mt_params; - List.iter (on_mp cb) mty.mt_args +(* -------------------------------------------------------------------- *) +and on_modty (aenv : aenv) (mty : module_type) = + aenv.cb (`ModuleType mty.mt_name); + List.iter (fun (_, mty) -> on_modty aenv mty) mty.mt_params; + List.iter (on_mp aenv) mty.mt_args -and on_mty_mr (cb : cb) ((mty, mr) : mty_mr) = - on_modty cb mty; on_restr cb mr +(* -------------------------------------------------------------------- *) +and on_mty_mr (aenv : aenv) ((mty, mr) : mty_mr) = + on_modty aenv mty; on_restr aenv mr -and on_gbinding (cb : cb) (b : gty) = +(* -------------------------------------------------------------------- *) +and on_gbinding (aenv : aenv) (b : gty) = match b with | EcAst.GTty ty -> - on_ty cb ty + on_ty aenv ty | EcAst.GTmodty mty -> - on_mty_mr cb mty + on_mty_mr aenv mty | EcAst.GTmem m -> - on_memtype cb m + on_memtype aenv m -and on_gbindings (cb : cb) (b : (EcIdent.t * gty) list) = - List.iter (fun (_, b) -> on_gbinding cb b) b +(* -------------------------------------------------------------------- *) +and on_gbindings (aenv : aenv) (b : (EcIdent.t * gty) list) = + List.iter (fun (_, b) -> on_gbinding aenv b) b -and on_module (cb : cb) (me : module_expr) = +(* -------------------------------------------------------------------- *) +and on_module (aenv : aenv) (me : module_expr) = match me.me_body with - | ME_Alias (_, mp) -> on_mp cb mp - | ME_Structure st -> on_mstruct cb st - | ME_Decl mty -> on_mty_mr cb mty + | ME_Alias (_, mp) -> on_mp aenv mp + | ME_Structure st -> on_mstruct aenv st + | ME_Decl mty -> on_mty_mr aenv mty -and on_mstruct (cb : cb) (st : module_structure) = - List.iter (on_mpath_mstruct1 cb) st.ms_body +(* -------------------------------------------------------------------- *) +and on_mstruct (aenv : aenv) (st : module_structure) = + List.iter (on_mstruct1 aenv) st.ms_body -and on_mpath_mstruct1 (cb : cb) (item : module_item) = +(* -------------------------------------------------------------------- *) +and on_mstruct1 (aenv : aenv) (item : module_item) = match item with - | MI_Module me -> on_module cb me - | MI_Variable x -> on_ty cb x.v_type - | MI_Function f -> on_fun cb f + | MI_Module me -> on_module aenv me + | MI_Variable x -> on_ty aenv x.v_type + | MI_Function f -> on_fun aenv f -and on_fun (cb : cb) (fun_ : function_) = - on_fun_sig cb fun_.f_sig; - on_fun_body cb fun_.f_def +(* -------------------------------------------------------------------- *) +and on_fun (aenv : aenv) (fun_ : function_) = + on_fun_sig aenv fun_.f_sig; + on_fun_body aenv fun_.f_def -and on_fun_sig (cb : cb) (fsig : funsig) = - on_ty cb fsig.fs_arg; - on_ty cb fsig.fs_ret +(* -------------------------------------------------------------------- *) +and on_fun_sig (aenv : aenv) (fsig : funsig) = + on_ty aenv fsig.fs_arg; + on_ty aenv fsig.fs_ret -and on_fun_body (cb : cb) (fbody : function_body) = +(* -------------------------------------------------------------------- *) +and on_fun_body (aenv : aenv) (fbody : function_body) = match fbody with - | FBalias xp -> on_xp cb xp - | FBdef fdef -> on_fun_def cb fdef - | FBabs oi -> on_oi cb oi + | FBalias xp -> on_xp aenv xp + | FBdef fdef -> on_fun_def aenv fdef + | FBabs oi -> on_oi aenv oi -and on_fun_def (cb : cb) (fdef : function_def) = - List.iter (fun v -> on_ty cb v.v_type) fdef.f_locals; - on_stmt cb fdef.f_body; - fdef.f_ret |> oiter (on_expr cb); - on_uses cb fdef.f_uses +(* -------------------------------------------------------------------- *) +and on_fun_def (aenv : aenv) (fdef : function_def) = + List.iter (fun v -> on_ty aenv v.v_type) fdef.f_locals; + on_stmt aenv fdef.f_body; + fdef.f_ret |> oiter (on_expr aenv); + on_uses aenv fdef.f_uses -and on_uses (cb : cb) (uses : uses) = - List.iter (on_xp cb) uses.us_calls; - Sx.iter (on_xp cb) uses.us_reads; - Sx.iter (on_xp cb) uses.us_writes +(* -------------------------------------------------------------------- *) +and on_uses (aenv : aenv) (uses : uses) = + List.iter (on_xp aenv) uses.us_calls; + Sx.iter (on_xp aenv) uses.us_reads; + Sx.iter (on_xp aenv) uses.us_writes -and on_oi (cb : cb) (oi : OI.t) = - List.iter (on_xp cb) (OI.allowed oi) +(* -------------------------------------------------------------------- *) +and on_oi (aenv : aenv) (oi : OI.t) = + List.iter (on_xp aenv) (OI.allowed oi) (* -------------------------------------------------------------------- *) -let on_typeclasses cb s = - Sp.iter (fun p -> cb (`Typeclass p)) s +and on_typeclasses (aenv : aenv) s = + Sp.iter (fun p -> aenv.cb (`Typeclass p)) s -let on_typarams cb typarams = - List.iter (fun (_,s) -> on_typeclasses cb s) typarams +and on_typarams (aenv : aenv) typarams = + List.iter (fun (_,s) -> on_typeclasses aenv s) typarams (* -------------------------------------------------------------------- *) -let on_tydecl (cb : cb) (tyd : tydecl) = - on_typarams cb tyd.tyd_params; +and on_tydecl (aenv : aenv) (tyd : tydecl) = + on_typarams aenv tyd.tyd_params; match tyd.tyd_type with - | `Concrete ty -> on_ty cb ty - | `Abstract s -> on_typeclasses cb s - | `Record (f, fds) -> - on_form cb f; - List.iter (on_ty cb |- snd) fds - | `Datatype dt -> - List.iter (List.iter (on_ty cb) |- snd) dt.tydt_ctors; - List.iter (on_form cb) [dt.tydt_schelim; dt.tydt_schcase] - -let on_typeclass cb tc = - oiter (fun p -> cb (`Typeclass p)) tc.tc_prt; - List.iter (fun (_,ty) -> on_ty cb ty) tc.tc_ops; - List.iter (fun (_,f) -> on_form cb f) tc.tc_axs + | Concrete ty -> on_ty aenv ty + | Abstract s -> on_typeclasses aenv s + | Record (f, fds) -> + on_form aenv f; + List.iter (on_ty aenv |- snd) fds + | Datatype dt -> + List.iter (List.iter (on_ty aenv) |- snd) dt.tydt_ctors; + List.iter (on_form aenv) [dt.tydt_schelim; dt.tydt_schcase] + +and on_typeclass (aenv : aenv) tc = + oiter (fun p -> aenv.cb (`Typeclass p)) tc.tc_prt; + List.iter (fun (_,ty) -> on_ty aenv ty) tc.tc_ops; + List.iter (fun (_,f) -> on_form aenv f) tc.tc_axs (* -------------------------------------------------------------------- *) -let on_opdecl (cb : cb) (opdecl : operator) = - on_typarams cb opdecl.op_tparams; +and on_opdecl (aenv : aenv) (opdecl : operator) = + on_typarams aenv opdecl.op_tparams; let for_kind () = match opdecl.op_kind with | OB_pred None -> () | OB_pred (Some (PR_Plain f)) -> - on_form cb f + on_form aenv f | OB_pred (Some (PR_Ind pri)) -> - on_bindings cb pri.pri_args; + on_bindings aenv pri.pri_args; List.iter (fun ctor -> - on_gbindings cb ctor.prc_bds; - List.iter (on_form cb) ctor.prc_spec) + on_gbindings aenv ctor.prc_bds; + List.iter (on_form aenv) ctor.prc_spec) pri.pri_ctors | OB_nott nott -> - List.iter (on_ty cb |- snd) nott.ont_args; - on_ty cb nott.ont_resty; - on_expr cb nott.ont_body + List.iter (on_ty aenv |- snd) nott.ont_args; + on_ty aenv nott.ont_resty; + on_expr aenv nott.ont_body | OB_oper None -> () | OB_oper Some b -> match b with - | OP_Constr _ | OP_Record _ | OP_Proj _ -> assert false - | OP_TC -> assert false - | OP_Plain f -> on_form cb f + | OP_Constr _ | OP_Record _ | OP_Proj _ | OP_TC -> () + | OP_Plain f -> on_form aenv f | OP_Fix f -> let rec on_mpath_branches br = match br with | OPB_Leaf (bds, e) -> - List.iter (on_bindings cb) bds; - on_expr cb e + List.iter (on_bindings aenv) bds; + on_expr aenv e | OPB_Branch br -> Parray.iter on_mpath_branch br @@ -418,45 +492,48 @@ let on_opdecl (cb : cb) (opdecl : operator) = in on_mpath_branches f.opf_branches - in on_ty cb opdecl.op_ty; for_kind () + in on_ty aenv opdecl.op_ty; for_kind () (* -------------------------------------------------------------------- *) -let on_axiom (cb : cb) (ax : axiom) = - on_typarams cb ax.ax_tparams; - on_form cb ax.ax_spec +and on_axiom (aenv : aenv) (ax : axiom) = + on_typarams aenv ax.ax_tparams; + on_form aenv ax.ax_spec (* -------------------------------------------------------------------- *) -let on_modsig (cb:cb) (ms:module_sig) = - List.iter (fun (_,mt) -> on_modty cb mt) ms.mis_params; +and on_modsig (aenv : aenv) (ms:module_sig) = + List.iter (fun (_,mt) -> on_modty aenv mt) ms.mis_params; List.iter (fun (Tys_function fs) -> - on_ty cb fs.fs_arg; - List.iter (fun x -> on_ty cb x.ov_type) fs.fs_anames; - on_ty cb fs.fs_ret;) ms.mis_body; - Msym.iter (fun _ oi -> on_oi cb oi) ms.mis_oinfos - -let on_ring cb r = - on_ty cb r.r_type; - let on_p p = cb (`Op p) in + on_ty aenv fs.fs_arg; + List.iter (fun x -> on_ty aenv x.ov_type) fs.fs_anames; + on_ty aenv fs.fs_ret;) ms.mis_body; + Msym.iter (fun _ oi -> on_oi aenv oi) ms.mis_oinfos + +(* -------------------------------------------------------------------- *) +and on_ring (aenv : aenv) (r : ring) = + on_ty aenv r.r_type; + let on_p p = on_opname aenv p in List.iter on_p [r.r_zero; r.r_one; r.r_add; r.r_mul]; List.iter (oiter on_p) [r.r_opp; r.r_exp; r.r_sub]; match r.r_embed with | `Direct | `Default -> () | `Embed p -> on_p p -let on_field cb f = - on_ring cb f.f_ring; - let on_p p = cb (`Op p) in +(* -------------------------------------------------------------------- *) +and on_field (aenv : aenv) (f : field) = + on_ring aenv f.f_ring; + let on_p p = on_opname aenv p in on_p f.f_inv; oiter on_p f.f_div -let on_instance cb ty tci = - on_typarams cb (fst ty); - on_ty cb (snd ty); +(* -------------------------------------------------------------------- *) +and on_instance (aenv : aenv) ty tci = + on_typarams aenv (fst ty); + on_ty aenv (snd ty); match tci with - | `Ring r -> on_ring cb r - | `Field f -> on_field cb f + | `Ring r -> on_ring aenv r + | `Field f -> on_field aenv f | `General p -> (* FIXME section: ring/field use type class that do not exists *) - cb (`Typeclass p) + aenv.cb (`Typeclass p) (* -------------------------------------------------------------------- *) type sc_name = @@ -504,15 +581,16 @@ let pp_thname scenv = (* -------------------------------------------------------------------- *) let locality (env : EcEnv.env) (who : cbarg) = match who with - | `Type p -> (EcEnv. Ty.by_path p env).tyd_loca - | `Op p -> (EcEnv. Op.by_path p env).op_loca - | `Ax p -> (EcEnv. Ax.by_path p env).ax_loca - | `Typeclass p -> ((EcEnv.TypeClass.by_path p env).tc_loca :> locality) - | `Module mp -> - begin match EcEnv.Mod.by_mpath_opt mp env with + | `Type p -> (EcEnv.Ty.by_path p env).tyd_loca + | `Op p -> (EcEnv.Op.by_path p env).op_loca + | `Ax p -> (EcEnv.Ax.by_path p env).ax_loca + | `Typeclass p -> ((EcEnv.TypeClass.by_path p env).tc_loca :> locality) + | `Module mp -> begin + match EcEnv.Mod.by_mpath_opt mp env with | Some (_, Some lc) -> lc - (* in this case it should be a quantified module *) - | _ -> `Global + | _ -> + let id = EcPath.mget_ident mp in + if EcEnv.Mod.is_declared id env then `Declare else `Global end | `ModuleType p -> ((EcEnv.ModTy.by_path p env).tms_loca :> locality) | `Instance _ -> assert false @@ -574,7 +652,7 @@ let add_declared_ty to_gen path tydecl = assert (tydecl.tyd_params = []); let s = match tydecl.tyd_type with - | `Abstract s -> s + | Abstract s -> s | _ -> assert false in let name = "'" ^ basename path in @@ -643,14 +721,14 @@ and fv_and_tvar_f f = let tydecl_fv tyd = let fv = match tyd.tyd_type with - | `Concrete ty -> ty_fv_and_tvar ty - | `Abstract _ -> Mid.empty - | `Datatype tydt -> + | Concrete ty -> ty_fv_and_tvar ty + | Abstract _ -> Mid.empty + | Datatype tydt -> List.fold_left (fun fv (_, l) -> List.fold_left (fun fv ty -> EcIdent.fv_union fv (ty_fv_and_tvar ty)) fv l) Mid.empty tydt.tydt_ctors - | `Record (_f, l) -> + | Record (_f, l) -> List.fold_left (fun fv (_, ty) -> EcIdent.fv_union fv (ty_fv_and_tvar ty)) Mid.empty l in List.fold_left (fun fv (id, _) -> Mid.remove id fv) fv tyd.tyd_params @@ -739,9 +817,9 @@ let generalize_tydecl to_gen prefix (name, tydecl) = let tosubst = fst_params, tconstr path args in let tg_subst, tyd_type = match tydecl.tyd_type with - | `Concrete _ | `Abstract _ -> + | Concrete _ | Abstract _ -> EcSubst.add_tydef to_gen.tg_subst path tosubst, tydecl.tyd_type - | `Record (f, prs) -> + | Record (f, prs) -> let subst = EcSubst.empty in let tg_subst = to_gen.tg_subst in let subst = EcSubst.add_tydef subst path tosubst in @@ -758,8 +836,8 @@ let generalize_tydecl to_gen prefix (name, tydecl) = in let prs = List.map add_op prs in let f = EcSubst.subst_form !rsubst f in - !rtg_subst, `Record (f, prs) - | `Datatype dt -> + !rtg_subst, Record (f, prs) + | Datatype dt -> let subst = EcSubst.empty in let tg_subst = to_gen.tg_subst in let subst = EcSubst.add_tydef subst path tosubst in @@ -779,7 +857,7 @@ let generalize_tydecl to_gen prefix (name, tydecl) = let tydt_ctors = List.map add_op dt.tydt_ctors in let tydt_schelim = EcSubst.subst_form !rsubst dt.tydt_schelim in let tydt_schcase = EcSubst.subst_form !rsubst dt.tydt_schcase in - !rtg_subst, `Datatype {tydt_ctors; tydt_schelim; tydt_schcase } + !rtg_subst, Datatype {tydt_ctors; tydt_schelim; tydt_schcase } in @@ -973,7 +1051,7 @@ let generalize_module to_gen prefix me = | _ -> () in try - on_mp check_gen mp; + on_mp (mkaenv to_gen.tg_env.sc_env check_gen) mp; to_gen, Some (Th_module me) with Inline -> @@ -1067,7 +1145,7 @@ let sc_decl_mod (id,mt) = SC_decl_mod (id,mt) (* ---------------------------------------------------------------- *) let is_abstract_ty = function - | `Abstract _ -> true + | Abstract _ -> true | _ -> false (* @@ -1159,27 +1237,7 @@ let check_tyd scenv prefix name tyd = d_modty = []; d_tc = [`Global]; } in - on_tydecl (cb scenv from cd) tyd - -(* -let cb_glob scenv (who:cbarg) = - match who with - | `Type p -> - if is_local scenv who then - hierror "global definition can't depend of local type %s" - (EcPath.tostring p) - | `Module mp -> - check_glob_mp scenv mp - | `Op p -> - if is_local scenv who then - hierror "global definition can't depend of local op %s" - (EcPath.tostring p) - | `ModuleType p -> - if is_local scenv who then - hierror "global definition can't depend of local module type %s" - (EcPath.tostring p) - | `Ax _ | `Typeclass _ -> assert false -*) + on_tydecl (mkaenv scenv.sc_env (cb scenv from cd)) tyd let is_abstract_op op = match op.op_kind with @@ -1205,7 +1263,7 @@ let check_op scenv prefix name op = d_modty = []; d_tc = [`Global]; } in - on_opdecl (cb scenv from cd) op + on_opdecl (mkaenv scenv.sc_env (cb scenv from cd)) op | `Global -> let cd = { @@ -1217,7 +1275,7 @@ let check_op scenv prefix name op = d_modty = []; d_tc = [`Global]; } in - on_opdecl (cb scenv from cd) op + on_opdecl (mkaenv scenv.sc_env (cb scenv from cd)) op let is_inth scenv = match scenv.sc_name with @@ -1236,7 +1294,7 @@ let check_ax (scenv : scenv) (prefix : path) (name : symbol) (ax : axiom) = d_modty = [`Global]; d_tc = [`Global]; } in - let doit = on_axiom (cb scenv from cd) in + let doit = on_axiom (mkaenv scenv.sc_env (cb scenv from cd)) in let error b s1 s = if b then hierror "%s %a %s" s1 (pp_axname scenv) path s in @@ -1268,7 +1326,7 @@ let check_modtype scenv prefix name ms = | `Local -> check_section scenv from | `Global -> if scenv.sc_insec then - on_modsig (cb scenv from cd_glob) ms.tms_sig + on_modsig (mkaenv scenv.sc_env (cb scenv from cd_glob)) ms.tms_sig let check_module scenv prefix tme = @@ -1278,17 +1336,19 @@ let check_module scenv prefix tme = match tme.tme_loca with | `Local -> check_section scenv from | `Global -> - if scenv.sc_insec then + if scenv.sc_insec then begin + let isalias = EcModules.is_me_body_alias tme.tme_expr.me_body in let cd = { d_ty = [`Global]; d_op = [`Global]; d_ax = []; d_sc = []; - d_mod = [`Global]; (* FIXME section: add local *) + d_mod = [`Global] @ (if isalias then [`Declare] else []); d_modty = [`Global]; d_tc = [`Global]; } in - on_module (cb scenv from cd) me + on_module (mkaenv scenv.sc_env (cb scenv from cd)) me + end | `Declare -> (* Should be SC_decl_mod ... *) assert false @@ -1297,7 +1357,7 @@ let check_typeclass scenv prefix name tc = let from = ((tc.tc_loca :> locality), `Typeclass path) in if tc.tc_loca = `Local then check_section scenv from else - on_typeclass (cb scenv from cd_glob) tc + on_typeclass (mkaenv scenv.sc_env (cb scenv from cd_glob)) tc let check_instance scenv ty tci lc = let from = (lc :> locality), `Instance tci in @@ -1305,10 +1365,11 @@ let check_instance scenv ty tci lc = else if scenv.sc_insec then match tci with - | `Ring _ | `Field _ -> on_instance (cb scenv from cd_glob) ty tci + | `Ring _ | `Field _ -> + on_instance (mkaenv scenv.sc_env (cb scenv from cd_glob) )ty tci | `General _ -> let cd = { cd_glob with d_ty = [`Declare; `Global]; } in - on_instance (cb scenv from cd) ty tci + on_instance (mkaenv scenv.sc_env (cb scenv from cd)) ty tci (* -----------------------------------------------------------*) let enter_theory (name:symbol) (lc:is_local) (mode:thmode) scenv : scenv = @@ -1443,7 +1504,7 @@ let genenv_of_scenv (scenv : scenv) : to_gen = ; tg_params = [] ; tg_binds = [] ; tg_subst = EcSubst.empty - ; tg_clear = empty_locals } + ; tg_clear = empty_locals } let generalize_lc_items scenv = let togen = @@ -1452,7 +1513,7 @@ let generalize_lc_items scenv = (EcEnv.root scenv.sc_env) (List.rev scenv.sc_items) in togen.tg_env - + (* -----------------------------------------------------------*) let import p scenv = { scenv with sc_env = EcEnv.Theory.import p scenv.sc_env } @@ -1527,7 +1588,7 @@ let add_decl_mod id mt scenv = d_tc = [`Global]; } in let from = `Declare, `Module (mpath_abs id []) in - on_mty_mr (cb scenv from cd) mt; + on_mty_mr (mkaenv scenv.sc_env (cb scenv from cd)) mt; { scenv with sc_env = EcEnv.Mod.declare_local id mt scenv.sc_env; sc_items = SC_decl_mod (id, mt) :: scenv.sc_items } diff --git a/src/ecSmt.ml b/src/ecSmt.ml index 84ccddd80..c6119dff3 100644 --- a/src/ecSmt.ml +++ b/src/ecSmt.ml @@ -400,16 +400,16 @@ and trans_tydecl genv (p, tydecl) = let ts, opts, decl = match tydecl.tyd_type with - | `Abstract _ -> + | Abstract _ -> let ts = WTy.create_tysymbol pid tparams WTy.NoDef in (ts, [], WDecl.create_ty_decl ts) - | `Concrete ty -> + | Concrete ty -> let ty = trans_ty (genv, lenv) ty in let ts = WTy.create_tysymbol pid tparams (WTy.Alias ty) in (ts, [], WDecl.create_ty_decl ts) - | `Datatype dt -> + | Datatype dt -> let ncs = List.length dt.tydt_ctors in let ts = WTy.create_tysymbol pid tparams WTy.NoDef in @@ -429,7 +429,7 @@ and trans_tydecl genv (p, tydecl) = (ts, opts, WDecl.create_data_decl [ts, wdtype]) - | `Record (_, rc) -> + | Record (_, rc) -> let ts = WTy.create_tysymbol pid tparams WTy.NoDef in Hp.add genv.te_ty p ts; diff --git a/src/ecSubst.ml b/src/ecSubst.ml index 45bdb2f74..33274e240 100644 --- a/src/ecSubst.ml +++ b/src/ecSubst.ml @@ -841,21 +841,21 @@ let subst_genty (s : subst) (tparams, ty) = (* -------------------------------------------------------------------- *) let subst_tydecl_body (s : subst) (tyd : ty_body) = match tyd with - | `Abstract tc -> - `Abstract (subst_typeclass s tc) + | Abstract tc -> + Abstract (subst_typeclass s tc) - | `Concrete ty -> - `Concrete (subst_ty s ty) + | Concrete ty -> + Concrete (subst_ty s ty) - | `Datatype dtype -> + | Datatype dtype -> let dtype = { tydt_ctors = List.map (snd_map (List.map (subst_ty s))) dtype.tydt_ctors; tydt_schelim = subst_form s dtype.tydt_schelim; tydt_schcase = subst_form s dtype.tydt_schcase; } - in `Datatype dtype + in Datatype dtype - | `Record (scheme, fields) -> - `Record (subst_form s scheme, List.map (snd_map (subst_ty s)) fields) + | Record (scheme, fields) -> + Record (subst_form s scheme, List.map (snd_map (subst_ty s)) fields) (* -------------------------------------------------------------------- *) let subst_tydecl (s : subst) (tyd : tydecl) = diff --git a/src/ecSymbols.ml b/src/ecSymbols.ml index e1e37313f..9b2ee1cc6 100644 --- a/src/ecSymbols.ml +++ b/src/ecSymbols.ml @@ -87,3 +87,11 @@ let rec string_of_msymbol (mx : msymbol) = let pp_msymbol fmt x = Format.fprintf fmt "%s" (string_of_msymbol x) + +(* -------------------------------------------------------------------- *) +let qsymbol_of_string (s : string) : qsymbol = + let sspl = String.split_on_char '.' s in + match List.rev sspl with + | [] -> raise (invalid_arg "EcSymbols.qsymbol_of_string") + | [x] -> ([], x) + | x :: xs -> (List.rev xs, x) diff --git a/src/ecSymbols.mli b/src/ecSymbols.mli index a761df52f..b42cb35e9 100644 --- a/src/ecSymbols.mli +++ b/src/ecSymbols.mli @@ -32,3 +32,5 @@ val pp_qsymbol : Format.formatter -> qsymbol -> unit val pp_msymbol : Format.formatter -> msymbol -> unit val string_of_qsymbol : qsymbol -> string + +val qsymbol_of_string : string -> qsymbol \ No newline at end of file diff --git a/src/ecTerminal.ml b/src/ecTerminal.ml index 7ed63fd89..94f7c048e 100644 --- a/src/ecTerminal.ml +++ b/src/ecTerminal.ml @@ -15,7 +15,7 @@ type loglevel = EcGState.loglevel class type terminal = object method interactive : bool - method next : EcParsetree.prog + method next : string * EcParsetree.prog method notice : immediate:bool -> loglevel -> string -> unit method finish : status -> unit method finalize : unit @@ -70,7 +70,7 @@ object(self) end; Format.printf "[%d|%s]>\n%!" (EcCommands.uuid ()) (EcCommands.mode ()); - EcIo.parse iparser + EcIo.xparse iparser method notice ~(immediate:bool) (lvl : loglevel) (msg : string) = match immediate with @@ -116,7 +116,7 @@ object method next = Format.printf "[%d|%s]>\n%!" (EcCommands.uuid ()) (EcCommands.mode ()); EcIo.drain iparser; - EcIo.parse iparser + EcIo.xparse iparser method notice ~(immediate:bool) (_ : loglevel) (msg : string) = ignore immediate; @@ -271,8 +271,8 @@ class from_channel method interactive = false method next = - let aout = EcIo.parse iparser in - loc <- aout.LC.pl_loc; + let aout = EcIo.xparse iparser in + loc <- (snd aout).LC.pl_loc; self#_update_progress; aout method notice ~immediate lvl msg = diff --git a/src/ecTerminal.mli b/src/ecTerminal.mli index f18cee1ac..0a96a56d2 100644 --- a/src/ecTerminal.mli +++ b/src/ecTerminal.mli @@ -10,7 +10,7 @@ type loglevel = EcGState.loglevel (* -------------------------------------------------------------------- *) val interactive : terminal -> bool -val next : terminal -> EcParsetree.prog +val next : terminal -> string * EcParsetree.prog val notice : immediate:bool -> loglevel -> string -> terminal -> unit val finish : status -> terminal -> unit val finalize : terminal -> unit diff --git a/src/ecThCloning.ml b/src/ecThCloning.ml index a2f24e593..2731d928b 100644 --- a/src/ecThCloning.ml +++ b/src/ecThCloning.ml @@ -71,7 +71,6 @@ type evclone = { evc_ops : (xop_override located) Msym.t; evc_preds : (xpr_override located) Msym.t; evc_abbrevs : (nt_override located) Msym.t; - evc_modexprs : (me_override located) Msym.t; evc_modtypes : (mt_override located) Msym.t; evc_lemmas : evlemma; evc_ths : (evclone * bool) Msym.t; @@ -93,7 +92,6 @@ let evc_empty = evc_ops = Msym.empty; evc_preds = Msym.empty; evc_abbrevs = Msym.empty; - evc_modexprs = Msym.empty; evc_modtypes = Msym.empty; evc_lemmas = evl; evc_ths = Msym.empty; } diff --git a/src/ecThCloning.mli b/src/ecThCloning.mli index a135698ba..82e160cfa 100644 --- a/src/ecThCloning.mli +++ b/src/ecThCloning.mli @@ -57,7 +57,6 @@ type evclone = { evc_ops : (xop_override located) Msym.t; evc_preds : (xpr_override located) Msym.t; evc_abbrevs : (nt_override located) Msym.t; - evc_modexprs : (me_override located) Msym.t; evc_modtypes : (mt_override located) Msym.t; evc_lemmas : evlemma; evc_ths : (evclone * bool) Msym.t; diff --git a/src/ecTheoryReplay.ml b/src/ecTheoryReplay.ml index 0eff5cf66..a6af17b16 100644 --- a/src/ecTheoryReplay.ml +++ b/src/ecTheoryReplay.ml @@ -159,16 +159,16 @@ end = struct let rec tybody (hyps : EcEnv.LDecl.hyps) (ty_body1 : ty_body) (ty_body2 : ty_body) = match ty_body1, ty_body2 with - | `Abstract _ , `Abstract _ -> () (* FIXME Sp.t *) - | `Concrete ty1 , `Concrete ty2 -> check (EcReduction.EqTest.for_type (toenv hyps) ty1 ty2) - | `Datatype ty1 , `Datatype ty2 -> for_datatype hyps ty1 ty2 - | `Record rec1, `Record rec2 -> for_record hyps rec1 rec2 + | Abstract _ , Abstract _ -> () (* FIXME Sp.t *) + | Concrete ty1 , Concrete ty2 -> check (EcReduction.EqTest.for_type (toenv hyps) ty1 ty2) + | Datatype ty1 , Datatype ty2 -> for_datatype hyps ty1 ty2 + | Record rec1, Record rec2 -> for_record hyps rec1 rec2 - | _, `Concrete { ty_node = Tconstr (p, tys) } -> + | _, Concrete { ty_node = Tconstr (p, tys) } -> let ty_body2 = get_open_tydecl (toenv hyps) p tys in tybody hyps ty_body1 ty_body2 - | `Concrete{ ty_node = Tconstr (p, tys) }, _ -> + | Concrete{ ty_node = Tconstr (p, tys) }, _ -> let ty_body1 = get_open_tydecl (toenv hyps) p tys in tybody hyps ty_body1 ty_body2 @@ -187,7 +187,7 @@ end = struct let hyps = EcEnv.LDecl.init env params in match ty_body1, ty_body2 with - | `Abstract _, _ -> () (* FIXME Sp.t *) + | Abstract _, _ -> () (* FIXME Sp.t *) | _, _ -> tybody hyps ty_body1 ty_body2 with CoreIncompatible -> raise (Incompatible TyBody) @@ -429,7 +429,7 @@ let rec replay_tyd (ove : _ ovrenv) (subst, ops, proofs, scope) (import, x, otyd let ntyd = EcTyping.transty EcTyping.tp_tydecl env ue ntyd in let decl = { tyd_params = nargs; - tyd_type = `Concrete ntyd; + tyd_type = Concrete ntyd; tyd_loca = otyd.tyd_loca; } in (decl, ntyd) @@ -439,7 +439,7 @@ let rec replay_tyd (ove : _ ovrenv) (subst, ops, proofs, scope) (import, x, otyd | Some reftyd -> let tyargs = List.map (fun (x, _) -> EcTypes.tvar x) reftyd.tyd_params in let body = tconstr p tyargs in - let decl = { reftyd with tyd_type = `Concrete body; } in + let decl = { reftyd with tyd_type = Concrete body; } in (decl, body) | _ -> assert false @@ -449,7 +449,7 @@ let rec replay_tyd (ove : _ ovrenv) (subst, ops, proofs, scope) (import, x, otyd assert (List.is_empty otyd.tyd_params); let decl = { tyd_params = []; - tyd_type = `Concrete ty; + tyd_type = Concrete ty; tyd_loca = otyd.tyd_loca; } in (decl, ty) @@ -469,9 +469,9 @@ let rec replay_tyd (ove : _ ovrenv) (subst, ops, proofs, scope) (import, x, otyd let subst = (* FIXME: HACK *) match otyd.tyd_type, body.ty_node with - | `Datatype { tydt_ctors = octors }, Tconstr (np, _) -> begin + | Datatype { tydt_ctors = octors }, Tconstr (np, _) -> begin match (EcEnv.Ty.by_path np env).tyd_type with - | `Datatype { tydt_ctors = _ } -> + | Datatype { tydt_ctors = _ } -> let newtparams = List.fst newtyd.tyd_params in let newtparams_ty = List.map tvar newtparams in let newdtype = tconstr np newtparams_ty in @@ -868,54 +868,11 @@ and replay_modtype and replay_mod (ove : _ ovrenv) (subst, ops, proofs, scope) (import, (me : top_module_expr)) = - match Msym.find_opt me.tme_expr.me_name ove.ovre_ovrd.evc_modexprs with - | None -> - let subst, name = rename ove subst (`Module, me.tme_expr.me_name) in - let me = EcSubst.subst_top_module subst me in - let me = { me with tme_expr = { me.tme_expr with me_name = name } } in - let item = (Th_module me) in - (subst, ops, proofs, ove.ovre_hooks.hadd_item scope ~import item) - - | Some { pl_desc = (newname, mode) } -> - let name = me.tme_expr.me_name in - let env = EcSection.env (ove.ovre_hooks.henv scope) in - - let mp, (newme, newlc) = EcEnv.Mod.lookup (unloc newname) env in - - let substme = EcSubst.add_moddef subst ~src:(xpath ove name) ~dst:mp in - - let me = EcSubst.subst_top_module substme me in - let me = { me with tme_expr = { me.tme_expr with me_name = name } } in - let newme = { newme with me_name = name } in - let newme = { tme_expr = newme; tme_loca = Option.get newlc; } in - - if not (EcReduction.EqTest.for_mexpr ~body:false env me.tme_expr newme.tme_expr) then - clone_error env (CE_ModIncompatible (snd ove.ovre_prefix, name)); - - let subst = - match mode with - | `Alias -> - fst (rename ove subst (`Module, name)) - | `Inline _ -> - substme in - - let newme = - if mode = `Alias || mode = `Inline `Keep then - let alias = ME_Alias ( - List.length newme.tme_expr.me_params, - EcPath.m_apply - mp - (List.map (fun (id, _) -> EcPath.mident id) newme.tme_expr.me_params) - ) - in { newme with tme_expr = { newme.tme_expr with me_body = alias } } - else newme in - - let scope = - if keep_of_mode mode - then ove.ovre_hooks.hadd_item scope ~import (Th_module newme) - else scope in - - (subst, ops, proofs, scope) + let subst, name = rename ove subst (`Module, me.tme_expr.me_name) in + let me = EcSubst.subst_top_module subst me in + let me = { me with tme_expr = { me.tme_expr with me_name = name } } in + let item = (Th_module me) in + (subst, ops, proofs, ove.ovre_hooks.hadd_item scope ~import item) (* -------------------------------------------------------------------- *) and replay_export diff --git a/src/ecTypes.ml b/src/ecTypes.ml index 87efc57be..bebd2087e 100644 --- a/src/ecTypes.ml +++ b/src/ecTypes.ml @@ -154,6 +154,12 @@ let rec ty_check_uni t = | Tunivar _ -> raise FoundUnivar | _ -> ty_iter ty_check_uni t +let rec var_mem ?(check_glob = false) id t = + match t.ty_node with + | Tvar id' -> EcIdent.id_equal id id' + | Tglob id' when check_glob -> EcIdent.id_equal id id' + | _ -> ty_sub_exists (var_mem ~check_glob id) t + (* -------------------------------------------------------------------- *) let symbol_of_ty (ty : ty) = match ty.ty_node with diff --git a/src/ecTypes.mli b/src/ecTypes.mli index 34b7b4cbf..95ee26bb3 100644 --- a/src/ecTypes.mli +++ b/src/ecTypes.mli @@ -79,6 +79,8 @@ val ty_sub_exists : (ty -> bool) -> ty -> bool val ty_fold : ('a -> ty -> 'a) -> 'a -> ty -> 'a val ty_iter : (ty -> unit) -> ty -> unit +val var_mem : ?check_glob:bool -> EcIdent.t -> ty -> bool + (* -------------------------------------------------------------------- *) val symbol_of_ty : ty -> string val fresh_id_of_ty : ty -> EcIdent.t diff --git a/src/ecTyping.ml b/src/ecTyping.ml index 75f594a10..6af2f28e3 100644 --- a/src/ecTyping.ml +++ b/src/ecTyping.ml @@ -2720,7 +2720,7 @@ and transinstr match (EcEnv.ty_hnorm ety env).ty_node with | Tconstr (indp, _) -> begin match EcEnv.Ty.by_path indp env with - | { tyd_type = `Datatype dt } -> + | { tyd_type = Datatype dt } -> Some (indp, dt) | _ -> None end @@ -3329,7 +3329,7 @@ and trans_form_or_pattern env mode ?mv ?ps ue pf tt = match (EcEnv.ty_hnorm cfty env).ty_node with | Tconstr (indp, _) -> begin match EcEnv.Ty.by_path indp env with - | { tyd_type = `Datatype dt } -> + | { tyd_type = Datatype dt } -> Some (indp, dt) | _ -> None end diff --git a/src/ecUnify.ml b/src/ecUnify.ml index e5bb56299..4664a8a71 100644 --- a/src/ecUnify.ml +++ b/src/ecUnify.ml @@ -14,7 +14,7 @@ module TC = EcTypeClass (* -------------------------------------------------------------------- *) exception UnificationFailure of [`TyUni of ty * ty | `TcCtt of ty * Sp.t] -exception UninstanciateUni +exception UninstantiateUni (* -------------------------------------------------------------------- *) type pb = [ `TyUni of ty * ty | `TcCtt of ty * Sp.t ] @@ -376,7 +376,7 @@ module UniEnv = struct UF.closed (!ue).ue_uf let close (ue : unienv) = - if not (closed ue) then raise UninstanciateUni; + if not (closed ue) then raise UninstantiateUni; (subst_of_uf (!ue).ue_uf) let assubst ue = subst_of_uf (!ue).ue_uf diff --git a/src/ecUnify.mli b/src/ecUnify.mli index 90488fabc..1f6ed3e45 100644 --- a/src/ecUnify.mli +++ b/src/ecUnify.mli @@ -7,7 +7,7 @@ open EcDecl (* -------------------------------------------------------------------- *) exception UnificationFailure of [`TyUni of ty * ty | `TcCtt of ty * Sp.t] -exception UninstanciateUni +exception UninstantiateUni type unienv diff --git a/src/ecUserMessages.ml b/src/ecUserMessages.ml index 9c947c1b6..249cfb520 100644 --- a/src/ecUserMessages.ml +++ b/src/ecUserMessages.ml @@ -573,6 +573,7 @@ module InductiveError : sig val pp_fxerror : env -> Format.formatter -> fxerror -> unit end = struct open EcHiInductive + open EcInductive open TypingError let pp_rcerror env fmt error = @@ -591,8 +592,38 @@ end = struct | RCE_Empty -> msg "this record type is empty" + let format_intype fmt p (tyvar, ctx) = + (match ctx with + | Concrete -> Format.fprintf fmt "... in type %s" p + | Record s -> Format.fprintf fmt "... in record field %s of type %s" s p + | Variant s -> Format.fprintf fmt "... in variant %s of type %s" s p); + let subty tyvar = + Format.fprintf fmt " (in an instance of type variable %a)" + EcIdent.pp_ident tyvar + in + Option.iter subty tyvar + +let format_context pp fmt (p, ctx) = match ctx with + | InType (tyvar, ctx) -> format_intype fmt p (tyvar, ctx) + | NonPositiveOcc ty -> + Format.fprintf fmt "non-positive occurrence of %s in type %a" + p (EcPrinting.pp_type pp) ty + | AbstractTypeRestriction -> + Format.fprintf fmt "unauthorised abstract type constructor %s" p + | TypePositionRestriction ty -> + Format.fprintf fmt + "recursive occurrence %a in the definition of %s has different \ + arguments, which is not allowed." + (EcPrinting.pp_type pp) ty p + +let format_context_list p l pp fmt = + Format.fprintf fmt "Could not verify strict positivity of type %s:@.@;<0 2>@[" p; + Format.pp_print_list (format_context pp) fmt l; + Format.fprintf fmt "@;@]" + let pp_dterror env fmt error = let msg x = Format.fprintf fmt x in + let env1 = EcPrinting.PPEnv.ofenv env in match error with | DTE_TypeError ee -> @@ -605,12 +636,11 @@ end = struct msg "invalid constructor type: `%s`: %a'" name (pp_tyerror env) ee - | DTE_NonPositive -> - msg "the datatype does not respect the positivity condition" - | DTE_Empty -> msg "the datatype may be empty" + | DTE_NonPositive (s, ctx) -> format_context_list s ctx env1 fmt + let pp_fxerror env fmt error = match error with | FXLowError ee -> diff --git a/src/phl/ecPhlApp.ml b/src/phl/ecPhlApp.ml index d63286429..2d2ea618b 100644 --- a/src/phl/ecPhlApp.ml +++ b/src/phl/ecPhlApp.ml @@ -26,12 +26,13 @@ let t_hoare_app_r i phi tc = let t_hoare_app = FApi.t_low2 "hoare-app" t_hoare_app_r (* -------------------------------------------------------------------- *) -let t_ehoare_app_r i f tc = +let t_ehoare_app_r i phi tc = let env = FApi.tc1_env tc in let hs = tc1_as_ehoareS tc in let s1, s2 = s_split env i hs.ehs_s in - let a = f_eHoareS (snd hs.ehs_m) (ehs_pr hs) (stmt s1) f in - let b = f_eHoareS (snd hs.ehs_m) f (stmt s2) (ehs_po hs) in + let phi = ss_inv_rebind phi (fst hs.ehs_m) in + let a = f_eHoareS (snd hs.ehs_m) (ehs_pr hs) (stmt s1) phi in + let b = f_eHoareS (snd hs.ehs_m) phi (stmt s2) (ehs_po hs) in FApi.xmutate1 tc `HlApp [a; b] let t_ehoare_app = FApi.t_low2 "hoare-app" t_ehoare_app_r @@ -67,7 +68,7 @@ let t_bdhoare_app_r_low i (phi, pR, f1, f2, g1, g2) tc = let (ir1, ir2) = EcIdent.create "r", EcIdent.create "r" in let (r1 , r2 ) = f_local ir1 treal, f_local ir2 treal in let condnm = - let eqs = map_ss_inv2 f_and (map_ss_inv1 ((EcUtils.flip f_eq) r1) f2) + let eqs = map_ss_inv2 f_and (map_ss_inv1 ((EcUtils.flip f_eq) r1) f2) (map_ss_inv1 ((EcUtils.flip f_eq) r2) g2) in f_forall [(ir1, GTty treal); (ir2, GTty treal)] @@ -125,12 +126,12 @@ let t_equiv_app_onesided side i pre post tc = let s, s', p', q' = match side with | `Left -> - let p' = ss_inv_generalize_right (EcSubst.ss_inv_rebind pre ml) mr in - let q' = ss_inv_generalize_right (EcSubst.ss_inv_rebind post ml) mr in + let p' = ss_inv_generalize_as_left pre ml mr in + let q' = ss_inv_generalize_as_left post ml mr in es.es_sl, es.es_sr, p', q' | `Right -> - let p' = ss_inv_generalize_left (EcSubst.ss_inv_rebind pre mr) ml in - let q' = ss_inv_generalize_left (EcSubst.ss_inv_rebind post mr) ml in + let p' = ss_inv_generalize_as_right pre ml mr in + let q' = ss_inv_generalize_as_right post ml mr in es.es_sr, es.es_sl, p', q' in let generalize_mod_side= sideif side generalize_mod_left generalize_mod_right in @@ -227,13 +228,13 @@ let process_app (side, dir, k, phi, bd_info) tc = | Single i, PAppNone when is_hoareS concl -> check_side side; let _, phi = TTC.tc1_process_Xhl_formula tc (get_single phi) in - let i = EcProofTyping.tc1_process_codepos1 tc (side, i) in + let i = EcLowPhlGoal.tc1_process_codepos1 tc (side, i) in t_hoare_app i phi tc | Single i, PAppNone when is_eHoareS concl -> check_side side; let _, phi = TTC.tc1_process_Xhl_formula_xreal tc (get_single phi) in - let i = EcProofTyping.tc1_process_codepos1 tc (side, i) in + let i = EcLowPhlGoal.tc1_process_codepos1 tc (side, i) in t_ehoare_app i phi tc | Single i, PAppNone when is_equivS concl -> @@ -248,21 +249,21 @@ let process_app (side, dir, k, phi, bd_info) tc = match side with | None -> tc_error !!tc "seq onsided: side information expected" | Some side -> side in - let i = EcProofTyping.tc1_process_codepos1 tc (Some side, i) in + let i = EcLowPhlGoal.tc1_process_codepos1 tc (Some side, i) in t_equiv_app_onesided side i pre post tc | Single i, _ when is_bdHoareS concl -> check_side side; let _, pia = TTC.tc1_process_Xhl_formula tc (get_single phi) in let (ra, f1, f2, f3, f4) = process_phl_bd_info dir bd_info tc in - let i = EcProofTyping.tc1_process_codepos1 tc (side, i) in + let i = EcLowPhlGoal.tc1_process_codepos1 tc (side, i) in t_bdhoare_app i (ra, pia, f1, f2, f3, f4) tc | Double (i, j), PAppNone when is_equivS concl -> check_side side; let phi = TTC.tc1_process_prhl_formula tc (get_single phi) in - let i = EcProofTyping.tc1_process_codepos1 tc (Some `Left, i) in - let j = EcProofTyping.tc1_process_codepos1 tc (Some `Left, j) in + let i = EcLowPhlGoal.tc1_process_codepos1 tc (Some `Left, i) in + let j = EcLowPhlGoal.tc1_process_codepos1 tc (Some `Left, j) in t_equiv_app (i, j) phi tc | Single _, PAppNone diff --git a/src/phl/ecPhlCall.ml b/src/phl/ecPhlCall.ml index 298e0588d..8231b9f27 100644 --- a/src/phl/ecPhlCall.ml +++ b/src/phl/ecPhlCall.ml @@ -152,7 +152,7 @@ let t_ehoare_call fpre fpost tc = let t_ehoare_call_concave f fpre fpost tc = let _, _, _, s, _, wppre, wppost = ehoare_call_pre_post fpre fpost tc in let tcenv = - EcPhlApp.t_ehoare_app (EcMatching.Zipper.cpos (List.length s.s_node)) + EcPhlApp.t_ehoare_app (EcMatching.Zipper.cpos (List.length s.s_node)) (map_ss_inv2 (fun wppre f -> f_app_simpl f [wppre] txreal) wppre f) tc in let tcenv = FApi.t_swap_goals 0 1 tcenv in let t_call = @@ -219,7 +219,7 @@ let t_bdhoare_call fpre fpost opt_bd tc = let post = map_ss_inv2 f_anda_simpl (map_ss_inv1 (PVM.subst env spre) fpre) post in (* most of the above code is duplicated from t_hoare_call *) - let concl = + let concl = let _,mt = bhs.bhs_m in match bhs.bhs_cmp, opt_bd with | FHle, None -> @@ -325,8 +325,8 @@ let call_error env tc f1 f2 = let t_call side ax tc = let env = FApi.tc1_env tc in - let concl = FApi.tc1_goal tc in - + let hyps, concl = FApi.tc1_flat tc in + let ax = EcReduction.h_red_until EcReduction.full_red hyps ax in match ax.f_node, concl.f_node with | FhoareF hf, FhoareS hs -> let (_, f, _), _ = tc1_last_call tc hs.hs_s in @@ -418,7 +418,7 @@ let process_call side info tc = let m = (EcIdent.create "&hr") in let penv, qenv = LDecl.hoareF m f hyps in let pre = TTC.pf_process_form !!tc penv tbool pre in - let post = TTC.pf_process_form !!tc qenv tbool post in + let post = TTC.pf_process_form !!tc qenv tbool post in f_hoareF {m;inv=pre} f {m;inv=post} | FbdHoareS bhs, None -> @@ -435,7 +435,7 @@ let process_call side info tc = let m = (EcIdent.create "&hr") in let penv, qenv = LDecl.hoareF m f hyps in let pre = TTC.pf_process_form !!tc penv txreal pre in - let post = TTC.pf_process_form !!tc qenv txreal post in + let post = TTC.pf_process_form !!tc qenv txreal post in f_eHoareF {m;inv=pre} f {m;inv=post} | FbdHoareS _, Some _ @@ -448,7 +448,7 @@ let process_call side info tc = let (ml, mr) = (EcIdent.create "&1", EcIdent.create "&2") in let penv, qenv = LDecl.equivF ml mr fl fr hyps in let pre = TTC.pf_process_form !!tc penv tbool pre in - let post = TTC.pf_process_form !!tc qenv tbool post in + let post = TTC.pf_process_form !!tc qenv tbool post in f_equivF {ml;mr;inv=pre} fl fr {ml;mr;inv=post} | FequivS es, Some side -> diff --git a/src/phl/ecPhlCodeTx.ml b/src/phl/ecPhlCodeTx.ml index 0cc9e48b4..f3ec2c513 100644 --- a/src/phl/ecPhlCodeTx.ml +++ b/src/phl/ecPhlCodeTx.ml @@ -236,11 +236,11 @@ let cfold_stmt ?(simplify = true) (pf, hyps) (me : memenv) (olen : int option) ( | e, _ -> [e] in let lv = lv_to_ty_list lv in - + let tosubst, asgn2 = List.partition (fun ((pv, _), e) -> Mpv.mem env pv subst0 && is_const_expression e ) (List.combine lv es) in - + let subst = List.fold_left (fun subst ((pv, _), e) -> Mpv.add env pv e subst) @@ -342,24 +342,24 @@ let t_cfold = FApi.t_low3 "code-tx-cfold" t_cfold_r (* -------------------------------------------------------------------- *) let process_cfold (side, cpos, olen) tc = - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_cfold side cpos olen tc let process_kill (side, cpos, len) tc = - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_kill side cpos len tc let process_alias (side, cpos, id) tc = - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_alias side cpos id tc let process_set (side, cpos, fresh, id, e) tc = let e = TTC.tc1_process_Xhl_exp tc side None e in - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_set side cpos (fresh, id) e tc let process_set_match (side, cpos, id, pattern) tc = - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in let me, _ = tc1_get_stmt side tc in let hyps = LDecl.push_active_ss me (FApi.tc1_hyps tc) in let ue = EcProofTyping.unienv_of_hyps hyps in @@ -368,7 +368,7 @@ let process_set_match (side, cpos, id, pattern) tc = t_set_match side cpos (EcLocation.unloc id) (ue, EcMatching.MEV.of_idents (Mid.keys !ptnmap) `Form, pattern) tc - + (* -------------------------------------------------------------------- *) let process_weakmem (side, id, params) tc = let open EcLocation in @@ -459,7 +459,7 @@ let process_case ((side, pos) : side option * pcodepos) (tc : tcenv1) = assert false; let _, s = EcLowPhlGoal.tc1_get_stmt side tc in - let pos = EcProofTyping.tc1_process_codepos tc (side, pos) in + let pos = EcLowPhlGoal.tc1_process_codepos tc (side, pos) in let goals, s = EcMatching.Zipper.map env pos change s in let concl = EcLowPhlGoal.hl_set_stmt side concl s in diff --git a/src/phl/ecPhlConseq.ml b/src/phl/ecPhlConseq.ml index 90a07c86f..97bdfb2df 100644 --- a/src/phl/ecPhlConseq.ml +++ b/src/phl/ecPhlConseq.ml @@ -453,6 +453,9 @@ let t_bdHoareS_conseq_nm = gen_conseq_nm t_bdHoareS_notmod t_bdHoareS_conseq let t_ehoareF_concave (fc: ss_inv) pre post tc = let env = FApi.tc1_env tc in let hf = tc1_as_ehoareF tc in + let pre = ss_inv_rebind pre hf.ehf_m in + let post = ss_inv_rebind post hf.ehf_m in + let fc = ss_inv_rebind fc hf.ehf_m in let f = hf.ehf_f in let mpr,mpo = Fun.hoareF_memenv hf.ehf_m f env in let fsig = (Fun.by_xpath f env).f_sig in @@ -491,6 +494,9 @@ let t_ehoareS_concave (fc: ss_inv) (* xreal -> xreal *) pre post tc = let hs = tc1_as_ehoareS tc in let s = hs.ehs_s in let m = fst hs.ehs_m in + let pre = ss_inv_rebind pre m in + let post = ss_inv_rebind post m in + let fc = ss_inv_rebind fc m in (* ensure that f only depend of notmod *) let modi = s_write env s in let fv = PV.fv env m fc.inv in @@ -719,10 +725,10 @@ let t_equivS_conseq_conj pre1 post1 pre2 post2 pre' post' tc = let (_, hyps, _) = FApi.tc1_eflat tc in let es = tc1_as_equivS tc in let (ml, mtl), (mr, mtr) = es.es_ml, es.es_mr in - let pre1' = ss_inv_generalize_right (ss_inv_rebind pre1 ml) mr in - let post1' = ss_inv_generalize_right (ss_inv_rebind post1 ml) mr in - let pre2' = ss_inv_generalize_left (ss_inv_rebind pre2 mr) ml in - let post2' = ss_inv_generalize_left (ss_inv_rebind post2 mr) ml in + let pre1' = ss_inv_generalize_as_left pre1 ml mr in + let post1' = ss_inv_generalize_as_left post1 ml mr in + let pre2' = ss_inv_generalize_as_right pre2 ml mr in + let post2' = ss_inv_generalize_as_right post2 ml mr in if not (ts_inv_alpha_eq hyps (es_pr es) (map_ts_inv f_ands [pre';pre1';pre2'])) then tc_error !!tc "invalid pre-condition"; if not (ts_inv_alpha_eq hyps (es_po es) (map_ts_inv f_ands [post';post1';post2'])) then @@ -737,15 +743,17 @@ let t_equivF_conseq_conj pre1 post1 pre2 post2 pre' post' tc = let (_, hyps, _) = FApi.tc1_eflat tc in let ef = tc1_as_equivF tc in let ml, mr = ef.ef_ml, ef.ef_mr in - let pre1' = ss_inv_generalize_right (ss_inv_rebind pre1 ml) mr in - let post1' = ss_inv_generalize_right (ss_inv_rebind post1 ml) mr in - let pre2' = ss_inv_generalize_left (ss_inv_rebind pre2 mr) ml in - let post2' = ss_inv_generalize_left (ss_inv_rebind post2 mr) ml in - let pre'' = map_ts_inv f_ands [pre'; pre1'; pre2'] in - let post'' = map_ts_inv f_ands [post'; post1'; post2'] in - if not (ts_inv_alpha_eq hyps (ef_pr ef) pre'') + let pre1' = ss_inv_generalize_as_left pre1 ml mr in + let post1' = ss_inv_generalize_as_left post1 ml mr in + let pre2' = ss_inv_generalize_as_right pre2 ml mr in + let post2' = ss_inv_generalize_as_right post2 ml mr in + let pre'' = ts_inv_rebind pre' ml mr in + let pre_and = map_ts_inv f_ands [pre''; pre1'; pre2'] in + let post'' = ts_inv_rebind post' ml mr in + let post_and = map_ts_inv f_ands [post''; post1'; post2'] in + if not (ts_inv_alpha_eq hyps (ef_pr ef) pre_and) then tc_error !!tc "invalid pre-condition"; - if not (ts_inv_alpha_eq hyps (ef_po ef) post'') + if not (ts_inv_alpha_eq hyps (ef_po ef) post_and) then tc_error !!tc "invalid post-condition"; let concl1 = f_hoareF pre1 ef.ef_fl post1 in let concl2 = f_hoareF pre2 ef.ef_fr post2 in @@ -760,12 +768,12 @@ let t_equivS_conseq_bd side pr po tc = let m,s,s',prs,pos = match side with | `Left -> - let pos = ss_inv_generalize_right (ss_inv_rebind po ml) mr in - let prs = ss_inv_generalize_right (ss_inv_rebind pr ml) mr in + let pos = ss_inv_generalize_as_left po ml mr in + let prs = ss_inv_generalize_as_left pr ml mr in es.es_ml, es.es_sl, es.es_sr, prs, pos | `Right -> - let pos = ss_inv_generalize_left (ss_inv_rebind po mr) ml in - let prs = ss_inv_generalize_left (ss_inv_rebind pr mr) ml in + let pos = ss_inv_generalize_as_right po ml mr in + let prs = ss_inv_generalize_as_right pr ml mr in es.es_mr, es.es_sr, es.es_sl, prs, pos in if not (List.is_empty s'.s_node) then begin @@ -785,26 +793,35 @@ let t_equivS_conseq_bd side pr po tc = (* -------------------------------------------------------------------- *) (* -(forall m1, P1 m1 => exists m2, P m1 m2 /\ P2 m2) +(forall m1, P1 m1 => exists m2, P m1 m2 /\ P2 m2 /\ q m1 = p m2) (forall m1 m2, Q m1 m2 => Q2 m2 => Q1 m1) -equiv M1 ~ M2 : P ==> Q hoare M2 : P2 ==> Q2. +equiv M1 ~ M2 : P ==> Q phoare M2 : P2 ==> Q2 R p. ----------------------------------------------- -hoare M1 : P1 ==> Q1. +phoare M1 : P1 ==> Q1 R q. *) -let transitivity_side_cond hyps prml poml pomr p q p2 q2 p1 q1 = +let transitivity_side_cond ?bds hyps prml poml pomr p q p2 q2 p1 q1 = let env = LDecl.toenv hyps in let cond1 = let fv1 = PV.fv env p.mr p.inv in let fv2 = PV.fv env p2.m p2.inv in let fv = PV.union fv1 fv2 in + let fv = match bds with + | Some (_, bd2) -> + let fvbd2 = PV.fv env bd2.m bd2.inv in + PV.union fv fvbd2 + | None -> fv in let elts, glob = PV.ntr_elements fv in let bd, s = generalize_subst env p2.m elts glob in let s1 = PVM.of_mpv s p.mr in let s2 = PVM.of_mpv s p2.m in - let concl = f_and (PVM.subst env s1 p.inv) (PVM.subst env s2 p2.inv) in - let p1 = ss_inv_rebind p1 p.ml in - f_forall_mems [prml] (f_imp p1.inv (f_exists bd concl)) in + let concl = {m=p1.m; inv=f_and (PVM.subst env s1 p.inv) (PVM.subst env s2 p2.inv)} in + let concl = match bds with + | Some (bd1, bd2) -> + let sbd = PVM.of_mpv s bd2.m in + map_ss_inv2 f_and concl (map_ss_inv1 (fun bd1 -> f_eq bd1 (PVM.subst env sbd bd2.inv)) bd1) + | None -> concl in + f_forall_mems_ss_inv prml (map_ss_inv2 f_imp p1 (map_ss_inv1 (f_exists bd) concl)) in let cond2 = let q1 = ss_inv_generalize_as_left q1 q.ml q.mr in let q2 = ss_inv_generalize_as_right q2 q.ml q.mr in @@ -821,14 +838,14 @@ let t_hoareF_conseq_equiv f2 p q p2 q2 tc = transitivity_side_cond hyps prml poml pomr p q p2 q2 (hf_pr hf1) (hf_po hf1) in FApi.xmutate1 tc `HoareFConseqEquiv [cond1; cond2; ef; hf2] -let t_bdHoareF_conseq_equiv f2 p q p2 q2 tc = +let t_bdHoareF_conseq_equiv f2 p q p2 q2 bd2 tc = let env, hyps, _ = FApi.tc1_eflat tc in let hf1 = tc1_as_bdhoareF tc in let ef = f_equivF p hf1.bhf_f f2 q in - let hf2 = f_bdHoareF p2 f2 q2 hf1.bhf_cmp (bhf_bd hf1) in + let hf2 = f_bdHoareF p2 f2 q2 hf1.bhf_cmp bd2 in let (prml, _prmr), (poml, pomr) = Fun.equivF_memenv p.ml p.mr hf1.bhf_f f2 env in let (cond1, cond2) = - transitivity_side_cond hyps prml poml pomr p q p2 q2 (bhf_pr hf1) (bhf_po hf1) in + transitivity_side_cond ~bds:(bhf_bd hf1, bd2) hyps prml poml pomr p q p2 q2 (bhf_pr hf1) (bhf_po hf1) in FApi.xmutate1 tc `BdHoareFConseqEquiv [cond1; cond2; ef; hf2] @@ -1120,7 +1137,7 @@ let rec t_hi_conseq notmod f1 f2 f3 tc = t_intros_i [m;h0] @! t_cutdef (ptlocal ~args:[pamemory m; palocal h0] hi) mpre @! EcLowGoal.t_trivial; - t_mytrivial; + t_mytrivial @! t_intros_i [m; h0] @! t_apply_hyp h0; t_apply_hyp hh]; tac pre posta @+ [ t_apply_hyp hi; @@ -1152,7 +1169,7 @@ let rec t_hi_conseq notmod f1 f2 f3 tc = let hf2 = pf_as_bdhoareF !!tc f2 in FApi.t_seqsub (t_bdHoareF_conseq_equiv hf2.bhf_f (ef_pr ef) (ef_po ef) - (bhf_pr hf2) (bhf_po hf2)) + (bhf_pr hf2) (bhf_po hf2) (bhf_bd hf2)) [t_id; t_id; t_apply_r nef; t_apply_r nf2] tc (* ------------------------------------------------------------------ *) @@ -1171,10 +1188,10 @@ let rec t_hi_conseq notmod f1 f2 f3 tc = let hs2 = pf_as_hoareS !!tc f2 in let hs3 = pf_as_hoareS !!tc f3 in let (ml, mr) = (fst es.es_ml, fst es.es_mr) in - let hs2_pr = ss_inv_generalize_right (ss_inv_rebind (hs_pr hs2) ml) mr in - let hs2_po = ss_inv_generalize_right (ss_inv_rebind (hs_po hs2) ml) mr in - let hs3_pr = ss_inv_generalize_left (ss_inv_rebind (hs_pr hs3) mr) ml in - let hs3_po = ss_inv_generalize_left (ss_inv_rebind (hs_po hs3) mr) ml in + let hs2_pr = ss_inv_generalize_as_left (hs_pr hs2) ml mr in + let hs2_po = ss_inv_generalize_as_left (hs_po hs2) ml mr in + let hs3_pr = ss_inv_generalize_as_right (hs_pr hs3) ml mr in + let hs3_po = ss_inv_generalize_as_right (hs_po hs3) ml mr in let pre = map_ts_inv f_ands [es_pr es; hs2_pr; hs3_pr] in let post = map_ts_inv f_ands [es_po es; hs2_po; hs3_po] in let tac = if notmod then t_equivS_conseq_nm else t_equivS_conseq in @@ -1229,8 +1246,8 @@ let rec t_hi_conseq notmod f1 f2 f3 tc = | FequivS es, None, Some ((_, f2) as nf2), None -> let hs = pf_as_bdhoareS !!tc f2 in let (ml, mr) = (fst es.es_ml, fst es.es_mr) in - let pre = ss_inv_generalize_right (ss_inv_rebind (bhs_pr hs) ml) mr in - let post = ss_inv_generalize_right (ss_inv_rebind (bhs_po hs) ml) mr in + let pre = ss_inv_generalize_as_left (bhs_pr hs) ml mr in + let post = ss_inv_generalize_as_left (bhs_po hs) ml mr in let tac = if notmod then t_equivS_conseq_nm else t_equivS_conseq in check_is_detbound `Second (bhs_bd hs).inv; @@ -1247,8 +1264,8 @@ let rec t_hi_conseq notmod f1 f2 f3 tc = | FequivS es, None, None, Some ((_, f3) as nf3) -> let hs = pf_as_bdhoareS !!tc f3 in let (ml, mr) = (fst es.es_ml, fst es.es_mr) in - let pre = ss_inv_generalize_left (ss_inv_rebind (bhs_pr hs) mr) ml in - let post = ss_inv_generalize_left (ss_inv_rebind (bhs_po hs) mr) ml in + let pre = ss_inv_generalize_as_right (bhs_pr hs) ml mr in + let post = ss_inv_generalize_as_right (bhs_po hs) ml mr in let tac = if notmod then t_equivS_conseq_nm else t_equivS_conseq in check_is_detbound `Third (bhs_bd hs).inv; @@ -1276,11 +1293,11 @@ let rec t_hi_conseq notmod f1 f2 f3 tc = let hs2 = pf_as_hoareF !!tc f2 in let hs3 = pf_as_hoareF !!tc f3 in let (ml, mr) = (ef.ef_ml, ef.ef_mr) in - let hs2_pr = ss_inv_generalize_right (ss_inv_rebind (hf_pr hs2) ml) mr in - let hs3_pr = ss_inv_generalize_left (ss_inv_rebind (hf_pr hs3) mr) ml in + let hs2_pr = ss_inv_generalize_as_left (hf_pr hs2) ml mr in + let hs3_pr = ss_inv_generalize_as_right (hf_pr hs3) ml mr in let pre = map_ts_inv f_ands [ef_pr ef; hs2_pr; hs3_pr] in - let hs2_po = ss_inv_generalize_right (ss_inv_rebind (hf_po hs2) ml) mr in - let hs3_po = ss_inv_generalize_left (ss_inv_rebind (hf_po hs3) mr) ml in + let hs2_po = ss_inv_generalize_as_left (hf_po hs2) ml mr in + let hs3_po = ss_inv_generalize_as_right (hf_po hs3) ml mr in let post = map_ts_inv f_ands [ef_po ef; hs2_po; hs3_po] in let tac = if notmod then t_equivF_conseq_nm else t_equivF_conseq in t_on1seq 2 diff --git a/src/phl/ecPhlDeno.ml b/src/phl/ecPhlDeno.ml index 0a6ce9af2..544e6f308 100644 --- a/src/phl/ecPhlDeno.ml +++ b/src/phl/ecPhlDeno.ml @@ -86,6 +86,8 @@ let t_phoare_deno_r pre post tc = (* -------------------------------------------------------------------- *) let t_ehoare_deno_r pre post tc = + let m = pre.m in + assert (m = post.m); let env, _, concl = FApi.tc1_eflat tc in let f, bd = @@ -99,16 +101,17 @@ let t_ehoare_deno_r pre post tc = let pr = destr_pr f in let concl_e = f_eHoareF pre pr.pr_fun post in - let mpr, mpo = EcEnv.Fun.hoareF_memenv pr.pr_mem pr.pr_fun env in + let _, mpo = EcEnv.Fun.hoareF_memenv m pr.pr_fun env in (* pre <= bd *) (* building the substitution for the pre *) - let sargs = PVM.add env pv_arg (fst mpr) pr.pr_args PVM.empty in - let smem = Fsubst.f_bind_mem Fsubst.f_subst_id (fst mpr) pr.pr_mem in + let sargs = PVM.add env pv_arg m pr.pr_args PVM.empty in + let smem = Fsubst.f_bind_mem Fsubst.f_subst_id m pr.pr_mem in let pre = Fsubst.f_subst smem (PVM.subst env sargs pre.inv) in let concl_pr = f_xreal_le pre (f_r2xr bd) in (* forall m, ev%r%xr <= post *) let ev = pr.pr_event in + let ev = ss_inv_rebind ev m in let concl_po = map_ss_inv2 f_xreal_le (map_ss_inv1 f_b2xr ev) post in let concl_po = f_forall_mems_ss_inv mpo concl_po in diff --git a/src/phl/ecPhlEager.ml b/src/phl/ecPhlEager.ml index a99092c82..e4f23a790 100644 --- a/src/phl/ecPhlEager.ml +++ b/src/phl/ecPhlEager.ml @@ -1,299 +1,358 @@ -(* -------------------------------------------------------------------- *) -open EcUtils -open EcLocation open EcAst -open EcTypes -open EcModules -open EcFol -open EcEnv -open EcPV - open EcCoreGoal +open EcEnv +open EcFol open EcLowGoal open EcLowPhlGoal - -module ER = EcReduction -module PT = EcProofTerm +open EcMatching.Zipper +open EcModules +open EcPV +open EcTypes +open EcUtils +module ER = EcReduction +module PT = EcProofTerm module TTC = EcProofTyping -(* -------------------------------------------------------------------- *) -let pf_destr_eqobsS pf env f = - let es = destr_equivS f in - let of_form = - try Mpv2.of_form env - with Not_found -> tc_error pf "cannot reconize a set of equalities" +(** Builds a formula that represents equality on the list of variables [l] + between two memories [ml] and [mr] *) +let list_eq_to_form ml mr (l, l_glob) = + let to_form m = List.map (fun (pv, ty) -> (f_pvar pv ty m).inv) in + let to_form_glob m = + List.map (fun x -> (f_glob (EcPath.mget_ident x) m).inv) in - (es, es.es_sl, es.es_sr, of_form (es_pr es), of_form (es_po es)) - -(* -------------------------------------------------------------------- *) -let pf_hSS pf hyps h = - let tH = LDecl.hyp_by_id h hyps in - (tH, pf_destr_eqobsS pf (LDecl.toenv hyps) tH) - -(* -------------------------------------------------------------------- *) -let tc1_destr_eagerS tc s s' = - let env = FApi.tc1_env tc in - let es = tc1_as_equivS tc in - let c , c' = es.es_sl, es.es_sr in - let s1, c = s_split env (Zpr.cpos (List.length s.s_node)) c in - let c',s1' = s_split env (Zpr.cpos (List.length c'.s_node - List.length s'.s_node)) c' in - - if not (List.all2 i_equal s1 s.s_node) then begin - let ppe = EcPrinting.PPEnv.ofenv (FApi.tc1_env tc) in + { + ml; + mr; + inv = + f_eqs + (to_form ml l @ to_form_glob ml l_glob) + (to_form mr l @ to_form_glob mr l_glob); + } + +(** Returns a formula that describes equality on all variables from one side of + the memory present in the formula [q]. + + Example: If [q] is [(a{ml} \/ b{m'} /\ c{ml})], (with [ml] the first bound + memory, [mr] the second and [m'] another memory, distinct from [ml]) then + this function returns [(a{ml} = a{mr} /\ c{ml} = c{mr})]. The result of this + operation is sometimes denoted [={q.m1}]. *) +let eq_on_sided_form env { ml; mr; inv } = + PV.fv env ml inv |> PV.elements |> list_eq_to_form ml mr + +(** Returns a formula that describes equality on all variables from both + memories in predicate [inv], as well as equality on all variables read from + [c]. + + This is used to implement what is denoted [Eq] in the module documentation, + i.e. equality on the whole memory. *) +let eq_on_form_and_stmt env { ml; mr; inv } c = + s_read env c + |> PV.union (PV.fv env ml inv) + |> PV.union (PV.fv env mr inv) + |> PV.elements |> list_eq_to_form ml mr + +(** Equality on all variables from a function [f] *) +let eq_on_fun env m1 m2 f = + let l, l' = NormMp.flatten_use (NormMp.fun_use env f) in + let l_glob = List.map EcPath.mident l in + let l_pv = List.map (fun (x, ty) -> (pv_glob x, ty)) l' in + list_eq_to_form m1 m2 (l_pv, l_glob) + +(** Given a goal environment [tc] and a statement [s], if the goal is an + equivalence of the shape [s; c ~ c'; s], returns the same equivalence goal, + as well as the terms c and c'. + + Yields an error if the statements are not of the right form. *) +let destruct_eager tc s = + let env = FApi.tc1_env tc + and es = tc1_as_equivS tc + and ss = List.length s.s_node in + + let c, c' = (es.es_sl, es.es_sr) in + let z, c = s_split env (Zpr.cpos ss) c + and c', z' = s_split env (Zpr.cpos (List.length c'.s_node - ss)) c' in + + let env, _, _ = FApi.tc1_eflat tc in + let z_eq_s = ER.EqTest.for_stmt env (stmt z) s + and z'_eq_s = ER.EqTest.for_stmt env (stmt z') s in + + if z_eq_s && z'_eq_s then (es, stmt c, stmt c') + else + let err_stmt, prefix = + if z_eq_s then (z', "tail of the right") else (z, "head of the left") + and ppe = EcPrinting.PPEnv.ofenv (FApi.tc1_env tc) in tc_error_lazy !!tc (fun fmt -> - Format.fprintf fmt - "the head of the left statement is not of the right form:@\n%a should be@\n%a" - (EcPrinting.pp_stmt ppe) (stmt s1) (EcPrinting.pp_stmt ppe) s) - end; - - if not (List.all2 i_equal s1' s'.s_node) then begin - let ppe = EcPrinting.PPEnv.ofenv (FApi.tc1_env tc) in - tc_error_lazy !!tc (fun fmt -> - Format.fprintf fmt - "the tail of the right statement is not of the right form:@\n%a should be@\n%a" - (EcPrinting.pp_stmt ppe) (stmt s1') (EcPrinting.pp_stmt ppe) s') - end; - - (es, stmt c, stmt c') - -(* -------------------------------------------------------------------- *) -(* This ensure condition (d) and (e) of the eager_seq rule. *) -let pf_compat pf env modS modS' eqR eqIs eqXs = - if not (Mpv2.subset eqIs eqR) then begin - let ml, mr = EcIdent.create "&1_dummy", EcIdent.create "&2_dummy" in - let f_true = {ml; mr; inv=f_true} in - let eqR = Mpv2.to_form_ts_inv eqR f_true in - let eqIs = Mpv2.to_form_ts_inv eqIs f_true in - tc_error_lazy pf (fun fmt -> - let ppe = EcPrinting.PPEnv.ofenv env in - Format.fprintf fmt "%a should be included in %a" - (EcPrinting.pp_form ppe) eqIs.inv (EcPrinting.pp_form ppe) eqR.inv) - end; - - let check_pv x1 x2 _ = - if not (Mpv2.mem x1 x2 eqXs) - && (PV.mem_pv env x1 modS || PV.mem_pv env x2 modS') - then - tc_error_lazy pf (fun fmt -> - let ppe = EcPrinting.PPEnv.ofenv env in Format.fprintf fmt - "equality of %a and %a should be ensured by the swapping statement" - (EcPrinting.pp_pv ppe) x1 (EcPrinting.pp_pv ppe) x2) + "eager: the %s statement is not of the right form:@\n\ + %a should be@\n\ + %a" + prefix (EcPrinting.pp_stmt ppe) (stmt err_stmt) + (EcPrinting.pp_stmt ppe) s) + +(** Given a goal environment with a current goal of the shape [s; op ~ op'; s], + returns the triplet [(es, s, op, op')]. Yields an error if the goal doesn't + have the right shape *) +let destruct_on_op id_op tc = + let env = FApi.tc1_env tc and es = tc1_as_equivS tc in + let s = + try + let s, _ = split_at_cpos1 env (-1, `ByMatch (None, id_op)) es.es_sl + (* ensure the right statement also contains an [id_op]: *) + and _, _ = split_at_cpos1 env (1, `ByMatch (None, id_op)) es.es_sr in + s + with InvalidCPos -> + tc_error_lazy !!tc (fun fmt -> + Format.fprintf fmt "eager: invalid pivot statement") + in - and check_glob m = - if not (Mpv2.mem_glob m eqXs) - && (PV.mem_glob env m modS || PV.mem_glob env m modS') - then + if List.is_empty s then + tc_error_lazy !!tc (fun fmt -> + Format.fprintf fmt "eager: empty swapping statement"); + + let es, c1, c2 = destruct_eager tc (stmt s) in + match (c1.s_node, c2.s_node) with + | [ i1 ], [ i2 ] -> (es, stmt s, i1, i2) + | _, _ -> + let verb, side = + if List.length c1.s_node = 1 then ("precede", "right") + else ("follow", "left") + in + tc_error_lazy !!tc (fun fmt -> + Format.fprintf fmt + "eager: no statements may %s the %s pivot statement." verb side) + +let rec match_eq tc m1 m2 t1 t2 = + match (t1.f_node, t2.f_node) with + | Fpvar (p1, m1_), Fpvar (p2, m2_) -> + ((m1 = m1_ && m2 = m2_) || (m1 = m2_ && m2 = m1_)) && p1 = p2 + | Fglob (p1, m1_), Fglob (p2, m2_) -> + ((m1 = m1_ && m2 = m2_) || (m1 = m2_ && m2 = m1_)) && p1 = p2 + | Ftuple l1, Ftuple l2 -> List.for_all2 (match_eq tc m1 m2) l1 l2 + | _ -> false + +(** Ensure that a given proposition is a conjunction of same-name variables + equalities between two given memories. + + This test is of course a bit conservative but should be sufficient for all + the use cases it covers *) +let rec ensure_eq_shape tc m1 m2 q = + match q.f_node with + | Fapp (_, [ q1; q2 ]) when is_and q -> + ensure_eq_shape tc m1 m2 q1 && ensure_eq_shape tc m1 m2 q2 + | Fapp (_, [ t1; t2 ]) when is_eq q -> match_eq tc m1 m2 t1 t2 + | _ -> is_true q + +(** Ensure the swapping statement [s] only interacts with global variables. *) +let check_only_global pf env s = + let sw = s_write env s + and sr = s_read env s + and check_mp _ = () + and check_glob v _ = + if is_loc v then tc_error_lazy pf (fun fmt -> - let ppe = EcPrinting.PPEnv.ofenv env in - Format.fprintf fmt - "equality of %a should be ensured by the swapping statement" - (EcPrinting.pp_topmod ppe) m) - + let ppe = EcPrinting.PPEnv.ofenv env in + Format.fprintf fmt + "eager: swapping statement may use only global variables: %a" + (EcPrinting.pp_pv ppe) v) in - Mpv2.iter check_pv check_glob eqR + PV.iter check_glob check_mp sw; + PV.iter check_glob check_mp sr (* -------------------------------------------------------------------- *) -let t_eager_seq_r i j eqR h tc = - let env, hyps, _ = FApi.tc1_eflat tc in - - (* h is a proof of (h) *) - let tH, (_, s, s', eqIs, eqXs) = pf_hSS !!tc hyps h in - let eC, c, c' = tc1_destr_eagerS tc s s' in - let seqR = Mpv2.of_form env eqR in - - (* check (d) and (e) *) - pf_compat !!tc env (s_write env s) (s_write env s') seqR eqIs eqXs; - - let eqO2 = Mpv2.eq_refl (PV.fv env (fst eC.es_mr) (es_po eC).inv) in - let c1 ,c2 = s_split env i c in - let c1',c2' = s_split env j c' in - - let to_form eq = Mpv2.to_form_ts_inv eq {ml=(fst eC.es_ml); mr=(fst eC.es_mr); inv=f_true} in - - let a = f_equivS (snd eC.es_ml) (snd eC.es_mr) (es_pr eC) (stmt (s.s_node@c1)) (stmt (c1'@s'.s_node)) eqR - and b = f_equivS (snd eC.es_ml) (snd eC.es_mr) eqR (stmt (s.s_node@c2)) (stmt (c2'@s'.s_node)) (es_po eC) - and c = f_equivS (snd eC.es_mr) (snd eC.es_mr) (to_form (Mpv2.eq_fv2 seqR)) - (stmt c2') (stmt c2') (to_form eqO2) in - - FApi.t_first - (t_apply_hyp h) - (FApi.xmutate1 tc `EagerSeq [tH; a; b; c]) +(* Internal variants of eager tactics *) + +let t_eager_seq_r (i, j) s (r2, r1) tc = + let env, _, _ = FApi.tc1_eflat tc and eC, c, c' = destruct_eager tc s in + + let (_, ml_ty), (_, mr_ty) = (eC.es_ml, eC.es_mr) in + let c1, c2 = s_split env i c and c1', c2' = s_split env j c' in + let eqMem1 = eq_on_form_and_stmt env r1 (stmt c1') + and eqQ1 = eq_on_sided_form env (es_po eC) in + + let a = + f_equivS ml_ty mr_ty (es_pr eC) + (stmt (s.s_node @ c1)) + (stmt (c1' @ s.s_node)) + r2 + and b = + f_equivS ml_ty mr_ty r1 + (stmt (s.s_node @ c2)) + (stmt (c2' @ s.s_node)) + (es_po eC) + and c = f_equivS mr_ty mr_ty eqMem1 (stmt c1') (stmt c1') r1 + and d = f_equivS ml_ty ml_ty r2 (stmt c2) (stmt c2) eqQ1 in + FApi.xmutate1 tc `EagerSeq [ a; b; c; d ] (* -------------------------------------------------------------------- *) let t_eager_if_r tc = - let es = tc1_as_equivS tc in - let ml, mr = fst es.es_ml, fst es.es_mr in - - let (e , c1 , c2 ), s = pf_last_if !!tc es.es_sl in - let (e', c1', c2'), s' = pf_first_if !!tc es.es_sr in + let es, s, c, c' = destruct_on_op `If tc in + let e, c1, c2 = destr_if c and e', c1', c2' = destr_if c' in - let fel = ss_inv_generalize_right (ss_inv_of_expr ml e) mr in - let fer = ss_inv_generalize_left (ss_inv_of_expr mr e') ml in + let { ml; mr; inv = pr_inv } = es_pr es in + let { es_ml = _, ml_ty; es_mr = _, mr_ty } = es in + let fe = (ss_inv_of_expr ml e).inv and fe' = (ss_inv_of_expr mr e').inv in let aT = - EcSubst.f_forall_mems_ts_inv es.es_ml es.es_mr - (map_ts_inv2 f_imp (es_pr es) (map_ts_inv2 f_eq fel fer)) in + f_forall + [ (ml, GTmem ml_ty); (mr, GTmem mr_ty) ] + (f_imp pr_inv (f_eq fe fe')) + in let bT = - let b = EcIdent.create "b1" in - let fe = ss_inv_generalize_right (ss_inv_of_expr ml e) mr in - let eqb = map_ts_inv2 f_eq fe {ml;mr;inv=f_local b tbool} in - - EcSubst.f_forall_mems_ss_inv es.es_mr - (map_ss_inv1 - (f_forall [(b, GTty tbool)]) - (ts_inv_lower_left2 (fun pr po -> f_hoareS (snd es.es_ml) pr s po) (map_ts_inv2 f_and (es_pr es) eqb) eqb)) in + let b = EcIdent.create "b" in + let eqb = f_eq fe (f_local b tbool) in + let pre = { m = ml; inv = f_and pr_inv eqb } in + let post = { m = ml; inv = eqb } in + f_forall [ (mr, GTmem mr_ty); (b, GTty tbool) ] (f_hoareS ml_ty pre s post) + in let cT = - let pre = map_ts_inv2 f_and (es_pr es) (map_ts_inv2 f_eq fel {ml;mr;inv=f_true}) in - let st = stmt (s.s_node @ c1.s_node) in - let st' = stmt (c1'.s_node @ s'.s_node) in - f_equivS (snd es.es_ml) (snd es.es_mr) pre st st' (es_po es) in + let pre = { ml; mr; inv = f_and pr_inv (f_eq fe f_true) } in + let st = stmt (s.s_node @ c1.s_node) in + let st' = stmt (c1'.s_node @ s.s_node) in + f_equivS ml_ty mr_ty pre st st' (es_po es) + in let dT = - let pre = map_ts_inv2 f_and (es_pr es) (map_ts_inv2 f_eq fel {ml;mr;inv=f_false}) in - let st = stmt (s.s_node @ c2.s_node) in - let st' = stmt (c2'.s_node @ s'.s_node) in - f_equivS (snd es.es_ml) (snd es.es_mr) pre st st' (es_po es) in + let pre = { ml; mr; inv = f_and pr_inv (f_eq fe f_false) } in + let st = stmt (s.s_node @ c2.s_node) in + let st' = stmt (c2'.s_node @ s.s_node) in + f_equivS ml_ty mr_ty pre st st' (es_po es) + in - FApi.xmutate1 tc `EagerIf [aT; bT; cT; dT] + FApi.xmutate1 tc `EagerIf [ aT; bT; cT; dT ] (* -------------------------------------------------------------------- *) -let t_eager_while_r h tc = - let env, hyps, _ = FApi.tc1_eflat tc in +let t_eager_while_r i tc = + let env, _, _ = FApi.tc1_eflat tc in - let tH, (_, s, s', eqIs, eqXs) = pf_hSS !!tc hyps h in - let eC, wc, wc' = tc1_destr_eagerS tc s s' in - let ml, mr = fst eC.es_ml, fst eC.es_mr in + let es, s, w, w' = destruct_on_op `While tc in + let e, c = destr_while w and _e, c' = destr_while w' in - let (e , c ), n = pf_first_while !!tc wc in - let (e', c'), n' = pf_first_while !!tc wc' in - if not (List.is_empty n.s_node && List.is_empty n'.s_node) then - tc_error !!tc "no statements should followed the while loops"; + let { ml; mr; inv = pr_inv } = es_pr es in + let { es_ml = _, ml_ty; es_mr = _, mr_ty } = es in - let to_form eq = Mpv2.to_form_ts_inv eq {ml=(fst eC.es_ml);mr=(fst eC.es_mr);inv=f_true} in - - let eqI = (es_pr eC) in - let seqI = - try - Mpv2.of_form env eqI - with Not_found -> - tc_error_lazy !!tc (fun fmt -> - let ppe = EcPrinting.PPEnv.ofenv env in - Format.fprintf fmt "recognize equalities in %a@." (EcPrinting.pp_form ppe) eqI.inv) + let sub_to_left_mem = + let open EcSubst in + subst_expr (add_memory empty mr ml) in - let eqI2 = to_form (Mpv2.eq_fv2 seqI) in - let e1 = ss_inv_generalize_right (ss_inv_of_expr ml e) mr in - let e2 = ss_inv_generalize_left (ss_inv_of_expr mr e') ml in - let post = Mpv2.to_form_ts_inv (Mpv2.union seqI eqXs) (map_ts_inv1 f_not e1) in - - (* check (e) and (f) *) - pf_compat !!tc env (s_write env s) (s_write env s') seqI eqIs eqXs; - let aT = EcSubst.f_forall_mems_ts_inv eC.es_ml eC.es_mr - (map_ts_inv2 f_imp eqI (map_ts_inv2 f_eq e1 e2)) - - and bT = f_equivS (snd eC.es_ml) (snd eC.es_mr) (map_ts_inv2 f_and_simpl eqI e1) (stmt (s.s_node@c.s_node)) - (stmt (c'.s_node@s'.s_node)) eqI + if (not (e_equal e (sub_to_left_mem _e))) then + tc_error !!tc "eager: both while guards must be syntactically equal"; - and cT = f_equivS (snd eC.es_mr) (snd eC.es_mr) eqI2 c' c' eqI2 - in + let eqMem1 = eq_on_form_and_stmt env i c' and eqI = eq_on_sided_form env i in + + let el = ss_inv_of_expr ml e and er = ss_inv_of_expr mr e in - let tsolve tc = - FApi.t_first - (t_apply_hyp h) - (FApi.xmutate1 tc `EagerWhile [tH; aT; bT; cT]) + let aT = + let and_ = f_and_simpl (f_eq el.inv er.inv) eqI.inv in + f_forall [ (ml, GTmem ml_ty); (mr, GTmem mr_ty) ] (f_imp i.inv and_) + and bT = + let pre = { ml; mr; inv = f_and i.inv el.inv } in + f_equivS ml_ty mr_ty pre + (stmt (s.s_node @ c.s_node)) + (stmt (c'.s_node @ s.s_node)) + i + and cT = + let b = EcIdent.create "b" in + let eqb = f_eq el.inv (f_local b tbool) in + let pre = { m = ml; inv = f_and pr_inv eqb } in + let post = { m = ml; inv = eqb } in + f_forall [ (mr, GTmem mr_ty); (b, GTty tbool) ] (f_hoareS ml_ty pre s post) + and dT = f_equivS ml_ty mr_ty eqMem1 c' c' i + and eT = f_equivS ml_ty mr_ty i c c i + and fT = + f_equivS ml_ty mr_ty { ml; mr; inv = f_and i.inv (f_not el.inv) } s s i in - FApi.t_seqsub - (EcPhlConseq.t_equivS_conseq eqI post) - [t_trivial; t_trivial; tsolve] - tc + FApi.xmutate1 tc `EagerWhile [ aT; bT; cT; dT; eT; fT ] (* -------------------------------------------------------------------- *) let t_eager_fun_def_r tc = let env = FApi.tc1_env tc in - let eg = tc1_as_eagerF tc in - let ml, mr = eg.eg_ml, eg.eg_mr in + let eg = tc1_as_eagerF tc in - let fl, fr = - (NormMp.norm_xfun env eg.eg_fl, - NormMp.norm_xfun env eg.eg_fr) - in + let fl, fr = (NormMp.norm_xfun env eg.eg_fl, NormMp.norm_xfun env eg.eg_fr) in EcPhlFun.check_concrete !!tc env fl; EcPhlFun.check_concrete !!tc env fr; - let (memenvl, (fsigl,fdefl), - memenvr, (fsigr,fdefr), env) = Fun.equivS ml mr fl fr env in + let memenvl, (fsigl, fdefl), memenvr, (fsigr, fdefr), env = + Fun.equivS eg.eg_ml eg.eg_mr fl fr env + in let extend mem fdef = match fdef.f_ret with - | None -> {m=fst mem;inv=f_tt}, mem, fdef.f_body + | None -> (f_tt, mem, fdef.f_body) | Some e -> - let v = { ov_name = Some "result"; ov_type = e.e_ty } in - let mem, s = EcMemory.bind_fresh v mem in - (* oget cannot fail — Some in, Some out *) - let x = EcTypes.pv_loc (oget s.ov_name) in - f_pvar x e.e_ty (fst mem), mem, - s_seq fdef.f_body (stmt [i_asgn(LvVar(x,e.e_ty), e)]) + let v = { ov_name = Some "result"; ov_type = e.e_ty } in + let mem, s = EcMemory.bind_fresh v mem in + (* oget cannot fail — Some in, Some out *) + let x = EcTypes.pv_loc (oget s.ov_name) in + ( (f_pvar x e.e_ty (fst mem)).inv, + mem, + s_seq fdef.f_body (stmt [ i_asgn (LvVar (x, e.e_ty), e) ]) ) in let el, meml, sfl = extend memenvl fdefl in let er, memr, sfr = extend memenvr fdefr in - let ml, mr = EcMemory.memory meml, EcMemory.memory memr in + let ml, mr = (EcMemory.memory meml, EcMemory.memory memr) in let s = PVM.empty in - let s = PVM.add env pv_res ml el.inv s in - let s = PVM.add env pv_res mr er.inv s in + let s = PVM.add env pv_res ml el s in + let s = PVM.add env pv_res mr er s in let post = map_ts_inv1 (PVM.subst env s) (eg_po eg) in let s = PVM.empty in let s = EcPhlFun.subst_pre env fsigl ml s in let s = EcPhlFun.subst_pre env fsigr mr s in let pre = map_ts_inv1 (PVM.subst env s) (eg_pr eg) in - let cond = f_equivS (snd meml) (snd memr) pre (s_seq eg.eg_sl sfl) (s_seq sfr eg.eg_sr) post in + let cond = + f_equivS (snd meml) (snd memr) pre (s_seq eg.eg_sl sfl) (s_seq sfr eg.eg_sr) + post + in - FApi.xmutate1 tc `EagerFunDef [cond] + FApi.xmutate1 tc `EagerFunDef [ cond ] (* -------------------------------------------------------------------- *) -let t_eager_fun_abs_r eqI h tc = - let env, hyps, _ = FApi.tc1_eflat tc in +let t_eager_fun_abs_r i tc = + let env, _, _ = FApi.tc1_eflat tc and eg = tc1_as_eagerF tc in - let tH, (_, s, s', eqIs, eqXs) = pf_hSS !!tc hyps h in - let eg = tc1_as_eagerF tc in + if not (s_equal eg.eg_sl eg.eg_sr) then + tc_error !!tc "eager: both swapping statements must be identical"; - if not (s_equal s eg.eg_sl && s_equal s' eg.eg_sr) then - tc_error !!tc "cannot reconize the swapping statement"; + if not (ensure_eq_shape tc i.ml i.mr i.inv) then + tc_error !!tc + "eager: the invariant must be a conjunction of same-name variable \ + equalities"; - let fl, fr = eg.eg_fl, eg.eg_fr in - let pre, post, sg = - EcPhlFun.FunAbsLow.equivF_abs_spec !!tc env fl fr eqI in + let s, fl, fr = (eg.eg_sl, eg.eg_fl, eg.eg_fr) in - let do1 og sg = + let pre, post, sg_e = EcPhlFun.FunAbsLow.equivF_abs_spec !!tc env fl fr i in + let _, _, sg_f = EcPhlFun.FunAbsLow.equivF_abs_spec !!tc env fr fr i in + let _, _, sg_g = EcPhlFun.FunAbsLow.equivF_abs_spec !!tc env fl fl i in + + let do_e og = let ef = destr_equivF og in - let torefl f = - Mpv2.to_form_ts_inv - (Mpv2.eq_refl (PV.fv env f.mr f.inv)) - {ml=f.ml;mr=f.mr;inv=f_true} - in - f_eagerF (ef_pr ef) s ef.ef_fl ef.ef_fr s' (ef_po ef) - :: f_equivF (torefl (ef_pr ef)) ef.ef_fr ef.ef_fr (torefl (ef_po ef)) - :: sg + f_eagerF (ef_pr ef) s ef.ef_fl ef.ef_fr s (ef_po ef) in - let sg = List.fold_right do1 sg [] in - let seqI = Mpv2.of_form env eqI in - - (* check (e) and (f)*) - pf_compat !!tc env (s_write env s) (s_write env s') seqI eqIs eqXs; + let do_f og = + let ef = destr_equivF og in - (* TODO : check that S S' do not modify glob A *) - let tactic tc = - FApi.t_first (t_apply_hyp h) - (FApi.xmutate1 tc `EagerFunAbs (tH::sg)) + let eqMem = eq_on_fun env i.ml i.mr ef.ef_fr in + f_equivF (map_ts_inv2 f_and eqMem (ef_pr ef)) ef.ef_fl ef.ef_fl (ef_po ef) in + let sg_e = List.map do_e sg_e and sg_f = List.map do_f sg_f in + + (* Reorder per-oracle goals in order to align with the description *) + let sg = + List.combine sg_e (List.combine sg_f sg_g) + |> List.concat_map (fun (x, (y, z)) -> [ x; y; z ]) + and sg_d = f_equivS EcMemory.abstract_mt EcMemory.abstract_mt i s s i in + + let tactic tc = FApi.xmutate1 tc `EagerFunAbs (sg_d :: sg) in + FApi.t_last tactic (EcPhlConseq.t_eagerF_conseq pre post tc) (* -------------------------------------------------------------------- *) @@ -303,338 +362,111 @@ let t_eager_call_r fpre fpost tc = let fpre = EcSubst.ts_inv_rebind fpre (fst es.es_ml) (fst es.es_mr) in let fpost = EcSubst.ts_inv_rebind fpost (fst es.es_ml) (fst es.es_mr) in - let (lvl, fl, argsl), sl = pf_last_call !!tc es.es_sl in + let (lvl, fl, argsl), sl = pf_last_call !!tc es.es_sl in let (lvr, fr, argsr), sr = pf_first_call !!tc es.es_sr in let swl = s_write env sl in let swr = s_write env sr in let check_a e = - let er = e_read env e in + let er = e_read env e in let diff = PV.interdep env swl er in if not (PV.is_empty diff) then tc_error_lazy !!tc (fun fmt -> - Format.fprintf fmt - "eager call: the statement write %a" - (PV.pp env) diff) + Format.fprintf fmt "eager: swapping statement may not write to `%a`" + (PV.pp env) diff) in List.iter check_a argsl; let modil = PV.union (f_write env fl) swl in let modir = PV.union (f_write env fr) swr in - let post = EcPhlCall.wp2_call env fpre fpost (lvl, fl, argsl) modil - - (lvr,fr,argsr) modir (es_po es) hyps in - let f_concl = f_eagerF fpre sl fl fr sr fpost in - let concl = f_equivS (snd es.es_ml) (snd es.es_mr) (es_pr es) (stmt []) (stmt []) post in - - FApi.xmutate1 tc `EagerCall [f_concl; concl] - -(* -------------------------------------------------------------------- *) -let check_only_global pf env s = - let sw = s_write env s in - let sr = s_read env s in - - let check_glob v _ = - if is_loc v then - tc_error_lazy pf (fun fmt -> - let ppe = EcPrinting.PPEnv.ofenv env in - Format.fprintf fmt - "swapping statement should use only global variables: %a" - (EcPrinting.pp_pv ppe) v) - in - - let check_mp _ = () in - - PV.iter check_glob check_mp sw; - PV.iter check_glob check_mp sr - -(* -------------------------------------------------------------------- *) -(* This part of the code is for automatic application of eager rules *) -(* -------------------------------------------------------------------- *) -let eager pf env s s' inv eqIs eqXs c c' eqO = - let modi = s_write env s in - let modi' = s_write env s' in - let readi = s_read env s in - - let rev st = List.rev st.s_node in - - let check_args args = - let read = List.fold_left (e_read_r env) PV.empty args in - if not (PV.indep env modi read) then raise EqObsInError in - - let check_swap_s i = - let m = is_write env [i] in - let r = is_read env [i] in - let t = - PV.indep env m modi - && PV.indep env m readi - && PV.indep env modi r - in - if not t then raise EqObsInError - in - - let remove lvl lvr eqs = - let aux eqs (pvl, tyl) (pvr, tyr) = - if (ER.EqTest.for_type env tyl tyr) - then Mpv2.remove env pvl pvr eqs - else raise EqObsInError in - - match lvl, lvr with - | LvVar xl, LvVar xr -> aux eqs xl xr - - | LvTuple ll, LvTuple lr - when List.length ll = List.length lr - -> - List.fold_left2 aux eqs ll lr - - | _, _ -> raise EqObsInError in - - let oremove lvl lvr eqs = - match lvl, lvr with - | None , None -> eqs - | Some lvl, Some lvr -> remove lvl lvr eqs - | _ , _ -> raise EqObsInError in - - let rec s_eager fhyps rsl rsr eqo = - match rsl, rsr with - | [], _ -> [], rsr, fhyps, eqo - | _ , [] -> rsl, [], fhyps, eqo - - | il::rsl', ir::rsr' -> - match (try Some (i_eager fhyps il ir eqo) with _ -> None) with - | None -> rsl, rsr, fhyps, eqo - | Some (fhyps, eqi) -> - (* we ensure that the seq rule can be apply *) - let eqi2 = i_eqobs_in_refl env ir (Mpv2.fv2 eqo) in - if not (PV.subset eqi2 (Mpv2.fv2 eqi)) then raise EqObsInError; - pf_compat pf env modi modi' eqi eqIs eqXs; - s_eager fhyps rsl' rsr' eqi - - and i_eager fhyps il ir eqo = - match il.i_node, ir.i_node with - | Sasgn (lvl, el), Sasgn (lvr, er) - | Srnd (lvl, el), Srnd (lvr, er) -> - check_swap_s il; - let eqnm = Mpv2.split_nmod env modi modi' eqo in - let eqm = Mpv2.split_mod env modi modi' eqo in - if not (Mpv2.subset eqm eqXs) then raise EqObsInError; - let eqi = Mpv2.union eqIs eqnm in - (fhyps, Mpv2.add_eqs env el er (remove lvl lvr eqi) ) - - | Scall (lvl, fl, argsl), Scall (lvr, fr, argsr) - when List.length argsl = List.length argsr - -> - check_args argsl; - let eqo = oremove lvl lvr eqo in - let modl = PV.union modi (f_write env fl) in - let modr = PV.union modi' (f_write env fr) in - let eqnm = Mpv2.split_nmod env modl modr eqo in - let outf = Mpv2.split_mod env modl modr eqo in - Mpv2.check_glob outf; - let fhyps, inf = f_eager fhyps fl fr outf in - let eqi = - List.fold_left2 - (fun eqs e1 e2 -> Mpv2.add_eqs env e1 e2 eqs) - (Mpv2.union eqnm inf) argsl argsr - in - (fhyps, eqi) - - | Sif (el, stl, sfl), Sif (er, str, sfr) -> - check_args [el]; - let r1,r2,fhyps1, eqs1 = s_eager fhyps (rev stl) (rev str) eqo in - if r1 <> [] || r2 <> [] then raise EqObsInError; - let r1,r2, fhyps2, eqs2 = s_eager fhyps1 (rev sfl) (rev sfr) eqo in - if r1 <> [] || r2 <> [] then raise EqObsInError; - let eqi = Mpv2.union eqs1 eqs2 in - let eqe = Mpv2.add_eqs env el er eqi in - (fhyps2, eqe) - - | Swhile (el, sl), Swhile (er, sr2) -> - check_args [el]; (* ensure condition (d) *) - let sl, sr = rev sl, rev sr2 in - let rec aux eqo = - let r1,r2,fhyps, eqi = s_eager fhyps sl sr eqo in - if r1 <> [] || r2 <> [] then raise EqObsInError; - if Mpv2.subset eqi eqo then fhyps, eqo - else aux (Mpv2.union eqi eqo) - in - let fhyps, eqi = aux (Mpv2.union eqIs (Mpv2.add_eqs env el er eqo)) in - (* by construction condition (a), (b) and (c) are satisfied *) - pf_compat pf env modi modi' eqi eqIs eqXs; (* ensure (e) and (f) *) - (* (h) is assumed *) - (fhyps, eqi) - - | Sassert el, Sassert er -> - check_args [el]; - let eqnm = Mpv2.split_nmod env modi modi' eqo in - let eqm = Mpv2.split_mod env modi modi' eqo in - if not (Mpv2.subset eqm eqXs) then raise EqObsInError; - let eqi = Mpv2.union eqIs eqnm in - (fhyps, Mpv2.add_eqs env el er eqi) - - | Sabstract _, Sabstract _ -> assert false (* FIXME *) - - | _, _ -> raise EqObsInError - - and f_eager fhyps fl fr out = - let fl = NormMp.norm_xfun env fl in - let fr = NormMp.norm_xfun env fr in - - let rec aux fhyps = - match fhyps with - | [] -> [fl,fr,out] - | (fl', fr', out') :: fhyps -> - if EcPath.x_equal fl fl' && EcPath.x_equal fr fr' - then (fl ,fr , Mpv2.union out out') :: fhyps - else (fl',fr', out') :: (aux fhyps) - in - aux fhyps, inv - in - - s_eager [] (rev c) (rev c') eqO - -(* -------------------------------------------------------------------- *) -let t_eager_r h inv tc = - let env, hyps, _ = FApi.tc1_eflat tc in - let _, (_, s, s', eqIs, eqXs) = pf_hSS !!tc hyps h in - - check_only_global !!tc env s; - check_only_global !!tc env s'; - - let eC, c, c' = tc1_destr_eagerS tc s s' in - let ml, mr = fst eC.es_ml, fst eC.es_mr in - let eqinv = Mpv2.of_form env inv in - let eqO = Mpv2.of_form env (es_po eC) in - let c1, c1', fhyps, eqi = eager !!tc env s s' eqinv eqIs eqXs c c' eqO in - - if c1 <> [] || c1' <> [] then - tc_error !!tc "not able to apply eager"; (* FIXME *) - - let dof (fl,fr,eqo) = - let defl = Fun.by_xpath fl env in - let defr = Fun.by_xpath fr env in - let sigl, sigr = defl.f_sig, defr.f_sig in - let eq_res = ts_inv_eqres sigl.fs_ret ml sigr.fs_ret mr in - let post = Mpv2.to_form_ts_inv eqo eq_res in - let eq_params = - ts_inv_eqparams - sigl.fs_arg sigl.fs_anames ml - sigr.fs_arg sigr.fs_anames mr in - let pre = map_ts_inv2 f_and_simpl eq_params inv in - f_eagerF pre s fl fr s' post + let post = + EcPhlCall.wp2_call env fpre fpost (lvl, fl, argsl) modil (lvr, fr, argsr) + modir (es_po es) hyps in - + let f_concl = f_eagerF fpre sl fl fr sr fpost in let concl = - f_equivS (snd eC.es_ml) (snd eC.es_mr) (es_pr eC) (stmt []) (stmt []) - (Mpv2.to_form_ts_inv eqi {ml;mr;inv=f_true}) in - - let concls = List.map dof fhyps in + f_equivS (snd es.es_ml) (snd es.es_mr) (es_pr es) (stmt []) (stmt []) post + in - FApi.xmutate1 tc `EagerAuto (concl::concls) + FApi.xmutate1 tc `EagerCall [ f_concl; concl ] (* -------------------------------------------------------------------- *) -let t_eager_seq = FApi.t_low4 "eager-seq" t_eager_seq_r -let t_eager_if = FApi.t_low0 "eager-if" t_eager_if_r -let t_eager_while = FApi.t_low1 "eager-while" t_eager_while_r +let t_eager_seq = FApi.t_low3 "eager-seq" t_eager_seq_r +let t_eager_if = FApi.t_low0 "eager-if" t_eager_if_r +let t_eager_while = FApi.t_low1 "eager-while" t_eager_while_r let t_eager_fun_def = FApi.t_low0 "eager-fun-def" t_eager_fun_def_r -let t_eager_fun_abs = FApi.t_low2 "eager-fun-abs" t_eager_fun_abs_r -let t_eager_call = FApi.t_low2 "eager-call" t_eager_call_r -let t_eager = FApi.t_low2 "eager" t_eager_r +let t_eager_fun_abs = FApi.t_low1 "eager-fun-abs" t_eager_fun_abs_r +let t_eager_call = FApi.t_low2 "eager-call" t_eager_call_r (* -------------------------------------------------------------------- *) -let process_info info tc = - let hyps = FApi.tc1_hyps tc in - - match info with - | EcParsetree.LE_done h -> - (t_id tc, fst (LDecl.hyp_by_name (unloc h) hyps)) - - | EcParsetree.LE_todo (h, s1, s2, eqIs, eqXs) -> - let (ml, mlt) as mle, ((mr, mrt) as mre) = - match (FApi.tc1_goal tc).f_node with - | FeagerF {eg_ml=ml;eg_mr=mr} -> - EcMemory.abstract ml, EcMemory.abstract mr - | _ -> - let es = tc1_as_equivS tc in - es.es_ml, es.es_mr in - let hyps = LDecl.push_active_ts mle mre hyps in - let process_formula = TTC.pf_process_form !!tc hyps tbool in - let eqIs = {ml;mr;inv=process_formula eqIs} in - let eqXs = {ml;mr;inv=process_formula eqXs} in - let s1 = TTC.tc1_process_prhl_stmt tc `Left s1 in - let s2 = TTC.tc1_process_prhl_stmt tc `Right s2 in - let f = f_equivS mlt mrt eqIs s1 s2 eqXs in - let h = LDecl.fresh_id hyps (unloc h) in - (FApi.t_last (t_intros_i [h]) (t_cut f tc), h) - -(* -------------------------------------------------------------------- *) -let process_seq info (i, j) eqR tc = - let eqR = TTC.tc1_process_prhl_form tc tbool eqR in - let gs, h = process_info info tc in - let i = EcProofTyping.tc1_process_codepos1 tc (Some `Left , i) in - let j = EcProofTyping.tc1_process_codepos1 tc (Some `Right, j) in - FApi.t_last (t_eager_seq i j eqR h) gs - -(* -------------------------------------------------------------------- *) -let process_if tc = - t_eager_if tc - -(* -------------------------------------------------------------------- *) -let process_while info tc = - let gs, h = process_info info tc in - FApi.t_last (t_eager_while h) gs +let process_seq (i, j) s factor tc = + let open BatTuple.Tuple2 in + let indices = + mapn (tc1_process_codepos1 tc) ((Some `Left, i), (Some `Right, j)) + and factor = + factor + |> ( function Single p -> (p, p) | Double pp -> pp ) + |> mapn (TTC.tc1_process_prhl_form tc tbool) + and s = TTC.tc1_process_prhl_stmt tc `Left s in + + t_eager_seq indices s factor tc + +let process_if = t_eager_if + +let process_while inv tc = + (* This is performed here only to recover [e{1}] and setup + the consequence rule accordingly. *) + let es, _, w, _ = destruct_on_op `While tc in + let e, _ = destr_while w in + let e1 = ss_inv_of_expr (fst es.es_ml) e in + + let inv = TTC.tc1_process_prhl_form tc tbool inv in + (EcPhlConseq.t_equivS_conseq inv + { inv with inv = f_and inv.inv (f_not e1.inv) } + @+ [ t_trivial; t_trivial; t_eager_while inv ]) + tc -(* -------------------------------------------------------------------- *) -let process_fun_def tc = - t_eager_fun_def tc +let process_fun_def tc = t_eager_fun_def tc -(* -------------------------------------------------------------------- *) -let process_fun_abs info eqI tc = - let eg = EcLowPhlGoal.tc1_as_eagerF tc in - let ml, mr = eg.eg_ml, eg.eg_mr in - let hyps = FApi.tc1_hyps tc in - let env = LDecl.inv_memenv ml mr hyps in - let eqI = TTC.pf_process_form !!tc env tbool eqI in - let gs, h = process_info info tc in - FApi.t_last (t_eager_fun_abs {inv=eqI;ml;mr} h) gs +let process_fun_abs inv tc = + let hyps = FApi.tc1_hyps tc in + let { eg_ml = ml; eg_mr = mr } = tc1_as_eagerF tc in + let env = LDecl.inv_memenv ml mr hyps in + let inv = TTC.pf_process_formula !!tc env inv in + t_eager_fun_abs { ml; mr; inv } tc -(* -------------------------------------------------------------------- *) let process_call info tc = - let process_cut info = - match info with - | EcParsetree.CI_spec (fpre, fpost) -> - let env, hyps, _ = FApi.tc1_eflat tc in - let es = tc1_as_equivS tc in + let process_cut' fpre fpost = + let env, hyps, _ = FApi.tc1_eflat tc in + let es = tc1_as_equivS tc in - let (_,fl,_), sl = tc1_last_call tc es.es_sl in - let (_,fr,_), sr = tc1_first_call tc es.es_sr in + let (_, fl, _), sl = tc1_last_call tc es.es_sl in + let (_, fr, _), sr = tc1_first_call tc es.es_sr in - check_only_global !!tc env sl; - check_only_global !!tc env sr; + check_only_global !!tc env sl; + check_only_global !!tc env sr; - let (ml, mr) = fst es.es_ml, fst es.es_mr in - let penv, qenv = LDecl.equivF ml mr fl fr hyps in - let fpre = TTC.pf_process_form !!tc penv tbool fpre in - let fpost = TTC.pf_process_form !!tc qenv tbool fpost in - f_eagerF {ml;mr;inv=fpre} sl fl fr sr {ml;mr;inv=fpost} - - | _ -> tc_error !!tc "invalid arguments" + let ml, mr = (fst es.es_ml, fst es.es_mr) in + let penv, qenv = LDecl.equivF ml mr fl fr hyps in + let fpre = TTC.pf_process_formula !!tc penv fpre in + let fpost = TTC.pf_process_formula !!tc qenv fpost in + f_eagerF { ml; mr; inv = fpre } sl fl fr sr { ml; mr; inv = fpost } + in + let process_cut = function + | EcParsetree.CI_spec (fpre, fpost) -> process_cut' fpre fpost + | CI_inv inv -> process_cut' inv inv + | _ -> tc_error !!tc "eager: invalid call specification" in let pt, ax = - PT.tc1_process_full_closed_pterm_cut ~prcut:process_cut tc info in + PT.tc1_process_full_closed_pterm_cut ~prcut:process_cut tc info + in let eg = pf_as_eagerF !!tc ax in FApi.t_on1seq 0 (t_eager_call (eg_pr eg) (eg_po eg)) (EcLowGoal.Apply.t_apply_bwd_hi ~dpe:true pt) tc - -(* -------------------------------------------------------------------- *) -let process_eager info inv tc = - let inv = TTC.tc1_process_prhl_form tc tbool inv in - let gs, h = process_info info tc in - FApi.t_last (t_eager h inv) gs diff --git a/src/phl/ecPhlEager.mli b/src/phl/ecPhlEager.mli index 1958df552..6d5c2b058 100644 --- a/src/phl/ecPhlEager.mli +++ b/src/phl/ecPhlEager.mli @@ -1,95 +1,103 @@ (* -------------------------------------------------------------------- *) +open EcAst open EcUtils open EcParsetree open EcCoreGoal.FApi open EcMatching.Position -open EcAst -(* -------------------------------------------------------------------- *) -val t_eager_seq : codepos1 -> codepos1 -> ts_inv -> EcIdent.t -> backward -val t_eager_if : backward -val t_eager_while : EcIdent.t -> backward -val t_eager_fun_def : backward -val t_eager_fun_abs : ts_inv -> EcIdent.t -> backward -val t_eager_call : ts_inv -> ts_inv -> backward +val process_seq : pcodepos1 pair -> pstmt -> pformula doption -> backward +(** Tactic [eager seq] derives the following proof: + {v + (a) S; c₁ ~ c₁'; S : P ==> R₂ + (b) S; c₂ ~ c₂'; S : R₁ ==> Q + (c) c₁' ~ c₁' : Eq ==> R₁ + (d) c₂ ~ c₂ : R₂ ==> ={Q.1} + ----------------------------------- + S; c₁; c₂ ~ c₁'; c₂'; S : P ==> Q + v} + where [R₁] and [R₂] are provided manually (and equal if a single value was + provided), as well as [S]. The predicate [={Q.1}] means equality on all free + variables bound to the first memory in [Q]. *) + +val t_eager_seq : codepos1 pair -> stmt -> ts_inv pair -> backward +(** Internal variant of [eager seq] *) + +val process_if : backward +(** Tactic [eager if] derives the following proof: + {v + (a) forall &1 &2, P => e{1} = e'{2} + (b) forall &2 b, S : P /\ e = b ==> e = b + (c) S; c₁ ~ c₁'; S : P /\ e{1} ==> Q + (d) S; c₂ ~ c₂'; S : P /\ !e{1} ==> Q + -------------------------------------------- + S; if e then c₁ else c₂ + ~ if e' then c₁' else c₂'; S : P ==> Q + v} *) + +val t_eager_if : backward +(** Internal variant of [eager if] *) + +val process_while : pformula -> backward +(** Tactic [eager while] derives the following proof: + {v + (a) I => ={e, I.1} + (b) S; c ~ c'; S : I /\ e{1} ==> I + (c) forall b &2, S : e = b ==> e = b + (d) c' ~ c' : Eq ==> I + (e) c ~ c : I ==> I + (f) S ~ S : I /\ !e{1} ==> I + -------------------------------------------------------- + S; while e do c ~ while e do c'; S : I ==> I /\ !e{1} + v} + Where the invariant [I] is manually provided. + Please note that the guard [e] is syntactically identical in both + programs. *) + +val t_eager_while : ts_inv -> backward +(** Internal variant of [eager while] *) -(* -------------------------------------------------------------------- *) -val process_seq : eager_info -> pcodepos1 pair -> pformula -> backward -val process_if : backward -val process_while : eager_info -> backward val process_fun_def : backward -val process_fun_abs : eager_info -> pformula -> backward -val process_call : call_info gppterm -> backward -val process_eager : eager_info -> pformula -> backward +(** Tactic [eager proc] derives the following proof: + {v + (0) S and S' depend only of global (typing invariant) + (a) S; f.body; result = f.res; ~ S'; f'.body; result' = f'.res + : P ==> Q{res{1} <- result, res{2} <- result'} + ---------------------------------------------------------------- + S, f ~ f', S : P ==> Q + v} *) -(* -------------------------------------------------------------------- *) -(* [eager-seq] - * (a) c1;S ~ S;c1' : P ==> ={R} - * (b) c2;S ~ S;c2' : ={R} ==> Q - * (c) c2' ~ c2' : ={R.2} ==> ={Q.2} - * (d) ={R} => ={Is} - * (e) compat S S' R Xs - * (h) S ~ S' : ={Is} ==> ={Xs} - * -------------------------------------------------- - * c1;c2;S ~ S;c1';c2' : P ==> Q - * - * where compat S S' R Xs = - * forall modS modS', ={Xs{modS,modS'}} => ={R{modS,modS'}} - * - * [eager-if] - * (a) P => e{1} = e'{2} - * (b) S;c1 ~ S';c1' : P /\ e{1} ==> Q - * (c) S;c2 ~ S';c2' : P /\ !e{1} ==> Q - * (d) forall b &2, S : P /\ e = b ==> e = b - * -------------------------------------------------- - * S;if e then c1 else c2 - * ~ if e' then c1' else c2';S' : P ==> Q - * - * [eager-while] - * - * (a) ={I} => e{1} = e{2} - * (b) S;c ~ c';S' : ={I} /\ e{1} ==> ={I} - * (c) c' ~ c' : ={I.2} ==> ={I.2} - * (d) forall b &2, S : e = b ==> e = b - * (e) ={I} => ={Is} - * (f) compat S S' I Xs - * (h) S ~ S' : ={Is} ==> ={Xs} - * -------------------------------------------------- - * S;while e do c ~ while e' do c';S' - * : ={I} ==> ={I,Xs} /\ !e{1} - * - * [eager-fun-def] - * - * (a) S and S' depend only of global - * (this should be an invariant of the typing) - * (b) S;f.body;result = f.res; ~ S';f'.body;result' = f'.res - * : P ==> Q{res{1}<- result, res{2} <- result'} - * -------------------------------------------------- - * S, f ~ f', S' : P ==> Q - * - * [eager-fun-abs] - * - * S and S' depend only of global (hould be an invariant of the typing) - * - * (a) ={I} => e{1} = e{2} - * for each oracles o o': - * o and o' do not modify (glob A) (this is implied by (f)) - * (b) S,o ~ o',S' : ={I,params} ==> ={I,res} - * (c) o'~ o' : ={I.2, o'.params} ==> ={I.2, res} - * (e) ={I} => ={Is} - * (f) compat S S' I Xs - * (h) S ~ S' : ={Is} ==> ={Xs} - * (i) glob A not in I (checked in EcPhlFun.equivF_abs_spec) - * (j) S, S' do not modify glob A - * -------------------------------------------------- - * S, A.f{o} ~ A.f(o'), S' - * : ={I,glob A,A.f.params} ==> ={I,glob A,res} - * - * Remark : ={glob A} is not required in pre condition when A.f is an initializer - * - * [eager-call] - * S,f ~ f',S' : fpre ==> fpost - * S do not write a - * -------------------------------------------------- - * S;x = f(a) ~ x' = f'(a');S' : wp_call fpre fpost post ==> post - *) +val t_eager_fun_def : backward +(** Internal variant of [eager proc] *) + +val process_call : call_info gppterm -> backward +(** Tactic [eager call] derives the following proof: + {v + (a) S, f ~ f', S : fpre ==> fpost + (b) S does not write a + ------------------------------------------------------------------ + S; x = f(a) ~ x' = f'(a'); S : wp_call fpre fpost post ==> post + v} *) + +val t_eager_call : ts_inv -> ts_inv -> backward +(** Internal variant of [eager call] *) + +val process_fun_abs : pformula -> backward +(** Tactic [eager call] (on abstract functions) derives the following proof: + {v + (0) S depends only on globals (typing invariant) + (a) I is a conjunction of same-name variable equalities + (b) glob A not in I (checked in EcPhlFun.equivF_abs_spec) + (c) S does not modify glob A + (d) S ~ S : I ==> I + for each oracles o o': + o and o' do not modify (glob A) + (e) S, o ~ o', S : I /\ ={o'.params} ==> I /\ ={res} + (f) o' ~ o' : Eq ==> I /\ ={res} + (g) o ~ o : I /\ ={o.params} ==> I /\ ={res} + -------------------------------------------------------- + S, A.f{o} ~ A.f(o'), S + : I /\ ={glob A, A.f.params} ==> I /\ ={glob A, res} + v} *) + +val t_eager_fun_abs : ts_inv -> backward +(** Internal variant of [eager call] (on abstract functions) *) diff --git a/src/phl/ecPhlEqobs.ml b/src/phl/ecPhlEqobs.ml index 6fc4497fd..8466ac935 100644 --- a/src/phl/ecPhlEqobs.ml +++ b/src/phl/ecPhlEqobs.ml @@ -469,8 +469,8 @@ let process_eqobs_inS info tc = (FApi.t_try (FApi.t_seq EcPhlSkip.t_skip t_trivial)) (t_eqobs_inS sim eqo tc) | Some(p1,p2) -> - let p1 = EcProofTyping.tc1_process_codepos1 tc (Some `Left , p1) in - let p2 = EcProofTyping.tc1_process_codepos1 tc (Some `Right, p2) in + let p1 = EcLowPhlGoal.tc1_process_codepos1 tc (Some `Left , p1) in + let p2 = EcLowPhlGoal.tc1_process_codepos1 tc (Some `Right, p2) in let _,sl2 = s_split env p1 es.es_sl in let _,sr2 = s_split env p2 es.es_sr in let _, eqi = @@ -499,7 +499,7 @@ let process_eqobs_inF info tc = let fl = ef.ef_fl and fr = ef.ef_fr in let eqo = match info.EcParsetree.sim_eqs with - | Some pf -> + | Some pf -> let _,(mle,mre) = Fun.equivF_memenv ml mr fl fr env in let hyps = LDecl.push_active_ts mle mre hyps in process_eqs env tc {ml; mr; inv=TTC.pf_process_form !!tc hyps tbool pf} diff --git a/src/phl/ecPhlHiCond.ml b/src/phl/ecPhlHiCond.ml index 77ffeb1b2..89225fb4d 100644 --- a/src/phl/ecPhlHiCond.ml +++ b/src/phl/ecPhlHiCond.ml @@ -22,8 +22,8 @@ let process_cond (info : EcParsetree.pcond_info) tc = | `Seq (side, (i1, i2), f) -> let es = tc1_as_equivS tc in let f = EcProofTyping.tc1_process_prhl_formula tc f in - let i1 = Option.map (fun i1 -> EcProofTyping.tc1_process_codepos1 tc (side, i1)) i1 in - let i2 = Option.map (fun i2 -> EcProofTyping.tc1_process_codepos1 tc (side, i2)) i2 in + let i1 = Option.map (fun i1 -> EcLowPhlGoal.tc1_process_codepos1 tc (side, i1)) i1 in + let i2 = Option.map (fun i2 -> EcLowPhlGoal.tc1_process_codepos1 tc (side, i2)) i2 in let n1 = default_if i1 es.es_sl in let n2 = default_if i2 es.es_sr in FApi.t_seqsub (EcPhlApp.t_equiv_app (n1, n2) f) @@ -31,7 +31,7 @@ let process_cond (info : EcParsetree.pcond_info) tc = | `SeqOne (s, i, f1, f2) -> let es = tc1_as_equivS tc in - let i = Option.map (fun i1 -> EcProofTyping.tc1_process_codepos1 tc (Some s, i1)) i in + let i = Option.map (fun i1 -> EcLowPhlGoal.tc1_process_codepos1 tc (Some s, i1)) i in let n = default_if i (match s with `Left -> es.es_sl | `Right -> es.es_sr) in let _, f1 = EcProofTyping.tc1_process_Xhl_formula ~side:s tc f1 in let _, f2 = EcProofTyping.tc1_process_Xhl_formula ~side:s tc f2 in diff --git a/src/phl/ecPhlInline.ml b/src/phl/ecPhlInline.ml index bdf1a7ef6..d40f5ab8b 100644 --- a/src/phl/ecPhlInline.ml +++ b/src/phl/ecPhlInline.ml @@ -407,7 +407,7 @@ let process_inline_occs ~use_tuple side cond occs tc = let process_inline_codepos ~use_tuple side pos tc = let env = FApi.tc1_env tc in let concl = FApi.tc1_goal tc in - let pos = EcProofTyping.tc1_process_codepos tc (side, pos) in + let pos = EcLowPhlGoal.tc1_process_codepos tc (side, pos) in try match concl.f_node, side with diff --git a/src/phl/ecPhlLoopTx.ml b/src/phl/ecPhlLoopTx.ml index bef120984..434dece2c 100644 --- a/src/phl/ecPhlLoopTx.ml +++ b/src/phl/ecPhlLoopTx.ml @@ -205,18 +205,18 @@ let t_splitwhile = FApi.t_low3 "split-while" t_splitwhile_r (* -------------------------------------------------------------------- *) let process_fission (side, cpos, infos) tc = - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_fission side cpos infos tc let process_fusion (side, cpos, infos) tc = - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_fusion side cpos infos tc let process_splitwhile (b, side, cpos) tc = let b = try TTC.tc1_process_Xhl_exp tc side (Some tbool) b with EcFol.DestrError _ -> tc_error !!tc "goal must be a *HL statement" in - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_splitwhile b side cpos tc (* -------------------------------------------------------------------- *) @@ -228,7 +228,7 @@ let process_unroll_for side cpos tc = if not (List.is_empty (fst cpos)) then tc_error !!tc "cannot use deep code position"; - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in let z, cpos = Zpr.zipper_of_cpos_r env cpos c in let pos = 1 + List.length z.Zpr.z_head in @@ -305,7 +305,7 @@ let process_unroll_for side cpos tc = let t_conseq_nm tc = match (tc1_get_pre tc) with - | Inv_ss inv -> + | Inv_ss inv -> (EcPhlConseq.t_hoareS_conseq_nm inv {m=inv.m;inv=f_true} @+ [ t_trivial; t_trivial; EcPhlTAuto.t_hoare_true]) tc | _ -> tc_error !!tc "expecting single sided precondition" in @@ -337,6 +337,6 @@ let process_unroll (side, cpos, for_) tc = if for_ then process_unroll_for side cpos tc else begin - let cpos = EcProofTyping.tc1_process_codepos tc (side, cpos) in + let cpos = EcLowPhlGoal.tc1_process_codepos tc (side, cpos) in t_unroll side cpos tc - end \ No newline at end of file + end diff --git a/src/phl/ecPhlOutline.ml b/src/phl/ecPhlOutline.ml index 1fd63d0bb..2898a138b 100644 --- a/src/phl/ecPhlOutline.ml +++ b/src/phl/ecPhlOutline.ml @@ -59,7 +59,7 @@ let process_outline info tc = let ppe = EcPrinting.PPEnv.ofenv env in let range = - EcProofTyping.tc1_process_codepos_range tc + EcLowPhlGoal.tc1_process_codepos_range tc (Some side, info.outline_range) in try diff --git a/src/phl/ecPhlRCond.ml b/src/phl/ecPhlRCond.ml index 20bd429ad..d45a50ede 100644 --- a/src/phl/ecPhlRCond.ml +++ b/src/phl/ecPhlRCond.ml @@ -79,7 +79,7 @@ module Low = struct let ss_inv_generalize_other = sideif side ss_inv_generalize_right ss_inv_generalize_left in let hd,_,e,s = gen_rcond (!!tc, env) b (fst m) at_pos s in let e = ss_inv_generalize_other e (fst mo) in - let concl1 = + let concl1 = EcSubst.f_forall_mems_ss_inv (EcIdent.create "&m", snd mo) (ts_inv_lower_side2 (fun pr po -> let mhs = EcIdent.create "&hr" in @@ -112,7 +112,7 @@ let t_rcond side b at_pos tc = Low.t_equiv_rcond side b at_pos tc let process_rcond side b at_pos tc = - let at_pos = EcProofTyping.tc1_process_codepos1 tc (side, at_pos) in + let at_pos = EcLowPhlGoal.tc1_process_codepos1 tc (side, at_pos) in t_rcond side b at_pos tc (* -------------------------------------------------------------------- *) @@ -266,12 +266,12 @@ module LowMatch = struct let (epr, hd, po1), (me, full) = gen_rcond_full (!!tc, FApi.tc1_env tc) c m at_pos s in - let ss_inv_generalize_other inv = sideif side + let ss_inv_generalize_other inv = sideif side (ss_inv_generalize_right inv mr) (ss_inv_generalize_left inv ml) in let epr = omap (fun epr -> ss_inv_generalize_other (ss_inv_rebind epr (fst m))) epr in - + let ts_inv_lower_side1 = sideif side ts_inv_lower_left1 ts_inv_lower_right1 in @@ -319,5 +319,5 @@ let t_rcond_match side c at_pos tc = (* -------------------------------------------------------------------- *) let process_rcond_match side c at_pos tc = - let at_pos = EcProofTyping.tc1_process_codepos1 tc (side, at_pos) in + let at_pos = EcLowPhlGoal.tc1_process_codepos1 tc (side, at_pos) in t_rcond_match side c at_pos tc diff --git a/src/phl/ecPhlRewrite.ml b/src/phl/ecPhlRewrite.ml index da5057855..57abab3d5 100644 --- a/src/phl/ecPhlRewrite.ml +++ b/src/phl/ecPhlRewrite.ml @@ -55,7 +55,7 @@ let process_change (form : pexpr) (tc : tcenv1) = - let pos = EcProofTyping.tc1_process_codepos tc (side, pos) in + let pos = EcLowPhlGoal.tc1_process_codepos tc (side, pos) in let expr (e : expr) ((hyps, m) : LDecl.hyps * memenv) = let hyps = LDecl.push_active_ss m hyps in @@ -96,7 +96,7 @@ let process_rewrite_rw let f2 = EcProofTerm.concretize_form pt.ptev_env f2 in let pt, _ = EcProofTerm.concretize pt in - let cpos = + let cpos = EcMatching.FPosition.select_form ~xconv:`AlphaEq ~keyed:occmode.k_keyed hyps None subf.inv e.inv in @@ -118,7 +118,7 @@ let process_rewrite_rw (m, data), expr_of_ss_inv e in - let pos = EcProofTyping.tc1_process_codepos tc (side, pos) in + let pos = EcLowPhlGoal.tc1_process_codepos tc (side, pos) in let (m, (pt, mode, cpos)), tc = t_change side pos change tc in let cpos = EcMatching.FPosition.reroot [1] cpos in @@ -147,7 +147,7 @@ let change (e : expr) ((hyps, me) : LDecl.hyps * memenv) = (fst me, f), e in - let pos = EcProofTyping.tc1_process_codepos tc (side, pos) in + let pos = EcLowPhlGoal.tc1_process_codepos tc (side, pos) in let (m, f), tc = t_change side pos change tc in FApi.t_first ( @@ -172,20 +172,20 @@ let process_rewrite (* -------------------------------------------------------------------- *) let t_change_stmt (side : side option) - (pos : EcMatching.Position.codepos_range) + (pos : EcMatching.Position.codepos_range) (s : stmt) (tc : tcenv1) = let env = FApi.tc1_env tc in - let me, stmt = EcLowPhlGoal.tc1_get_stmt side tc in + let me, stmt = EcLowPhlGoal.tc1_get_stmt side tc in let (zpr, _), (stmt, epilog) = EcMatching.Zipper.zipper_and_split_of_cpos_range env pos stmt in let pvs = EcPV.is_write env (stmt @ s.s_node) in let pvs, globs = EcPV.PV.elements pvs in - let pre_pvs, pre_globs = EcPV.PV.elements @@ EcPV.PV.inter - (EcPV.is_read env stmt) + let pre_pvs, pre_globs = EcPV.PV.elements @@ EcPV.PV.inter + (EcPV.is_read env stmt) (EcPV.is_read env s.s_node) in @@ -201,7 +201,7 @@ let t_change_stmt (fun mp -> f_eqglob mp mleft mp mright) globs in - let pre_eq = + let pre_eq = List.map (fun (pv, ty) -> f_eq (f_pvar pv ty mleft).inv (f_pvar pv ty mright).inv) pre_pvs @@ -214,7 +214,7 @@ let t_change_stmt let goal1 = f_equivS (snd me) (snd me) - {ml=mleft; mr=mright; inv=f_ands pre_eq} + {ml=mleft; mr=mright; inv=f_ands pre_eq} (EcAst.stmt stmt) s {ml=mleft; mr=mright; inv=f_ands eq} in @@ -238,31 +238,31 @@ let process_change_stmt let env = FApi.tc1_env tc in begin match side, (FApi.tc1_goal tc).f_node with - | _, FhoareF _ + | _, FhoareF _ | _, FeHoareF _ | _, FequivF _ | _, FbdHoareF _ -> tc_error !!tc "Expecting goal with inlined program code" - | Some _, FhoareS _ + | Some _, FhoareS _ | Some _, FeHoareS _ | Some _, FbdHoareS _-> tc_error !!tc "Tactic should not receive side for non-relational goal" | None, FequivS _ -> tc_error !!tc "Tactic requires side selector for relational goal" - | None, FhoareS _ + | None, FhoareS _ | None, FeHoareS _ | None, FbdHoareS _ | Some _ , FequivS _ -> () | _ -> tc_error !!tc "Wrong goal shape, expecting hoare or equiv goal with inlined code" end; - let me, _ = EcLowPhlGoal.tc1_get_stmt side tc in + let me, _ = EcLowPhlGoal.tc1_get_stmt side tc in - let pos = + let pos = let env = EcEnv.Memory.push_active_ss me env in - EcTyping.trans_codepos_range ~memory:(fst me) env pos + EcTyping.trans_codepos_range ~memory:(fst me) env pos in - let s = match side with + let s = match side with | Some side -> EcProofTyping.tc1_process_prhl_stmt tc side s - | None -> EcProofTyping.tc1_process_Xhl_stmt tc s + | None -> EcProofTyping.tc1_process_Xhl_stmt tc s in t_change_stmt side pos s tc diff --git a/src/phl/ecPhlRnd.ml b/src/phl/ecPhlRnd.ml index 48a8ed32f..35bb3de4d 100644 --- a/src/phl/ecPhlRnd.ml +++ b/src/phl/ecPhlRnd.ml @@ -51,7 +51,7 @@ module Core = struct let m = fst hs.ehs_m in let distr = EcFol.ss_inv_of_expr m distr in let post = subst_form_lv env lv {m;inv=x} (ehs_po hs) in - let post = map_ss_inv2 (f_Ep ty_distr) distr + let post = map_ss_inv2 (f_Ep ty_distr) distr (map_ss_inv1 (f_lambda [(x_id,GTty ty_distr)]) post) in let concl = f_eHoareS (snd hs.ehs_m) (ehs_pr hs) s post in FApi.xmutate1 tc `Rnd [concl] @@ -211,7 +211,7 @@ module Core = struct | PNoRndParams, FHle -> if is_post_indep then (* event is true *) - let concl = f_bdHoareS (snd bhs.bhs_m) + let concl = f_bdHoareS (snd bhs.bhs_m) (bhs_pr bhs) s (bhs_po bhs) bhs.bhs_cmp (bhs_bd bhs) in [concl] else @@ -273,7 +273,7 @@ module Core = struct let post = map_ss_inv2 f_anda bounded_distr (mk_event_cond event) in f_forall_mems_ss_inv bhs.bhs_m (map_ss_inv2 f_imp (map_ss_inv1 f_not phi) post) in let sgoal5 = - let f_inbound x = + let f_inbound x = let f_r1, f_r0 = {m;inv=f_r1}, {m;inv=f_r0} in map_ss_inv2 f_anda (map_ss_inv2 f_real_le f_r0 x) (map_ss_inv2 f_real_le x f_r1) in map_ss_inv f_ands (List.map f_inbound [d1; d2; d3; d4]) @@ -525,11 +525,11 @@ let wp_equiv_rnd_r bij tc = let po = match hdc2, hdc3 with | None , None -> None - | Some _, Some _ -> + | Some _, Some _ -> Some (map_ts_inv2 f_anda c1 (map_ts_inv1 (f_forall [x, xty]) (map_ts_inv2 f_imp ind c4))) - | Some _, None -> + | Some _, None -> Some (map_ts_inv2 f_anda c1 (map_ts_inv1 (f_forall [x, xty]) (map_ts_inv2 f_imp ind (map_ts_inv2 f_anda c3 c4)))) - | None , Some _ -> + | None , Some _ -> Some (map_ts_inv f_andas [c1; c2; map_ts_inv1 (f_forall [x, xty]) (map_ts_inv2 f_imp ind c4)]) in @@ -590,7 +590,7 @@ let t_equiv_rnd_r side pos bij_info tc = match side, pos, bij_info with | Some side, None, (None, None) -> wp_equiv_disj_rnd_r side tc - | Some side, None, _ -> + | Some _side, None, _ -> tc_error !!tc "one-sided rnd takes no arguments" | None, _, _ -> begin let pos = @@ -683,16 +683,16 @@ let process_rnd side pos tac_info tc = | Single (b, p) -> let p = if Option.is_some side then - EcProofTyping.tc1_process_codepos1 tc (side, p) + EcLowPhlGoal.tc1_process_codepos1 tc (side, p) else EcTyping.trans_codepos1 (FApi.tc1_env tc) p in Single (b, p) | Double ((b1, p1), (b2, p2)) -> - let p1 = EcProofTyping.tc1_process_codepos1 tc (Some `Left , p1) in - let p2 = EcProofTyping.tc1_process_codepos1 tc (Some `Right, p2) in + let p1 = EcLowPhlGoal.tc1_process_codepos1 tc (Some `Left , p1) in + let p2 = EcLowPhlGoal.tc1_process_codepos1 tc (Some `Right, p2) in Double ((b1, p1), (b2, p2)) ) in - + t_equiv_rnd side ?pos bij_info tc | _ -> tc_error !!tc "invalid arguments" @@ -705,7 +705,7 @@ let t_equiv_rndsem = FApi.t_low3 "equiv-rndsem" Core.t_equiv_rndsem_r (* -------------------------------------------------------------------- *) let process_rndsem ~reduce side pos tc = let concl = FApi.tc1_goal tc in - let pos = EcProofTyping.tc1_process_codepos1 tc (side, pos) in + let pos = EcLowPhlGoal.tc1_process_codepos1 tc (side, pos) in match side with | None when is_hoareS concl -> diff --git a/src/phl/ecPhlRwEquiv.ml b/src/phl/ecPhlRwEquiv.ml index b47d05084..100d49e72 100644 --- a/src/phl/ecPhlRwEquiv.ml +++ b/src/phl/ecPhlRwEquiv.ml @@ -56,7 +56,7 @@ let t_rewrite_equiv side dir cp (equiv : equivF) equiv_pt rargslv tc = (* Extract the call statement and surrounding code *) let prefix, (llv, func, largs), suffix = - let cp = EcProofTyping.tc1_process_codepos1 tc (Some side, cp) in + let cp = EcLowPhlGoal.tc1_process_codepos1 tc (Some side, cp) in let p, i, s = s_split_i env cp code in if not (is_call i) then rwe_error RWE_InvalidPosition; @@ -146,7 +146,7 @@ let process_rewrite_equiv info tc = let res = omap (fun v -> EcTyping.transexpcast subenv `InProc ue ret_ty v) pres in let es = e_subst (Tuni.subst (EcUnify.UniEnv.close ue)) in Some (List.map es args, omap (EcModules.lv_of_expr |- es) res) - with EcUnify.UninstanciateUni -> + with EcUnify.UninstantiateUni -> EcTyping.tyerror (loc pargs) env EcTyping.FreeTypeVariables end in @@ -159,4 +159,3 @@ let process_rewrite_equiv info tc = tc_error !!tc "rewrite equiv: function mismatch\nExpected %s but got %s" (x_tostring wanted) (x_tostring got) | RwEquivError RWE_InvalidPosition -> tc_error !!tc "rewrite equiv: targetted instruction is not a function call" - diff --git a/src/phl/ecPhlSym.ml b/src/phl/ecPhlSym.ml index 4868fa478..7d706dcc3 100644 --- a/src/phl/ecPhlSym.ml +++ b/src/phl/ecPhlSym.ml @@ -20,7 +20,7 @@ let t_equivS_sym tc = let (ml, mtl), (mr, mtr) = es.es_ml, es.es_mr in let pr = {ml;mr;inv=(ts_inv_rebind (es_pr es) mr ml).inv} in let po = {ml;mr;inv=(ts_inv_rebind (es_po es) mr ml).inv} in - let cond = f_equivS mtl mtr pr es.es_sr es.es_sl po in + let cond = f_equivS mtr mtl pr es.es_sr es.es_sl po in FApi.xmutate1 tc `EquivSym [cond] (*-------------------------------------------------------------------- *) diff --git a/tests/byehoare-arg.ec b/tests/byehoare-arg.ec new file mode 100644 index 000000000..3fd7eb934 --- /dev/null +++ b/tests/byehoare-arg.ec @@ -0,0 +1,28 @@ +require import AllCore Int Real Xreal. + +module M = { + proc main_int(x : int) = { + return x; + } + + proc main_bool(x : bool) = { + return x; + } +}. + +lemma L &m (_x : int): + Pr [ M.main_int(_x) @ &m : _x = res ] <= 1%r. +proof. +byehoare (_: ((arg = _x) `|` (1%xr)) ==> _) => //. +- proc; auto => &hr. + by apply xle_cxr_r => ->. +qed. + +lemma L1 (&m: {arg: bool}): !arg{m} => + Pr [ M.main_bool(true) @ &m : true] <= 0%r. +proof. +move => arg_eq. +byehoare (_: (!arg{m})%xr ==> _). ++ proc; auto. by rewrite arg_eq. +fail by auto. +abort. diff --git a/tests/call_with_op.ec b/tests/call_with_op.ec new file mode 100644 index 000000000..f0832de4f --- /dev/null +++ b/tests/call_with_op.ec @@ -0,0 +1,59 @@ +require import AllCore. + +module M = { + proc f(x:int) : int = { + return x; + } + + proc g(x:int) : int = { + var z; + z <@ f(x); + return z; + } +}. + +op f_spec x_ = hoare [M.f : x = x_ ==> res = x_]. +op g_spec x_ = hoare [M.g : x = x_ ==> res = x_]. + +lemma f_ok1 x_ : f_spec x_. +proof. + proc; auto. +qed. + +lemma g_ok1 x_ : g_spec x_. +proof. + proc. + call (f_ok1 x_). + auto. +qed. + +lemma g_ok1_e x_ : g_spec x_. +proof. + proc. + ecall (f_ok1 x). + auto. +qed. + +op f_spec_all = forall x_, hoare [M.f : x = x_ ==> res = x_]. + +lemma f_ok2 : f_spec_all. +proof. + move=> x_;proc; auto. +qed. + +lemma g_ok2 x_ : g_spec x_. +proof. + proc. + call (f_ok2 x_). + auto. +qed. + +lemma g_ok2_e x_ : g_spec x_. +proof. + proc. + ecall (f_ok2 x). + auto. +qed. + + + diff --git a/tests/conseq_equiv_phoare.ec b/tests/conseq_equiv_phoare.ec new file mode 100644 index 000000000..d6383d90e --- /dev/null +++ b/tests/conseq_equiv_phoare.ec @@ -0,0 +1,42 @@ +require import Real Int. + +module M = { + var b: bool + + proc run() = { + M.b <- false; + } +}. + +lemma dep_bound : phoare[M.run : M.b ==> !M.b] = (b2i M.b)%r. +proof. by proc; auto => &hr ->. qed. + +equiv triv_equiv : M.run ~ M.run : true ==> ={M.b}. +proof. proc; auto. qed. + +lemma bad_bound : phoare[M.run : true ==> !M.b] = (b2i M.b)%r. +proof. +conseq triv_equiv dep_bound => //=. +move => &1. +fail smt(). +abort. + +lemma dep_bound_conseq : + phoare[M.run : !M.b ==> !M.b] = (1-b2i M.b)%r. +proof. +conseq triv_equiv dep_bound => //=. +move => &1 -> /=. +by exists true => />. +qed. + +lemma nodep_bound : phoare[M.run: true ==> true] = 1%r. +proof. proc; auto. qed. + +lemma nodep_bound_conseq : + phoare[M.run : !M.b ==> !M.b] = 1%r. +proof. +conseq triv_equiv dep_bound => //=. +move => &1 /> _. +by exists true. +qed. + diff --git a/tests/conseq_equiv_phoare_at_equiv.ec b/tests/conseq_equiv_phoare_at_equiv.ec new file mode 100644 index 000000000..e45598b97 --- /dev/null +++ b/tests/conseq_equiv_phoare_at_equiv.ec @@ -0,0 +1,13 @@ +module Foo = { + proc foo(i : int) = { + } +}. + +lemma foo_corr : hoare [ Foo.foo : true ==> true] by proc;auto. + +lemma foo_eq : equiv [ Foo.foo ~ Foo.foo : ={arg} ==> true ] by sim. + +lemma foo_eq_corr: + equiv [ Foo.foo ~ Foo.foo : ={arg} ==> ={res} ]. + conseq foo_eq foo_corr. +qed. diff --git a/tests/conseq_phoare_hoare.ec b/tests/conseq_phoare_hoare.ec new file mode 100644 index 000000000..021622544 --- /dev/null +++ b/tests/conseq_phoare_hoare.ec @@ -0,0 +1,14 @@ +require import Real. + +module Foo = {proc foo() = {}}. + +lemma foo_ll : islossless Foo.foo by islossless. + +op [opaque] p = predT<:int>. + +lemma foo_h: hoare [ Foo.foo : true ==> forall j, p j]. +proof. by proc; auto => /> j; rewrite /p. qed. + +lemma foo_p: phoare[ Foo.foo : true ==> forall j, p j] = 1%r. +by conseq foo_ll foo_h. +qed. diff --git a/tests/positivity_checking.ec b/tests/positivity_checking.ec new file mode 100644 index 000000000..d50d815bf --- /dev/null +++ b/tests/positivity_checking.ec @@ -0,0 +1,40 @@ +(* Simple type *) +type 'a list = [ Nil | Cons of 'a & 'a list ]. + +type 'a tree = [ + | Leaf + (* Recursive occurrence within a pre-existing type constructor *) + | Node of 'a tree list + (* Positive occurrence in a function *) + | Fun of (bool -> 'a tree) +]. + +theory Bad. +type ('a, 'b) permlist = [ + | N of ('a -> 'b) (* Aaaaah *) + | C of 'a & ('a, 'b) permlist + | P of ('b, 'a) permlist +]. + +fail type posrej = [ A | B of (bool, posrej) permlist ]. +end Bad. + +theory Good. +type ('a, 'b) permlist = [ + | N (* No problem *) + | C of 'a & ('a, 'b) permlist + | P of ('b, 'a) permlist list (* For the sake of nesting in a list *) +]. + +(* this type fails because of the same limitation, + even though it is in fact strictly positive. *) +fail type posrej = [ A | B of (bool, posrej) permlist ]. +end Good. + +type ('a, 'b) arr = 'a -> 'b. +type ('a, 'b) orr = ('a, 'b) arr. +fail type 'a u = [ S | U of ('a u, bool) orr ]. + +type 'a t. +fail type tt = [ N | T of tt t ]. +fail type 'a tt = [ N | T of 'a tt tt]. diff --git a/tests/proc_with_op.ec b/tests/proc_with_op.ec new file mode 100644 index 000000000..7c79de262 --- /dev/null +++ b/tests/proc_with_op.ec @@ -0,0 +1,14 @@ +require import AllCore. + +module M = { + proc f () : int = { + return 0; + } +}. + +op spec = hoare[M.f : true ==> true]. + +lemma Mf : spec. +proc. +auto. +qed. diff --git a/tests/symmetry.ec b/tests/symmetry.ec new file mode 100644 index 000000000..a2c527c06 --- /dev/null +++ b/tests/symmetry.ec @@ -0,0 +1,15 @@ +module M = { + proc f() = { + var f : int; + + f <- 0; + } + + proc g() = {} +}. + +equiv toto: M.g ~ M.f: true ==> ={res}. +proof. +proc. symmetry. +conseq (:true ==> true) (: true ==> f=0). +abort. diff --git a/theories/crypto/PROM.ec b/theories/crypto/PROM.ec index c672898f6..59fd19bc5 100644 --- a/theories/crypto/PROM.ec +++ b/theories/crypto/PROM.ec @@ -692,8 +692,7 @@ lemma eager_D : D(RRO).distinguish, RRO.resample(); : ={glob D, FRO.m, arg} ==> ={FRO.m, glob D} /\ ={res}]. proof. -eager proc (H_: RRO.resample(); ~ RRO.resample();: ={FRO.m} ==> ={FRO.m}) - (={FRO.m}) =>//; try by sim. +eager proc (={FRO.m}) =>//; try by sim. + by apply eager_init. + by apply eager_get. + by apply eager_set. diff --git a/theories/datatypes/Xreal.ec b/theories/datatypes/Xreal.ec index a395b4017..026327f55 100644 --- a/theories/datatypes/Xreal.ec +++ b/theories/datatypes/Xreal.ec @@ -1,7 +1,7 @@ require import AllCore RealSeries List Distr StdBigop DBool DInterval. require import StdOrder. require Subtype Bigop. -import Bigreal Bigint RealOrder. +import Bigreal Bigint RealOrder Biased. (* -------------------------------------------------------------------- *) (* Definition of R+ *) @@ -399,6 +399,9 @@ proof. case: x y => [x|] [y|] //=; smt(@Rp). qed. lemma xle_add_l x y : x <= y + x. proof. rewrite addmC xle_add_r. qed. +lemma xle_rle (x y : real) : 0%r <= x <= y => x%xr <= y%xr. +proof. by move => [??] /=; rewrite !to_pos_pos // &(ler_trans x). qed. + lemma xler_add2r (x:realp) (y z : xreal) : y + x%xr <= z + x%xr <=> y <= z. proof. case: z => // z; case: y => //= y; smt(@Rp). qed. @@ -963,6 +966,14 @@ proof. by rewrite big_consT big_seq1 /= !dbool1E. qed. +lemma Ep_dbiased (p : real) (f : bool -> xreal) : + 0%r <= p <= 1%r => Ep (dbiased p) f = p ** f true + (1%r - p) ** f false. +proof. + move => ?. + rewrite (Ep_fin [true; false]) //; 1: by case. + by rewrite /BXA.big /predT /= !dbiased1E /= !clamp_id //. +qed. + (* -------------------------------------------------------------------- *) lemma Ep_dinterval (f : int -> xreal) i j: Ep [i..j] f = diff --git a/theories/distributions/SDist.ec b/theories/distributions/SDist.ec index ef3b2f244..70610147a 100644 --- a/theories/distributions/SDist.ec +++ b/theories/distributions/SDist.ec @@ -509,7 +509,7 @@ local module Gr(O : Oracle_i) = { } }. -(* TOTHINK: Can this be strenthened by dropping the requirement that +(* TOTHINK: Can this be strengthened by dropping the requirement that d1 and d2 are lossless? The current proof uses the eager tactics to swap the statement [if (Var.b) Var.x <$ Var.d;] over the call to the adversary, which only works if the distributions are lossless. *) @@ -538,12 +538,11 @@ byequiv => //. have eq_main_O1e_O1l: equiv[Game(A, O1e).main ~ Gr(O1l).main: ={arg, glob A} /\ arg{1} = d' ==> ={res}]. + proc; inline *. - seq 6 6 : (={glob Var, glob A}); 1: by auto. - eager (H : if (Var.b) Var.x <$ Var.d; ~ if (Var.b) Var.x <$ Var.d; - : ={glob Var} ==> ={glob Var} ) - : (={glob A,glob Var} ) => //; 1: by sim. -eager proc H (={glob Var}) => //; 2: by sim. - proc*; inline *; rcondf{2} 6; [ by auto | by sp; if; auto]. + seq 6 6 : (={glob Var, glob A}); 1: by auto. + eager call (: ={glob Var, glob A} ==> ={glob Var, glob A, res}) => //. + eager proc (={glob Var}) => //; try sim. + eager proc. + by inline*; rcondf{2} 6; [ by auto | by sp; if; auto]. proc. transitivity* {1} {r <@ Game(A, O1e).main(d);}. + by inline *; rcondt{2} 8; auto; call(: ={Var.x}); 1: sim; auto.